Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/translation service #2615

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from

Conversation

gregslag
Copy link

@gregslag gregslag commented Jan 21, 2025

Relates to

N/A

Risks

Low

Background

What does this PR do?

  • Implements a new service for text translation inside the plugin-node
  • Create a new command for client-discord and use the translation service

What kind of change is this?

Features:

  • New translation service for plugin-node with one provider (OpenAI) implemented
  • New slash command for client-discord for text translations

The idea is to make available a text translation service using IA or other providers (e.g. DeepL, Google Cloud Translation). At this moment I only implement a slash command for Discord, but it can be used for other clients integrations.

Documentation changes needed?

  • plugins.md file updated
  • clients.md file updated

Testing

Where should a reviewer start?

  • Set the TRANSLATION_PROVIDER ENV variable in .env (for now, just openai is accepted)
  • Confirgure the client-discord

Detailed testing steps

  • Add the bot to a server
  • Use the slash command /translate

image
image

Summary by CodeRabbit

Release Notes: Translation Service and Discord Integration

  • New Features

    • Added translation functionality to Discord bot
    • Introduced slash command /translate for translating messages
    • Implemented translation service with OpenAI provider
  • Configuration Updates

    • Added new environment configuration for translation provider
    • New configuration option for Marlin TEE Plugin attestation endpoint
  • Documentation

    • Updated client and plugin documentation to reflect new translation capabilities
  • Testing

    • Added comprehensive unit tests for translation service

These release notes highlight the key improvements in translation services and Discord bot functionality, focusing on the end-user experience and new capabilities.

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @gregslag! Welcome to the elizaOS community. Thanks for submitting your first pull request; your efforts are helping us accelerate towards AGI. We'll review it shortly. You are now an elizaOS contributor!

Copy link
Contributor

coderabbitai bot commented Jan 21, 2025

Important

Review skipped

Auto reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

This pull request introduces translation capabilities to the platform by adding a new TranslationService, configuring a translation provider mechanism, and implementing a Discord slash command for text translation. The changes span multiple files, introducing configuration options, service interfaces, and implementation details for translating messages using OpenAI as the initial provider.

Changes

File Change Summary
.env.example Added TRANSLATION_PROVIDER configuration
packages/core/src/types.ts Added ITranslationService interface, TranslationProvider enum, updated ServiceType
packages/client-discord/src/index.ts Added "translate" slash command
packages/client-discord/src/messages.ts Implemented handleTranslateCommand method
packages/plugin-node/src/services/translation.ts Created TranslationService with OpenAI translation logic

Possibly related PRs


🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (8)
packages/plugin-node/src/services/translation.ts (2)

88-88: Make the OpenAI model configurable

The OpenAI model is hardcoded as "gpt-4o-mini". Making the model configurable enhances flexibility and ease of updates.

Proposed change:

-            model: "gpt-4o-mini",
+            model: this.runtime.getSetting("OPENAI_MODEL") || "gpt-4",

18-62: Simplify provider initialization logic

The initialize method contains nested conditionals and repeated code for initializing the translation provider. Refactoring this logic can improve readability and reduce duplication.

Consider consolidating the provider selection logic into a more streamlined structure.

packages/core/src/types.ts (1)

1627-1629: Well-structured enum for future extensibility!

Consider adding JSDoc comments to document supported providers and their requirements.

+/**
+ * Available translation providers
+ * @property OpenAI - Uses OpenAI's API for translation
+ */
 export enum TranslationProvider {
     OpenAI = "openai",
 }
packages/plugin-node/__tests__/translation.test.ts (2)

42-58: Consider improving type safety of mock runtime.

The as unknown as IAgentRuntime type assertion could be replaced with a proper mock implementation.

-        } as unknown as IAgentRuntime;
+        mockRuntime = {
+            character: {
+                settings: {
+                    translation: TranslationProvider.OpenAI,
+                },
+            },
+            getSetting: vi.fn().mockImplementation((key: string) => ({
+                OPENAI_API_KEY: "test-openai-key",
+            })[key]),
+        } satisfies Partial<IAgentRuntime> as IAgentRuntime;

60-129: Add tests for error handling scenarios.

Consider adding test cases for:

  • Rate limiting errors from OpenAI
  • Network timeouts
  • Invalid API responses

Example test case:

it("should handle rate limiting errors from OpenAI", async () => {
    const rateLimitError = new Error("Rate limit exceeded");
    rateLimitError.name = "RateLimitError";
    
    vi.mocked(OpenAI).mockImplementationOnce(() => ({
        chat: {
            completions: {
                create: vi.fn().mockRejectedValue(rateLimitError)
            }
        }
    }));

    await service.initialize(mockRuntime);
    const result = await service.translate("Hello, world!", "French");
    
    expect(result).toBeNull();
    expect(elizaLogger.error).toHaveBeenCalledWith(
        expect.stringContaining("Rate limit")
    );
});
packages/client-discord/src/index.ts (1)

150-167: Enhance command descriptions for better user experience.

The command descriptions could be more informative about expected inputs.

 {
     name: "translate",
-    description: "Translate text",
+    description: "Translate text to one or more languages",
     options: [
         {
             name: "text",
             type: 3, // STRING type
-            description: "Text for translation",
+            description: "The text you want to translate",
             required: true,
         },
         {
             name: "to",
             type: 3, // STRING type
-            description: "Target language(s) for translation",
+            description: "Target language(s) (e.g., 'French' or 'Spanish, German')",
             required: true,
         },
     ],
 }
.env.example (1)

176-178: Consider enhancing the configuration documentation.

While the default OpenAI provider is specified, consider adding:

  • Required API configuration for OpenAI
  • Future supported providers (if planned)
docs/docs/packages/plugins.md (1)

97-97: LGTM! Added TranslationService documentation.

The addition of TranslationService to the Node Plugin services list is appropriate.

Consider adding a brief description of the translation service's capabilities and supported languages.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d51ab66 and 562c64b.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (11)
  • .env.example (1 hunks)
  • docs/docs/packages/clients.md (1 hunks)
  • docs/docs/packages/plugins.md (3 hunks)
  • packages/client-discord/src/index.ts (2 hunks)
  • packages/client-discord/src/messages.ts (52 hunks)
  • packages/core/src/types.ts (4 hunks)
  • packages/plugin-node/__tests__/translation.test.ts (1 hunks)
  • packages/plugin-node/package.json (2 hunks)
  • packages/plugin-node/src/index.ts (2 hunks)
  • packages/plugin-node/src/services/index.ts (2 hunks)
  • packages/plugin-node/src/services/translation.ts (1 hunks)
🔇 Additional comments (12)
packages/plugin-node/src/services/index.ts (2)

9-9: Importing TranslationService

Adding TranslationService to the imports correctly integrates the new service.


20-20: Exporting TranslationService

Exporting TranslationService makes it accessible for use in other modules.

packages/plugin-node/src/index.ts (2)

15-15: Importing TranslationService

Including TranslationService in the imports enhances the plugin's functionality.


33-33: Registering TranslationService in services

Adding TranslationService to the services array registers it properly with the plugin.

packages/core/src/types.ts (2)

1354-1356: Clean interface design!

The ITranslationService interface is well-defined with clear parameters and proper error handling through nullable return type.


1492-1492: LGTM! Maintains enum consistency.

The TRANSLATION service type is properly placed and follows the established naming convention.

packages/plugin-node/__tests__/translation.test.ts (1)

6-36: Well-structured mock setup!

The mocks are properly isolated and provide consistent test data.

packages/client-discord/src/index.ts (1)

405-407: LGTM! Clean integration of translate command.

The command handling follows the established pattern in the switch statement.

packages/plugin-node/package.json (2)

82-83: LGTM! Adding vitest for testing.

The addition of vitest as a dev dependency is appropriate for the new translation service implementation.


88-90: LGTM! Added lint and test scripts.

The new scripts enhance the development workflow:

  • lint for code quality
  • test for running vitest tests
docs/docs/packages/clients.md (1)

91-91: LGTM! Added translation feature documentation.

The addition of "Message translations" to the Discord client features section appropriately documents the new capability.

docs/docs/packages/plugins.md (1)

671-674: LGTM! Added TEE_MARLIN_ATTESTATION_ENDPOINT configuration.

The documentation clearly specifies the default endpoint and its optional nature.

🧰 Tools
🪛 Markdownlint (0.37.0)

672-672: null
Fenced code blocks should have a language specified

(MD040, fenced-code-language)

this.openai = new OpenAI({ apiKey: openaiKey });
chosenProvider = TranslationProvider.OpenAI;
} else {
elizaLogger.error("TranslationService unable");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Incomplete error message in logger

The error message at line 57 is incomplete and does not provide sufficient information. It simply states "TranslationService unable" without specifying the issue.

Proposed fix:

-            elizaLogger.error("TranslationService unable");
+            elizaLogger.error("TranslationService unable to initialize: No translation provider available");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
elizaLogger.error("TranslationService unable");
elizaLogger.error("TranslationService unable to initialize: No translation provider available");

Comment on lines +87 to +107
const completion = await this.openai!.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{
role: "system",
content:
'You are a language translator and you will be provided with a text to translate and one or more target languages. Return the result of the translation in the target language(s) organized in a list with the target language and the translation separated by a colon. Example: "English: Hello, world!"',
},
{
role: "user",
content: `Translate the following text into ${targetLanguages}: ${text}`,
},
],
temperature: 1,
max_tokens: 256,
top_p: 1,
frequency_penalty: 0,
presence_penalty: 0,
});

return completion.choices[0].message.content;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add error handling for OpenAI API call

The translateWithAI method lacks error handling for the OpenAI API call. If the API request fails, it could result in an unhandled exception.

Proposed fix:

Wrap the API call in a try-catch block to handle exceptions gracefully:

+        try {
            const completion = await this.openai!.chat.completions.create({
                model: "gpt-4o-mini",
                messages: [
                    {
                        role: "system",
                        content:
                            'You are a language translator and you will be provided with a text to translate and one or more target languages. Return the result of the translation in the target language(s) organized in a list with the target language and the translation separated by a colon. Example: "English: Hello, world!"',
                    },
                    {
                        role: "user",
                        content: `Translate the following text into ${targetLanguages}: ${text}`,
                    },
                ],
                temperature: 1,
                max_tokens: 256,
                top_p: 1,
                frequency_penalty: 0,
                presence_penalty: 0,
            });
            return completion.choices[0].message.content;
+        } catch (error) {
+            elizaLogger.error('Error during translation with OpenAI:', error);
+            return null;
+        }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +1714 to +1747
async handleTranslateCommand(interaction: any) {
try {
// Defer the reply immediately to prevent interaction timeout
await interaction.deferReply();

const text = interaction.options.get("text")?.value as string;
if (!text) {
await interaction.editReply(
"Please provide a text to translate.",
);
return;
}

const targetLanguages = interaction.options.get("to")
?.value as string;
if (!targetLanguages) {
await interaction.editReply(
"Please provide at least one target language to translate.",
);
return;
}

const translatedText = await this.runtime
.getService<ITranslationService>(ServiceType.TRANSLATION)
.translate(text, targetLanguages);

await interaction.editReply(translatedText);
} catch (error) {
console.error("Error on text translation:", error);
await interaction
.editReply("Failed to translate text.")
.catch(console.error);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance error handling and input validation.

The implementation needs more robust error handling and input validation.

 async handleTranslateCommand(interaction: any) {
     try {
         await interaction.deferReply();
 
         const text = interaction.options.get("text")?.value as string;
-        if (!text) {
+        if (!text?.trim()) {
             await interaction.editReply(
-                "Please provide a text to translate."
+                "Please provide non-empty text to translate."
             );
             return;
         }
 
         const targetLanguages = interaction.options.get("to")?.value as string;
-        if (!targetLanguages) {
+        if (!targetLanguages?.trim()) {
             await interaction.editReply(
-                "Please provide at least one target language to translate."
+                "Please provide at least one valid target language (e.g., 'French' or 'Spanish, German')."
             );
             return;
         }
 
+        // Sanitize inputs
+        const sanitizedText = text.trim();
+        const sanitizedLanguages = targetLanguages
+            .split(',')
+            .map(lang => lang.trim())
+            .filter(Boolean)
+            .join(', ');
+
         const translatedText = await this.runtime
             .getService<ITranslationService>(ServiceType.TRANSLATION)
-            .translate(text, targetLanguages);
+            .translate(sanitizedText, sanitizedLanguages);
+
+        if (!translatedText) {
+            await interaction.editReply("Translation service is currently unavailable. Please try again later.");
+            return;
+        }
 
         await interaction.editReply(translatedText);
     } catch (error) {
         console.error("Error on text translation:", error);
         await interaction
-            .editReply("Failed to translate text.")
+            .editReply("An error occurred during translation. Please try again later.")
             .catch(console.error);
     }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async handleTranslateCommand(interaction: any) {
try {
// Defer the reply immediately to prevent interaction timeout
await interaction.deferReply();
const text = interaction.options.get("text")?.value as string;
if (!text) {
await interaction.editReply(
"Please provide a text to translate.",
);
return;
}
const targetLanguages = interaction.options.get("to")
?.value as string;
if (!targetLanguages) {
await interaction.editReply(
"Please provide at least one target language to translate.",
);
return;
}
const translatedText = await this.runtime
.getService<ITranslationService>(ServiceType.TRANSLATION)
.translate(text, targetLanguages);
await interaction.editReply(translatedText);
} catch (error) {
console.error("Error on text translation:", error);
await interaction
.editReply("Failed to translate text.")
.catch(console.error);
}
}
async handleTranslateCommand(interaction: any) {
try {
// Defer the reply immediately to prevent interaction timeout
await interaction.deferReply();
const text = interaction.options.get("text")?.value as string;
if (!text?.trim()) {
await interaction.editReply(
"Please provide non-empty text to translate."
);
return;
}
const targetLanguages = interaction.options.get("to")?.value as string;
if (!targetLanguages?.trim()) {
await interaction.editReply(
"Please provide at least one valid target language (e.g., 'French' or 'Spanish, German')."
);
return;
}
// Sanitize inputs
const sanitizedText = text.trim();
const sanitizedLanguages = targetLanguages
.split(',')
.map(lang => lang.trim())
.filter(Boolean)
.join(', ');
const translatedText = await this.runtime
.getService<ITranslationService>(ServiceType.TRANSLATION)
.translate(sanitizedText, sanitizedLanguages);
if (!translatedText) {
await interaction.editReply("Translation service is currently unavailable. Please try again later.");
return;
}
await interaction.editReply(translatedText);
} catch (error) {
console.error("Error on text translation:", error);
await interaction
.editReply("An error occurred during translation. Please try again later.")
.catch(console.error);
}
}

@wtfsayo wtfsayo self-requested a review January 23, 2025 05:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants