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

refactor COT and cleanups #21

Merged
merged 8 commits into from
Jan 21, 2025
Merged

refactor COT and cleanups #21

merged 8 commits into from
Jan 21, 2025

Conversation

ponderingdemocritus
Copy link
Contributor

@ponderingdemocritus ponderingdemocritus commented Jan 21, 2025

  • introduces memory into CoT for dynamic fetching
  • Blackboard for local state management
  • Cleanups

Summary by CodeRabbit

Release Notes

  • New Features

    • Added memory management system using ChromaVectorDB.
    • Enhanced goal tracking and context retrieval capabilities.
    • Introduced detailed provider guidelines for GraphQL interactions.
    • Integrated new types and interfaces for step and goal management.
    • Added support for new API key configuration.
  • Bug Fixes

    • Improved error handling and query logic for document retrieval.
  • Refactor

    • Centralized type definitions for steps, goals, and events.
    • Restructured type management across multiple modules.
    • Transitioned from Anthropic SDK to openrouter SDK for text generation.
    • Removed unnecessary logging from content processing.
  • Documentation

    • Expanded guidelines with structured examples for using GraphQL functions.

This update enhances the framework for managing agent goals and memories, improving type safety and error handling.

Copy link
Contributor

coderabbitai bot commented Jan 21, 2025

Walkthrough

This pull request introduces enhancements to the CodeRabbit AI framework, focusing on memory management, goal tracking, and type system improvements. A new constant PROVIDER_GUIDE is added to provide structured guidelines for GraphQL functions related to the Eternum game. The integration of ChromaVectorDB for memory storage is implemented across multiple example files, while the ChainOfThought class is modified to utilize this memory for goal management. Type definitions are centralized in a new types/index.ts file, improving clarity and consistency throughout the codebase.

Changes

File Change Summary
examples/eternum-context.ts Added PROVIDER_GUIDE constant with detailed GraphQL function guidelines.
examples/example-basic.ts Integrated ChromaVectorDB for memory storage, updated ChainOfThought constructor to include memory.
examples/example-goal.ts Updated type imports, added memory management with ChromaVectorDB.
packages/core/src/core/chain-of-thought.ts Enhanced goal management, added memory interactions, new validation methods.
packages/core/src/core/goal-manager.ts Removed local type definitions, added centralized type imports.
packages/core/src/core/step-manager.ts Removed local step type definitions.
packages/core/src/core/vector-db.ts Improved searchDocumentsByTag method with error handling and refined query logic.
packages/core/src/types/index.ts Added comprehensive type definitions for steps, goals, and events.
packages/core/package.json Added dependencies: @openrouter/ai-sdk-provider and ai.
packages/core/src/core/env.ts Added OPENROUTER_API_KEY property to envSchema.
packages/core/src/core/llm-client.ts Removed Anthropic client logic, integrated openrouter SDK for text generation.
packages/core/src/core/processor.ts Removed console log statement for contentId in process method.
examples/example-twitter.ts Updated LLMClient initialization to use model-specific configuration instead of provider-based.

Possibly related PRs

  • refactor: removes redundant model string #12: The changes in the LLMClient class, particularly the removal of the currentModel property and its usages, may relate to how the LLMClient interacts with the PROVIDER_GUIDE constant introduced in the main PR, as both involve the configuration and usage of models in the context of the application.

Poem

🐰 In circuits of memory, goals take flight,
Rabbit's code weaves context so bright.
Types dance, steps align with grace,
A framework that leaves no empty space.
AI dreams, now structured and clear! 🌟

✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

🪧 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 (7)
packages/core/src/types/index.ts (3)

120-120: Redundant property 'content' in 'ActionStep'

The content property is already defined in BaseStep. Since ActionStep extends BaseStep, there's no need to redeclare content.

Apply this diff to remove the redundant content property:

 export interface ActionStep extends BaseStep {
   type: "action";
-  content: string;
   toolCall?: {
     name: string;
     arguments: any;

165-165: Consider making 'priority' an optional field or enforce its range

The priority field is currently required and accepts any number. Since priorities are expected to be between 1 and 10, you might want to enforce this range using a type or validation logic.

Consider updating the type:

-  priority: number;
+  priority: number; // Expected to be between 1-10

And enforce the range where the Goal objects are created or validated.


172-172: Specify the valid range for 'progress'

The progress field indicates partial completion between 0-100. Consider enforcing this range in the type definition or through validation to prevent invalid values.

Optionally, you can define the type with more specific constraints using a type alias or validation logic.

packages/core/src/core/chain-of-thought.ts (1)

Line range hint 1079-1273: Refactor 'think' method for better readability and maintainability

The think method is lengthy and complex, spanning nearly 200 lines. Consider breaking it into smaller, focused methods to enhance readability, simplify debugging, and improve maintainability.

packages/core/src/core/step-manager.ts (1)

Line range hint 33-43: Prevent 'type' property from being overwritten in 'updateStep'

Currently, the type property is explicitly preserved from currentStep, but if updates includes a type, it might overwrite the existing value. To safeguard against unintended changes, exclude type from updates before merging.

Apply this diff:

   public updateStep(id: string, updates: Partial<Step>): void {
     const index = this.steps.findIndex((s) => s.id === id);
     if (index === -1) {
       throw new Error(`Step with ID ${id} not found`);
     }

     const currentStep = this.steps[index];
+    const { type: _ignoredType, ...restUpdates } = updates;
     const updatedStep = {
       ...currentStep,
-      ...updates,
+      ...restUpdates,
       type: currentStep.type, // Preserve the original step type
       timestamp: Date.now(), // Update timestamp on changes
     } as Step;
examples/example-basic.ts (1)

4-4: Tests needed for memory integration.

The TODO comment indicates missing tests. Given the critical nature of the memory system, comprehensive tests should be added to ensure reliable functionality.

Would you like me to help generate unit tests for the memory integration? This should include:

  • Memory initialization tests
  • Document storage and retrieval tests
  • Integration tests with ChainOfThought
examples/example-goal.ts (1)

76-87: Consider maintaining consistency with example-basic.ts.

While the memory initialization is functional, consider storing both ETERNUM_CONTEXT and PROVIDER_GUIDE as done in example-basic.ts. The PROVIDER_GUIDE could be valuable for goal-based operations.

Apply this change for consistency:

 // Initialize memory
 const memory = new ChromaVectorDB("agent_memory");

 // Load initial context
 await memory.storeDocument({
   title: "Game Rules",
   content: ETERNUM_CONTEXT,
   category: "rules",
   tags: ["game-mechanics", "rules"],
   lastUpdated: new Date(),
 });
+
+await memory.storeDocument({
+  title: "Provider Guide",
+  content: PROVIDER_GUIDE,
+  category: "actions",
+  tags: ["actions", "provider-guide"],
+  lastUpdated: new Date(),
+});
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9001902 and 2e586f2.

📒 Files selected for processing (8)
  • examples/eternum-context.ts (2 hunks)
  • examples/example-basic.ts (3 hunks)
  • examples/example-goal.ts (3 hunks)
  • packages/core/src/core/chain-of-thought.ts (17 hunks)
  • packages/core/src/core/goal-manager.ts (1 hunks)
  • packages/core/src/core/step-manager.ts (1 hunks)
  • packages/core/src/core/vector-db.ts (1 hunks)
  • packages/core/src/types/index.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/core/src/core/goal-manager.ts
🔇 Additional comments (6)
packages/core/src/core/step-manager.ts (1)

3-3: Ensure imported types are consistent and complete

Now that step types are imported from ../types, verify that all necessary types (BaseStep, ActionStep, etc.) are correctly exported and imported where needed to maintain type safety.

examples/example-basic.ts (2)

40-57: Well-structured memory initialization with proper metadata!

The initialization of the memory system is well-organized with appropriate metadata for both game rules and provider guide documents. The categorization and tagging will help with efficient retrieval.


59-61: Clean integration of memory into ChainOfThought!

The memory system is properly integrated into the ChainOfThought constructor while maintaining the existing worldState parameter.

examples/example-goal.ts (1)

Line range hint 192-236: Excellent implementation of memory-related event handlers!

The event handlers are well-structured with:

  • Clear separation between experience and knowledge storage
  • Comprehensive error handling
  • Good use of visual differentiation for different event types
  • Proper logging of relevant metadata
examples/eternum-context.ts (1)

Line range hint 434-670: Excellent documentation structure and content!

The PROVIDER_GUIDE is well-organized with:

  • Clear sections for different operations
  • Comprehensive examples with parameters
  • Important rules and guidelines
  • Structured format using XML-like tags
packages/core/src/core/vector-db.ts (1)

686-705: Robust error handling and improved query logic!

The changes improve the searchDocumentsByTag method with:

  • Comprehensive error handling with proper logging
  • More precise query logic using $and operator
  • Clear error messages with relevant context

try {
parsed = JSON.parse(response.toString());

console.log("parsed", parsed);
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

Replace 'console.log' with the logger

Direct use of console.log is inconsistent with the application's logging strategy. Replace it with this.logger.debug for better log management.

Apply this diff:

-            console.log("parsed", parsed);
+            this.logger.debug("getValidatedLLMResponse", "Parsed response", { parsed });
📝 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
console.log("parsed", parsed);
this.logger.debug("getValidatedLLMResponse", "Parsed response", { parsed });

Comment on lines +1554 to +1560
.map((doc) => ({
...JSON.parse(doc.content),
id: doc.id,
lastUpdated: doc.lastUpdated,
}))
.sort((a, b) => b.timestamp - a.timestamp);
} catch (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

Add error handling for JSON parsing in 'getBlackboardHistory'

Similar to getBlackboardState, ensure that JSON parsing errors are handled to prevent crashes when processing blackboard history.

Apply this diff to add error handling:

 return docs
   .map((doc) => {
-    return {
-      ...JSON.parse(doc.content),
-      id: doc.id,
-      lastUpdated: doc.lastUpdated,
-    };
+    try {
+      return {
+        ...JSON.parse(doc.content),
+        id: doc.id,
+        lastUpdated: doc.lastUpdated,
+      };
+    } catch (error) {
+      this.logger.error("getBlackboardHistory", "Failed to parse blackboard document content", { error });
+      return null;
+    }
   })
+  .filter((entry) => entry !== null)
   .sort((a, b) => b.timestamp - a.timestamp);
📝 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
.map((doc) => ({
...JSON.parse(doc.content),
id: doc.id,
lastUpdated: doc.lastUpdated,
}))
.sort((a, b) => b.timestamp - a.timestamp);
} catch (error) {
.map((doc) => {
try {
return {
...JSON.parse(doc.content),
id: doc.id,
lastUpdated: doc.lastUpdated,
};
} catch (error) {
this.logger.error("getBlackboardHistory", "Failed to parse blackboard document content", { error });
return null;
}
})
.filter((entry) => entry !== null)
.sort((a, b) => b.timestamp - a.timestamp);
} catch (error) {

Comment on lines +1515 to +1526
const aContent = JSON.parse(a.content);
const bContent = JSON.parse(b.content);
return aContent.timestamp - bContent.timestamp;
})
.forEach((doc) => {
const update = JSON.parse(doc.content);
if (!state[update.type]) {
state[update.type] = {};
}
state[update.type][update.key] = update.value;
});

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

Add error handling for JSON parsing in 'getBlackboardState'

When parsing doc.content as JSON, there is a possibility of a parsing error if the content is malformed. Add error handling to catch JSON parsing errors and prevent the application from crashing.

Apply this diff to add error handling:

 blackboardDocs
   .sort((a, b) => {
-    const aContent = JSON.parse(a.content);
-    const bContent = JSON.parse(b.content);
+    let aContent;
+    let bContent;
+    try {
+      aContent = JSON.parse(a.content);
+      bContent = JSON.parse(b.content);
+    } catch (error) {
+      this.logger.error("getBlackboardState", "Failed to parse blackboard document content", { error });
+      return 0; // Keep original order if parsing fails
+    }
     return aContent.timestamp - bContent.timestamp;
   })
   .forEach((doc) => {
-    const update = JSON.parse(doc.content);
+    let update;
+    try {
+      update = JSON.parse(doc.content);
+    } catch (error) {
+      this.logger.error("getBlackboardState", "Failed to parse blackboard document content", { error });
+      return;
+    }
     if (!state[update.type]) {
       state[update.type] = {};
     }

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

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: 1

🔭 Outside diff range comments (1)
packages/core/src/core/llm-client.ts (1)

Line range hint 126-130: Handle 'openrouter' provider in getApiKeyFromEnv method.

The getApiKeyFromEnv method currently only supports the 'anthropic' provider. Since the client is now using the 'openrouter' provider, this method will throw an error. Update the method to handle 'openrouter' by returning env.OPENROUTER_API_KEY.

Apply this diff to fix the issue:

 private getApiKeyFromEnv(provider: string): string {
+  if (provider === "openrouter") {
+    return env.OPENROUTER_API_KEY;
+  } else if (provider === "anthropic") {
     return env.ANTHROPIC_API_KEY;
   }
   throw new Error(`Unsupported provider: ${provider}`);
 }
♻️ Duplicate comments (1)
packages/core/src/core/chain-of-thought.ts (1)

1453-1522: ⚠️ Potential issue

Add error handling for JSON parsing in blackboard operations.

The blackboard state management needs error handling for JSON parsing operations to prevent crashes.

Apply this diff:

 blackboardDocs
   .sort((a, b) => {
-    const aContent = JSON.parse(a.content);
-    const bContent = JSON.parse(b.content);
+    let aContent, bContent;
+    try {
+      aContent = JSON.parse(a.content);
+      bContent = JSON.parse(b.content);
+    } catch (error) {
+      this.logger.error("getBlackboardState", "Failed to parse blackboard content", { error });
+      return 0;
+    }
     return aContent.timestamp - bContent.timestamp;
   })
   .forEach((doc) => {
-    const update = JSON.parse(doc.content);
+    let update;
+    try {
+      update = JSON.parse(doc.content);
+    } catch (error) {
+      this.logger.error("getBlackboardState", "Failed to parse update content", { error });
+      return;
+    }
     if (!state[update.type]) {
       state[update.type] = {};
     }
     state[update.type][update.key] = update.value;
   });
🧹 Nitpick comments (7)
packages/core/src/core/llm-client.ts (2)

173-174: Make the model in analyze method configurable.

The analyze method uses a hardcoded model "deepseek/deepseek-r1". Consider making the model configurable by using this.config.model or accepting it as a parameter. This will enhance flexibility and maintain consistency across the client.

Apply this diff to enhance configurability:

 const response = await generateText({
-  model: openrouter("deepseek/deepseek-r1"),
+  model: openrouter(this.config.model),
   temperature,
   messages: [
     // ...
   ],
   maxTokens,
 });

189-191: Ensure event listeners are registered for emitted events.

The analyze method emits a "trace:tokens" event with token usage data. To prevent potential issues with unhandled events, verify that appropriate event listeners are registered to handle this event. Additionally, consider documenting the event name and data structure for clarity.

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

118-148: Consider standardizing error handling across step types.

While the ActionStep interface includes comprehensive error handling with error and duration fields, other step types might benefit from similar error tracking capabilities for consistency.

Consider extending other step types with error handling:

 export interface PlanningStep extends BaseStep {
   type: "planning";
   plan: string;
   facts: string;
+  error?: Error;
+  duration?: number;
 }

 export interface SystemStep extends BaseStep {
   type: "system";
   systemPrompt: string;
+  error?: Error;
+  duration?: number;
 }

 export interface TaskStep extends BaseStep {
   type: "task";
   task: string;
+  error?: Error;
+  duration?: number;
 }

231-265: Add JSDoc comments for event handlers.

The event system is well-designed, but would benefit from documentation explaining when each event is emitted and what the event parameters represent.

Add JSDoc comments for better developer experience:

 export interface ChainOfThoughtEvents {
+  /** Emitted when a new step is added to the chain */
   step: (step: Step) => void;
+  /** Emitted when an action starts execution */
   "action:start": (action: CoTAction) => void;
   // Add similar comments for other events
 }
examples/eternum-context.ts (1)

Line range hint 439-671: Standardize contract address placeholders.

The documentation uses <eternum-trade_systems> syntax for contract addresses. Consider using a more standard format that clearly indicates these are placeholders.

Apply this format consistently:

-            "contractAddress": "<eternum-trade_systems>",
+            "contractAddress": "{{ETERNUM_TRADE_SYSTEMS_ADDRESS}}",
packages/core/src/core/chain-of-thought.ts (2)

282-363: Enhance error handling in goal validation.

While the goal validation system is well-designed, it could benefit from more specific error handling.

Add specific error types and handling:

+interface GoalValidationError extends Error {
+  type: 'REQUIREMENTS' | 'RESOURCES' | 'DEPENDENCIES';
+  details: Record<string, any>;
+}

 private async canExecuteGoal(goal: Goal): Promise<{
   possible: boolean;
   reason: string;
   missing_requirements: string[];
 }> {
-  try {
+  try {
     const [relevantDocs, relevantExperiences, blackboardState] =
       await Promise.all([
         this.memory.findSimilarDocuments(goal.description, 5),
         this.memory.findSimilarEpisodes(goal.description, 3),
         this.getBlackboardState(),
       ]);
+  } catch (error) {
+    const validationError = new Error('Goal validation failed') as GoalValidationError;
+    validationError.type = 'DEPENDENCIES';
+    validationError.details = { error };
+    throw validationError;
+  }

1556-1643: Consider adding response caching.

The LLM response validation system could benefit from caching successful responses to avoid unnecessary retries and reduce API calls.

Consider implementing a caching mechanism:

  1. Cache successful responses using a combination of prompt and schema as key
  2. Implement cache invalidation strategy
  3. Add cache hit/miss metrics

Example implementation:

interface CacheEntry<T> {
  response: T;
  timestamp: number;
  schema: string;
}

private responseCache = new Map<string, CacheEntry<any>>();

private getCacheKey(prompt: string, schema: z.ZodSchema<any>): string {
  return crypto.createHash('md5')
    .update(`${prompt}${JSON.stringify(zodToJsonSchema(schema))}`)
    .digest('hex');
}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 2e586f2 and 85a4a5b.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (7)
  • examples/eternum-context.ts (3 hunks)
  • examples/example-goal.ts (4 hunks)
  • packages/core/package.json (1 hunks)
  • packages/core/src/core/chain-of-thought.ts (19 hunks)
  • packages/core/src/core/env.ts (1 hunks)
  • packages/core/src/core/llm-client.ts (4 hunks)
  • packages/core/src/types/index.ts (1 hunks)
🔇 Additional comments (7)
packages/core/src/core/env.ts (1)

13-13: Addition of OPENROUTER_API_KEY is appropriate.

Including OPENROUTER_API_KEY in the environment schema is necessary for configuring the OpenRouter SDK. This addition aligns with the changes made to the LLMClient.

packages/core/package.json (1)

17-17: New dependencies align with the updated SDK usage.

The addition of "@openrouter/ai-sdk-provider": "^0.0.6" and "ai": "^4.1.0" as dependencies is appropriate. These packages are required for the integration of the OpenRouter SDK and the generateText function used in LLMClient.

Also applies to: 19-19

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

107-116: Well-structured type definitions!

The StepType and BaseStep definitions provide a solid foundation for the step system. The use of discriminated unions with the type field and optional properties allows for both type safety and flexibility.


150-189: Excellent goal tracking system design!

The goal-related types provide a robust foundation for goal management:

  • Clear horizon types for temporal organization
  • Comprehensive status tracking
  • Well-thought-out success criteria and scoring system
  • Proper type safety with optional fields where appropriate
examples/example-goal.ts (2)

130-132: Use the logger for token tracking.

Replace direct console logging with the logger for consistent log management.

Apply this diff:

-    console.log("\n💡 Tokens used:", { input, output });
+    dreams.logger.debug("token_usage", "Tokens used", { input, output });

71-84: Verify the specified model version.

The LLM client is initialized with "deepseek/deepseek-r1". Ensure this model version exists and is appropriate for the task.

Run this script to check the model:

✅ Verification successful

Model configuration is correctly implemented

The model "deepseek/deepseek-r1" is properly integrated through OpenRouter's SDK and follows the correct implementation pattern. The necessary dependencies and configurations are in place.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if the specified model exists and get its capabilities
curl -s "https://api.deepseek.com/v1/models" | jq '.data[] | select(.id | contains("deepseek-r1"))'

Length of output: 157


Script:

#!/bin/bash
# Search for model configurations and documentation
echo "=== Searching for model configurations ==="
rg -i "deepseek" --type ts --type js --type json -C 2

echo -e "\n=== Checking package.json for relevant dependencies ==="
cat package.json | jq '.dependencies'

echo -e "\n=== Searching for documentation files ==="
fd -e md -e txt | xargs rg -i "deepseek" -C 2

Length of output: 1101


Script:

#!/bin/bash
# Check OpenRouter integration and configuration
echo "=== Checking OpenRouter configuration ==="
rg -i "openrouter" --type ts --type js --type json -C 3

echo -e "\n=== Looking for model configuration files ==="
fd -e config -e json -e yaml -e yml | xargs rg -i "openrouter|model"

Length of output: 2650

examples/eternum-context.ts (1)

435-439: Well-structured provider documentation!

The provider guide is clearly organized with important rules and guidelines prominently displayed at the beginning.

Comment on lines +123 to +124
private getDefaultModel(): string {
return "anthropic/claude-3.5-sonnet:beta";
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

Update the default model to match the new provider.

The getDefaultModel method returns "anthropic/claude-3.5-sonnet:beta", which is associated with the Anthropic provider. Since the code now utilizes the OpenRouter SDK, consider updating the default model to one compatible with OpenRouter or ensure that the provider is correctly set to Anthropic if that's intentional.

Apply this diff to fix the inconsistency:

 private getDefaultModel(): string {
-  return "anthropic/claude-3.5-sonnet:beta";
+  return "openrouter/default-model"; // Replace with an appropriate default model
 }

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

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: 4

🔭 Outside diff range comments (2)
packages/core/src/core/chain-of-thought.ts (2)

Line range hint 113-231: Add error handling for memory operations.

The strategy planning code doesn't handle potential failures in memory operations gracefully.

Apply this diff to add proper error handling:

   public async planStrategy(objective: string): Promise<void> {
     this.logger.debug("planStrategy", "Planning strategy for objective", {
       objective,
     });
 
-    const [relevantDocs, relevantExperiences] = await Promise.all([
-      this.memory.findSimilarDocuments(objective, 5),
-      this.memory.findSimilarEpisodes(objective, 3),
-    ]);
+    let relevantDocs: Documentation[] = [];
+    let relevantExperiences: EpisodicMemory[] = [];
+    
+    try {
+      [relevantDocs, relevantExperiences] = await Promise.all([
+        this.memory.findSimilarDocuments(objective, 5),
+        this.memory.findSimilarEpisodes(objective, 3),
+      ]);
+    } catch (error) {
+      this.logger.error("planStrategy", "Failed to retrieve context from memory", { error });
+      // Continue with empty context rather than failing completely
+    }

Line range hint 374-439: Add validation for goal refinement results.

The goal refinement process should validate that sub-goals maintain parent goal constraints.

Add validation to ensure sub-goals align with parent goals:

   private async refineGoal(goal: Goal, maxRetries: number = 3): Promise<void> {
+    const validateSubGoals = (subGoals: RefinedGoal[], parentGoal: Goal): boolean => {
+      // Ensure sub-goals don't exceed parent's scope
+      const totalComplexity = subGoals.reduce((sum, sg) => sum + sg.priority, 0);
+      if (totalComplexity > parentGoal.priority * 1.5) {
+        return false;
+      }
+      return true;
+    };

     try {
       const subGoals = await this.getValidatedLLMResponse<RefinedGoal[]>({
         prompt,
         systemPrompt:
           "You are a goal refinement system that breaks down complex goals into actionable steps.",
         schema,
         maxRetries,
         onRetry: (error, attempt) => {
           this.logger.error("refineGoal", `Attempt ${attempt} failed`, {
             error,
           });
         },
       });

+      if (!validateSubGoals(subGoals, goal)) {
+        throw new Error("Sub-goals exceed parent goal's scope");
+      }

       // Add sub-goals to goal manager with parent reference
       for (const subGoal of subGoals) {
         this.goalManager.addGoal({
♻️ Duplicate comments (1)
packages/core/src/core/llm-client.ts (1)

125-126: ⚠️ Potential issue

Update the default model to match the new provider.

The getDefaultModel method returns an Anthropic model, but the code now uses the OpenRouter SDK. This inconsistency could cause issues.

🧹 Nitpick comments (4)
packages/core/src/core/llm-client.ts (1)

100-123: Add type safety to response handling.

While the response mapping is correct, consider adding TypeScript interfaces for the OpenRouter response structure to improve type safety and maintainability.

interface OpenRouterResponse {
  text: string;
  usage: {
    promptTokens: number;
    completionTokens: number;
  };
  finishReason: string;
}
examples/example-goal.ts (1)

57-57: Consider making the model configurable.

The model is hardcoded to "deepseek/deepseek-r1". Consider:

  1. Moving this to a configuration file
  2. Making it environment-variable configurable
  3. Adding validation for supported models
// config.ts
export const DEFAULT_MODEL = process.env.LLM_MODEL || "deepseek/deepseek-r1";

// Validation
const SUPPORTED_MODELS = ["deepseek/deepseek-r1", "anthropic/claude-3.5-sonnet:beta"];
if (!SUPPORTED_MODELS.includes(DEFAULT_MODEL)) {
  throw new Error(`Unsupported model: ${DEFAULT_MODEL}`);
}
examples/eternum-context.ts (1)

14-25: Improve game overview structure and clarity.

The game overview section contains duplicate headers and could be better organized for clarity.

Apply this diff to improve the structure:

-# Game Overview
-1. Guide players through game mechanics
-2. Help optimize resource management and building decisions  
-3. Provide strategic recommendations based on game state
-
-# Game Overview
-
+# Game Overview and Objectives
+## Assistant Goals
+1. Guide players through game mechanics
+2. Help optimize resource management and building decisions  
+3. Provide strategic recommendations based on game state
+
+## Game Mechanics
- Players expand their Realm to build a hyperstructure
- Hyperstructures require 3M fragments and significant resources
- Once built, hyperstructures generate points when defended
- First player to accumulate 9M points wins the game
packages/core/src/core/chain-of-thought.ts (1)

53-53: Consider initializing memory in a separate method.

The memory initialization is spread across multiple locations. Consider consolidating it into a dedicated initialization method.

 export class ChainOfThought extends EventEmitter {
-  public memory: VectorDB;
+  private memory: VectorDB;
+  
+  public getMemory(): VectorDB {
+    return this.memory;
+  }
+  
+  private initializeMemory(memory: VectorDB): void {
+    this.memory = memory;
+    this.logger.debug("initializeMemory", "Memory system initialized");
+  }

   constructor(
     private llmClient: LLMClient,
     memory: VectorDB,
     initialContext?: ChainOfThoughtContext,
     config: {
       logLevel?: LogLevel;
     } = {}
   ) {
     super();
     // ... other initialization code ...
-    this.memory = memory;
+    this.initializeMemory(memory);
   }

Also applies to: 65-65, 105-105

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 85a4a5b and f042903.

📒 Files selected for processing (5)
  • examples/eternum-context.ts (3 hunks)
  • examples/example-goal.ts (4 hunks)
  • packages/core/src/core/chain-of-thought.ts (18 hunks)
  • packages/core/src/core/llm-client.ts (4 hunks)
  • packages/core/src/core/processor.ts (0 hunks)
💤 Files with no reviewable changes (1)
  • packages/core/src/core/processor.ts
🔇 Additional comments (6)
packages/core/src/core/llm-client.ts (2)

191-194: LGTM! Well-structured token usage tracking.

The implementation properly emits token usage metrics, enabling monitoring and optimization of API usage.


18-19: Consider documenting the rationale for MaxListeners value.

The MaxListeners limit of 50 seems high. While this might be intentional, it could indicate potential memory leaks if many listeners are not properly cleaned up. Consider:

  1. Documenting why 50 listeners are needed
  2. Implementing a cleanup mechanism for listeners
  3. Using a lower value if 50 is not justified

Run this script to check the actual listener usage:

✅ Verification successful

The MaxListeners limit of 50 is justified.

The high limit is appropriate given the extensive event system that covers core operations (thinking, actions), goal management, memory operations, and system monitoring. The value allows multiple components to subscribe to these events for logging, monitoring, and extensibility purposes.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for event listener registrations to verify the need for 50 listeners
rg -A 2 "\.on\(" 

Length of output: 5416

examples/example-goal.ts (1)

60-79: LGTM! Well-structured memory initialization.

The implementation properly initializes the ChromaVectorDB and stores both game rules and provider guide with appropriate metadata and tags.

examples/eternum-context.ts (1)

Line range hint 434-671: LGTM! Well-structured API documentation.

The PROVIDER_GUIDE section is well-organized with:

  • Clear function documentation
  • Detailed parameter descriptions
  • Helpful examples
  • Important rules and validations
packages/core/src/core/chain-of-thought.ts (2)

30-44: LGTM! Well-defined interfaces.

The new interfaces RefinedGoal and LLMValidationOptions are well-structured and provide good type safety.


289-370: 🛠️ Refactor suggestion

Add retry mechanism for goal execution checks.

The goal execution validation lacks a retry mechanism for transient failures.

Apply this diff to add retries:

   private async canExecuteGoal(goal: Goal): Promise<{
     possible: boolean;
     reason: string;
     missing_requirements: string[];
   }> {
+    const maxRetries = 3;
+    let attempts = 0;
+    
+    while (attempts < maxRetries) {
       try {
         const [relevantDocs, relevantExperiences, blackboardState] =
           await Promise.all([
             this.memory.findSimilarDocuments(goal.description, 5),
             this.memory.findSimilarEpisodes(goal.description, 3),
             this.getBlackboardState(),
           ]);
         // ... rest of the function ...
         return response;
       } catch (error) {
+        attempts++;
+        if (attempts >= maxRetries) {
           this.logger.error(
             "canExecuteGoal",
             "Failed to check goal executability",
             { error }
           );
           return {
             possible: false,
-            reason: "Error checking goal executability",
+            reason: `Failed to check goal executability after ${maxRetries} attempts`,
             missing_requirements: [],
           };
+        }
+        // Wait before retrying
+        await new Promise(resolve => setTimeout(resolve, attempts * 1000));
       }
+    }
   }

Likely invalid or redundant comment.

apiKey: this.config.apiKey,
});
}
private initializeClient(): void {}
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

Remove unused method.

The initializeClient method is empty and appears to be unused. Consider removing it to improve code maintainability.

-  private initializeClient(): void {}

Comment on lines +125 to +127
// llmClient.on("trace:tokens", ({ input, output }) => {
// console.log("\n💡 Tokens used:", { input, output });
// });
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

Remove or implement commented code.

The token usage event logging is commented out without explanation. Either:

  1. Remove the commented code if it's no longer needed
  2. Implement the feature if it's still required
  3. Add a comment explaining why it's disabled if it's temporarily disabled

Comment on lines +1563 to +1650
private async getValidatedLLMResponse<T>({
prompt,
systemPrompt,
schema,
maxRetries = 3,
onRetry,
}: LLMValidationOptions<T>): Promise<T> {
const jsonSchema = zodToJsonSchema(schema, "mySchema");
const validate = ajv.compile(jsonSchema as JSONSchemaType<T>);
let attempts = 0;

const formattedPrompt = `
${prompt}

<response_structure>
Return a JSON object matching this schema. Do not include any markdown formatting.

${JSON.stringify(jsonSchema, null, 2)}
</response_structure>
`;

while (attempts < maxRetries) {
try {
const response = await this.llmClient.analyze(formattedPrompt, {
system: systemPrompt,
});

let responseText = response.toString();

// Remove markdown code block formatting if present
responseText = responseText.replace(/```json\n?|\n?```/g, "");

let parsed: T;
try {
parsed = JSON.parse(responseText);
} catch (parseError) {
this.logger.error(
"getValidatedLLMResponse",
"Failed to parse LLM response as JSON",
{
response: responseText,
error: parseError,
}
);
attempts++;
onRetry?.(parseError as Error, attempts);
continue;
}

if (!validate(parsed)) {
this.logger.error(
"getValidatedLLMResponse",
"Response failed schema validation",
{
errors: validate.errors,
response: parsed,
}
);
attempts++;
onRetry?.(
new Error(
`Schema validation failed: ${JSON.stringify(validate.errors)}`
),
attempts
);
continue;
}

return parsed;
} catch (error) {
this.logger.error(
"getValidatedLLMResponse",
`Attempt ${attempts + 1} failed`,
{ error }
);
attempts++;
onRetry?.(error as Error, attempts);

if (attempts >= maxRetries) {
throw new Error(
`Failed to get valid LLM response after ${maxRetries} attempts: ${error}`
);
}
}
}

throw new Error("Maximum retries exceeded");
}
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

Add timeout handling for LLM operations.

The LLM response validation should include a timeout mechanism to prevent hanging.

Add timeout handling:

   private async getValidatedLLMResponse<T>({
     prompt,
     systemPrompt,
     schema,
     maxRetries = 3,
     onRetry,
+    timeout = 30000, // 30 second default timeout
   }: LLMValidationOptions<T>): Promise<T> {
     const jsonSchema = zodToJsonSchema(schema, "mySchema");
     const validate = ajv.compile(jsonSchema as JSONSchemaType<T>);
     let attempts = 0;

     while (attempts < maxRetries) {
       try {
-        const response = await this.llmClient.analyze(formattedPrompt, {
+        const response = await Promise.race([
+          this.llmClient.analyze(formattedPrompt, {
             system: systemPrompt,
-        });
+          }),
+          new Promise((_, reject) => 
+            setTimeout(() => reject(new Error("LLM response timeout")), timeout)
+          )
+        ]);
📝 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
private async getValidatedLLMResponse<T>({
prompt,
systemPrompt,
schema,
maxRetries = 3,
onRetry,
}: LLMValidationOptions<T>): Promise<T> {
const jsonSchema = zodToJsonSchema(schema, "mySchema");
const validate = ajv.compile(jsonSchema as JSONSchemaType<T>);
let attempts = 0;
const formattedPrompt = `
${prompt}
<response_structure>
Return a JSON object matching this schema. Do not include any markdown formatting.
${JSON.stringify(jsonSchema, null, 2)}
</response_structure>
`;
while (attempts < maxRetries) {
try {
const response = await this.llmClient.analyze(formattedPrompt, {
system: systemPrompt,
});
let responseText = response.toString();
// Remove markdown code block formatting if present
responseText = responseText.replace(/```json\n?|\n?```/g, "");
let parsed: T;
try {
parsed = JSON.parse(responseText);
} catch (parseError) {
this.logger.error(
"getValidatedLLMResponse",
"Failed to parse LLM response as JSON",
{
response: responseText,
error: parseError,
}
);
attempts++;
onRetry?.(parseError as Error, attempts);
continue;
}
if (!validate(parsed)) {
this.logger.error(
"getValidatedLLMResponse",
"Response failed schema validation",
{
errors: validate.errors,
response: parsed,
}
);
attempts++;
onRetry?.(
new Error(
`Schema validation failed: ${JSON.stringify(validate.errors)}`
),
attempts
);
continue;
}
return parsed;
} catch (error) {
this.logger.error(
"getValidatedLLMResponse",
`Attempt ${attempts + 1} failed`,
{ error }
);
attempts++;
onRetry?.(error as Error, attempts);
if (attempts >= maxRetries) {
throw new Error(
`Failed to get valid LLM response after ${maxRetries} attempts: ${error}`
);
}
}
}
throw new Error("Maximum retries exceeded");
}
private async getValidatedLLMResponse<T>({
prompt,
systemPrompt,
schema,
maxRetries = 3,
onRetry,
timeout = 30000, // 30 second default timeout
}: LLMValidationOptions<T>): Promise<T> {
const jsonSchema = zodToJsonSchema(schema, "mySchema");
const validate = ajv.compile(jsonSchema as JSONSchemaType<T>);
let attempts = 0;
const formattedPrompt = `
${prompt}
<response_structure>
Return a JSON object matching this schema. Do not include any markdown formatting.
${JSON.stringify(jsonSchema, null, 2)}
</response_structure>
`;
while (attempts < maxRetries) {
try {
const response = await Promise.race([
this.llmClient.analyze(formattedPrompt, {
system: systemPrompt,
}),
new Promise((_, reject) =>
setTimeout(() => reject(new Error("LLM response timeout")), timeout)
)
]);
let responseText = response.toString();
// Remove markdown code block formatting if present

Comment on lines +1460 to +1561
value: update.value,
timestamp: update.timestamp || Date.now(),
}),
category: "blackboard",
tags: ["blackboard", update.type, update.key],
lastUpdated: new Date(),
});

this.logger.debug("updateBlackboard", "Stored blackboard update", {
update,
});
} catch (error) {
this.logger.error(
"updateBlackboard",
"Failed to store blackboard update",
{
error,
}
);
}
}

// Add type safety to event emitter
export declare interface ChainOfThought {
on<K extends keyof ChainOfThoughtEvents>(
event: K,
listener: ChainOfThoughtEvents[K]
): this;
emit<K extends keyof ChainOfThoughtEvents>(
event: K,
...args: Parameters<ChainOfThoughtEvents[K]>
): boolean;
private async getBlackboardState(): Promise<Record<string, any>> {
try {
// Use findDocumentsByCategory to get all blackboard documents
const blackboardDocs = await this.memory.searchDocumentsByTag([
"blackboard",
]);

// Build current state by applying updates in order
const state: Record<string, any> = {};

blackboardDocs
.sort((a, b) => {
const aContent = JSON.parse(a.content);
const bContent = JSON.parse(b.content);
return aContent.timestamp - bContent.timestamp;
})
.forEach((doc) => {
const update = JSON.parse(doc.content);
if (!state[update.type]) {
state[update.type] = {};
}
state[update.type][update.key] = update.value;
});

return state;
} catch (error) {
this.logger.error(
"getBlackboardState",
"Failed to get blackboard state",
{
error,
}
);
return {};
}
}

private async getBlackboardHistory(
type?: string,
key?: string,
limit: number = 10
): Promise<any[]> {
try {
// Use searchDocumentsByTag to find relevant documents
const tags = ["blackboard"];
if (type) tags.push(type);
if (key) tags.push(key);

const docs = await this.memory.searchDocumentsByTag(tags, limit);

return docs
.map((doc) => ({
...JSON.parse(doc.content),
id: doc.id,
lastUpdated: doc.lastUpdated,
}))
.sort((a, b) => b.timestamp - a.timestamp);
} catch (error) {
this.logger.error(
"getBlackboardHistory",
"Failed to get blackboard history",
{
error,
}
);
return [];
}
}
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 data validation for blackboard operations.

The blackboard operations need input validation to prevent invalid state updates.

Add validation for blackboard updates:

   private async updateBlackboard(update: {
     type: "resource" | "state" | "event" | "achievement";
     key: string;
     value: any;
     timestamp: number;
     metadata?: Record<string, any>;
   }): Promise<void> {
+    // Validate update
+    if (!update.key || update.key.length > 100) {
+      throw new Error("Invalid key length");
+    }
+    
+    if (update.value === undefined || update.value === null) {
+      throw new Error("Value cannot be null or undefined");
+    }
+    
+    if (update.timestamp > Date.now()) {
+      throw new Error("Timestamp cannot be in the future");
+    }

     try {
       await this.memory.storeDocument({
📝 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
private async updateBlackboard(update: {
type: "resource" | "state" | "event" | "achievement";
key: string;
value: any;
timestamp: number;
metadata?: Record<string, any>;
}): Promise<void> {
try {
await this.memory.storeDocument({
title: `Blackboard Update: ${update.type} - ${update.key}`,
content: JSON.stringify({
...update,
value: update.value,
timestamp: update.timestamp || Date.now(),
}),
category: "blackboard",
tags: ["blackboard", update.type, update.key],
lastUpdated: new Date(),
});
this.logger.debug("updateBlackboard", "Stored blackboard update", {
update,
});
} catch (error) {
this.logger.error(
"updateBlackboard",
"Failed to store blackboard update",
{
error,
}
);
}
}
// Add type safety to event emitter
export declare interface ChainOfThought {
on<K extends keyof ChainOfThoughtEvents>(
event: K,
listener: ChainOfThoughtEvents[K]
): this;
emit<K extends keyof ChainOfThoughtEvents>(
event: K,
...args: Parameters<ChainOfThoughtEvents[K]>
): boolean;
private async getBlackboardState(): Promise<Record<string, any>> {
try {
// Use findDocumentsByCategory to get all blackboard documents
const blackboardDocs = await this.memory.searchDocumentsByTag([
"blackboard",
]);
// Build current state by applying updates in order
const state: Record<string, any> = {};
blackboardDocs
.sort((a, b) => {
const aContent = JSON.parse(a.content);
const bContent = JSON.parse(b.content);
return aContent.timestamp - bContent.timestamp;
})
.forEach((doc) => {
const update = JSON.parse(doc.content);
if (!state[update.type]) {
state[update.type] = {};
}
state[update.type][update.key] = update.value;
});
return state;
} catch (error) {
this.logger.error(
"getBlackboardState",
"Failed to get blackboard state",
{
error,
}
);
return {};
}
}
private async getBlackboardHistory(
type?: string,
key?: string,
limit: number = 10
): Promise<any[]> {
try {
// Use searchDocumentsByTag to find relevant documents
const tags = ["blackboard"];
if (type) tags.push(type);
if (key) tags.push(key);
const docs = await this.memory.searchDocumentsByTag(tags, limit);
return docs
.map((doc) => ({
...JSON.parse(doc.content),
id: doc.id,
lastUpdated: doc.lastUpdated,
}))
.sort((a, b) => b.timestamp - a.timestamp);
} catch (error) {
this.logger.error(
"getBlackboardHistory",
"Failed to get blackboard history",
{
error,
}
);
return [];
}
}
private async updateBlackboard(update: {
type: "resource" | "state" | "event" | "achievement";
key: string;
value: any;
timestamp: number;
metadata?: Record<string, any>;
}): Promise<void> {
// Validate update
if (!update.key || update.key.length > 100) {
throw new Error("Invalid key length");
}
if (update.value === undefined || update.value === null) {
throw new Error("Value cannot be null or undefined");
}
if (update.timestamp > Date.now()) {
throw new Error("Timestamp cannot be in the future");
}
try {
await this.memory.storeDocument({
title: `Blackboard Update: ${update.type} - ${update.key}`,
content: JSON.stringify({
...update,
value: update.value,
timestamp: update.timestamp || Date.now(),
}),
category: "blackboard",
tags: ["blackboard", update.type, update.key],
lastUpdated: new Date(),
});
this.logger.debug("updateBlackboard", "Stored blackboard update", {
update,
});
} catch (error) {
this.logger.error(
"updateBlackboard",
"Failed to store blackboard update",
{
error,
}
);
}
}
private async getBlackboardState(): Promise<Record<string, any>> {
try {
// Use findDocumentsByCategory to get all blackboard documents
const blackboardDocs = await this.memory.searchDocumentsByTag([
"blackboard",
]);
// Build current state by applying updates in order
const state: Record<string, any> = {};
blackboardDocs
.sort((a, b) => {
const aContent = JSON.parse(a.content);
const bContent = JSON.parse(b.content);
return aContent.timestamp - bContent.timestamp;
})
.forEach((doc) => {
const update = JSON.parse(doc.content);
if (!state[update.type]) {
state[update.type] = {};
}
state[update.type][update.key] = update.value;
});
return state;
} catch (error) {
this.logger.error(
"getBlackboardState",
"Failed to get blackboard state",
{
error,
}
);
return {};
}
}
private async getBlackboardHistory(
type?: string,
key?: string,
limit: number = 10
): Promise<any[]> {
try {
// Use searchDocumentsByTag to find relevant documents
const tags = ["blackboard"];
if (type) tags.push(type);
if (key) tags.push(key);
const docs = await this.memory.searchDocumentsByTag(tags, limit);
return docs
.map((doc) => ({
...JSON.parse(doc.content),
id: doc.id,
lastUpdated: doc.lastUpdated,
}))
.sort((a, b) => b.timestamp - a.timestamp);
} catch (error) {
this.logger.error(
"getBlackboardHistory",
"Failed to get blackboard history",
{
error,
}
);
return [];
}
}

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: 0

🧹 Nitpick comments (3)
packages/core/src/core/chain-of-thought.ts (3)

30-36: Consider making the horizon type more flexible.

The horizon property in RefinedGoal interface is hardcoded to "short". This might be too restrictive if you need to refine goals with different horizons in the future.

Consider using the HorizonType that's already imported:

-  horizon: "short";
+  horizon: HorizonType;

53-53: Consider making memory property private with public accessor methods.

Exposing the memory property as public might break encapsulation. Consider making it private and providing specific public methods for necessary memory operations.

Consider this approach:

-  public memory: VectorDB;
+  private memory: VectorDB;
+
+  public async searchMemory(query: string, limit: number): Promise<Documentation[]> {
+    return this.memory.findSimilarDocuments(query, limit);
+  }

1314-1322: Add validation for experience data before storage.

The experience object should be validated before being stored to ensure data integrity.

   const experience = {
     timestamp: new Date(),
     action: actionWithResult,
     outcome: formattedOutcome,
     context: this.context,
     importance: calculatedImportance,
     emotions,
   };

+  // Validate experience object
+  if (!experience.action || !experience.outcome) {
+    throw new Error("Invalid experience: action and outcome are required");
+  }
+  if (!Array.isArray(experience.emotions)) {
+    throw new Error("Invalid experience: emotions must be an array");
+  }
+  if (typeof experience.importance !== "number" || experience.importance < 0 || experience.importance > 1) {
+    throw new Error("Invalid experience: importance must be a number between 0 and 1");
+  }

   await this.memory.storeEpisode(experience);
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between f042903 and 7fbe41d.

📒 Files selected for processing (1)
  • packages/core/src/core/chain-of-thought.ts (18 hunks)
🔇 Additional comments (3)
packages/core/src/core/chain-of-thought.ts (3)

1460-1492: 🛠️ Refactor suggestion

Add input validation for blackboard updates.

The updateBlackboard method should validate its input parameters before processing.

   private async updateBlackboard(update: {
     type: "resource" | "state" | "event" | "achievement";
     key: string;
     value: any;
     timestamp: number;
     metadata?: Record<string, any>;
   }): Promise<void> {
+    // Validate update parameters
+    if (!update.key || update.key.length > 100) {
+      throw new Error("Invalid key length");
+    }
+    if (update.value === undefined || update.value === null) {
+      throw new Error("Value cannot be null or undefined");
+    }
+    if (update.timestamp > Date.now()) {
+      throw new Error("Timestamp cannot be in the future");
+    }
+
     try {

Likely invalid or redundant comment.


1595-1595: 🛠️ Refactor suggestion

Replace console.log with logger.

Direct use of console.log should be replaced with the class's logger instance for consistent logging.

-        console.log("responseText", responseText);
+        this.logger.debug("getValidatedLLMResponse", "Parsed response text", { responseText });

Likely invalid or redundant comment.


1586-1588: 🛠️ Refactor suggestion

Add timeout handling for LLM operations.

The LLM client call should include a timeout to prevent hanging operations.

-        const response = await this.llmClient.analyze(formattedPrompt, {
-          system: systemPrompt,
-        });
+        const response = await Promise.race([
+          this.llmClient.analyze(formattedPrompt, {
+            system: systemPrompt,
+          }),
+          new Promise((_, reject) => 
+            setTimeout(() => reject(new Error("LLM response timeout")), 30000)
+          )
+        ]);

Likely invalid or redundant comment.

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: 1

🔭 Outside diff range comments (1)
packages/core/src/core/chain-of-thought.ts (1)

Line range hint 636-650: Add structured validation criteria to validateGoalSuccess.

The goal validation logic relies heavily on LLM interpretation without structured criteria. Consider adding explicit validation rules.

 private async validateGoalSuccess(goal: Goal): Promise<boolean> {
   const blackboardState = await this.getBlackboardState();
+  
+  // Define structured validation criteria
+  const validationRules = {
+    requiredResources: (state: any) => {
+      return goal.required_resources?.every(resource => 
+        state.resources?.[resource] !== undefined
+      );
+    },
+    successCriteria: (state: any) => {
+      return goal.success_criteria?.every(criteria => 
+        // Add specific criteria validation logic
+        this.validateCriteria(criteria, state)
+      );
+    }
+  };
+
+  // Validate against structured rules first
+  const structuredValidation = Object.values(validationRules)
+    .every(rule => rule(blackboardState));
+
+  if (!structuredValidation) {
+    return false;
+  }

   const prompt = `
     <goal_validation>
🧹 Nitpick comments (3)
packages/core/src/core/chain-of-thought.ts (3)

30-44: Add JSDoc documentation for interfaces.

The newly added interfaces RefinedGoal and LLMValidationOptions<T> would benefit from JSDoc documentation explaining their purpose and parameters.

Add documentation like this:

+/**
+ * Represents a refined goal with specific success criteria and requirements.
+ */
 interface RefinedGoal {
   description: string;
   success_criteria: string[];
   priority: number;
   horizon: "short";
   requirements: Record<string, any>;
 }

+/**
+ * Options for validating LLM responses against a schema.
+ * @template T The expected type of the validated response
+ */
 interface LLMValidationOptions<T> {
   prompt: string;
   systemPrompt: string;
   schema: z.ZodSchema<T>;
   maxRetries?: number;
   onRetry?: (error: Error, attempt: number) => void;
 }

53-53: Consider making memory property private with public accessor methods.

Making the memory property public exposes internal implementation details and could lead to encapsulation issues. Consider making it private and providing specific public methods for memory operations.

-  public memory: VectorDB;
+  private memory: VectorDB;
+
+  /**
+   * Get the memory instance for specific operations
+   */
+  public getMemory(): VectorDB {
+    return this.memory;
+  }

1345-1358: Enhance emotion determination logic.

The current emotion determination is overly simplistic and could miss nuanced emotional states. Consider using a more comprehensive approach.

-const resultLower = resultStr.toLowerCase();
-const emotions: string[] = [];
-
-// Success/failure emotions
-const isFailure =
-  resultLower.includes("error") || resultLower.includes("failed");
-const isHighImportance = importance > 0.7;
-
-if (isFailure) {
-  emotions.push("frustrated");
-  if (isHighImportance) emotions.push("concerned");
-} else {
-  emotions.push("satisfied");
-  if (isHighImportance) emotions.push("excited");
-}

+const emotionMap = {
+  positive: {
+    high: ["excited", "triumphant"],
+    medium: ["satisfied", "confident"],
+    low: ["content", "calm"]
+  },
+  negative: {
+    high: ["frustrated", "concerned"],
+    medium: ["disappointed", "uncertain"],
+    low: ["hesitant", "cautious"]
+  }
+};
+
+const emotions: string[] = [];
+const resultLower = resultStr.toLowerCase();
+
+// Determine success/failure
+const isFailure = resultLower.includes("error") || 
+                  resultLower.includes("failed") ||
+                  resultLower.includes("exception");
+
+// Determine intensity based on importance and complexity
+const intensity = importance > 0.7 ? "high" : 
+                 importance > 0.3 ? "medium" : "low";
+
+// Add primary emotion
+emotions.push(...emotionMap[isFailure ? "negative" : "positive"][intensity]);
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 7fbe41d and 39376f4.

📒 Files selected for processing (1)
  • packages/core/src/core/chain-of-thought.ts (18 hunks)
🔇 Additional comments (2)
packages/core/src/core/chain-of-thought.ts (2)

1506-1516: Add error handling for JSON parsing in getBlackboardState.

The JSON parsing operations lack error handling which could lead to runtime errors.

This issue was previously identified in the past reviews. The suggested fix remains valid.


1563-1650: Add timeout handling for LLM operations.

The LLM response validation should include a timeout mechanism to prevent hanging.

This issue was previously identified in the past reviews. The suggested timeout handling implementation remains valid.

ponderingdemocritus and others added 2 commits January 21, 2025 21:35
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
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: 5

♻️ Duplicate comments (4)
packages/core/src/core/llm-client.ts (2)

32-32: 🛠️ Refactor suggestion

Remove empty initialization method.

The initializeClient method is empty and appears to be unused.


104-105: ⚠️ Potential issue

Update the default model to match OpenRouter.

The default model still references Anthropic's model, which should be updated to an OpenRouter-compatible model.

packages/core/src/core/chain-of-thought.ts (2)

1486-1499: ⚠️ Potential issue

Add error handling for JSON parsing in getBlackboardState.

The function performs unsafe JSON parsing without proper error handling, which could crash the application.

This issue was previously identified in past reviews. The same fix is still needed:

 blackboardDocs
   .sort((a, b) => {
-    const aContent = JSON.parse(a.content);
-    const bContent = JSON.parse(b.content);
+    let aContent;
+    let bContent;
+    try {
+      aContent = JSON.parse(a.content);
+      bContent = JSON.parse(b.content);
+    } catch (error) {
+      this.logger.error("getBlackboardState", "Failed to parse blackboard document content", { error });
+      return 0; // Keep original order if parsing fails
+    }
     return aContent.timestamp - bContent.timestamp;
   })
   .forEach((doc) => {
-    const update = JSON.parse(doc.content);
+    let update;
+    try {
+      update = JSON.parse(doc.content);
+    } catch (error) {
+      this.logger.error("getBlackboardState", "Failed to parse blackboard document content", { error });
+      return;
+    }
     if (!state[update.type]) {
       state[update.type] = {};
     }

1545-1551: ⚠️ Potential issue

Add timeout handling for LLM operations.

The LLM validation lacks timeout handling, which could lead to hanging operations.

This issue was previously identified in past reviews. The same enhancement is still needed:

   private async getValidatedLLMResponse<T>({
     prompt,
     systemPrompt,
     schema,
     maxRetries = 3,
     onRetry,
+    timeout = 30000, // 30 second default timeout
   }: LLMValidationOptions<T>): Promise<T> {
🧹 Nitpick comments (6)
packages/core/src/core/env.ts (1)

12-12: Consider grouping related environment variables.

For better readability and maintenance, consider grouping related environment variables together. The OPENROUTER_API_KEY could be placed near other API-related variables like OPENAI_API_KEY.

  TWITTER_USERNAME: z.string(),
  TWITTER_PASSWORD: z.string(),
  TWITTER_EMAIL: z.string(),
  OPENAI_API_KEY: z.string(),
+ OPENROUTER_API_KEY: z.string(),
  CHROMA_URL: z.string().default("http://localhost:8000"),
  STARKNET_RPC_URL: z.string(),
  STARKNET_ADDRESS: z.string(),
  STARKNET_PRIVATE_KEY: z.string(),
- OPENROUTER_API_KEY: z.string(),
  GRAPHQL_URL: z.string(),
packages/core/src/core/llm-client.ts (2)

12-28: Consider adding validation for configuration values.

While the default values are sensible, it would be beneficial to add validation for user-provided values to ensure they are within acceptable ranges (e.g., temperature between 0 and 1).


144-165: Consider extracting system message to a constant.

The system message for JSON formatting could be moved to a constant for better maintainability and reuse.

+ private static readonly JSON_SYSTEM_MESSAGE = "\n\nProvide response in JSON format.";

  async analyze(
    prompt: string,
    options: AnalysisOptions = {}
  ): Promise<string | StructuredAnalysis> {
    // ...
    const response = await generateText({
      model: openrouter(this.config.model),
      temperature,
      messages: [
        {
          role: "system",
-         content: "\n\nProvide response in JSON format.",
+         content: LLMClient.JSON_SYSTEM_MESSAGE,
        },
        // ...
      ],
    });
packages/core/src/types/index.ts (3)

118-130: Enhance error handling in ActionStep interface.

The error property in ActionStep could be more specific and structured to improve error handling and debugging.

Consider this enhancement:

 export interface ActionStep extends BaseStep {
   type: "action";
   content: string;
   toolCall?: {
     name: string;
     arguments: any;
     id: string;
   };
-  error?: Error;
+  error?: {
+    type: string;
+    message: string;
+    code?: string;
+    details?: Record<string, unknown>;
+    timestamp: number;
+  };
   observations?: string;
   actionOutput?: any;
   duration?: number;
 }

179-188: Add validation constraints for goal scoring.

The outcomeScore and score history lack validation constraints, which could lead to inconsistent scoring across the application.

Consider adding these constraints:

-  outcomeScore?: number;
+  outcomeScore?: number & { __brand: 'ValidatedScore' };  // Using TypeScript branded types
+  scoreRange: {
+    min: number;
+    max: number;
+    step?: number;
+  };
   scoreHistory?: Array<{
     timestamp: number;
     score: number;
+    validator: (score: number) => boolean;
     comment?: string;
   }>;

229-251: Standardize event metadata structure.

The events have inconsistent metadata structures. Some events include detailed data while others lack context that could be valuable for logging and debugging.

Consider standardizing the event metadata:

 export interface ChainOfThoughtEvents {
+  // Base event metadata
+  interface BaseEventMetadata {
+    timestamp: number;
+    correlationId: string;
+    source: string;
+  }
+
   step: (step: Step) => void;
-  "action:start": (action: CoTAction) => void;
+  "action:start": (data: { action: CoTAction } & BaseEventMetadata) => void;
   "action:complete": (data: { action: CoTAction; result: string }) => void;
   // ... apply similar pattern to other events
 }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 39376f4 and 097a6f5.

📒 Files selected for processing (7)
  • examples/example-basic.ts (3 hunks)
  • examples/example-goal.ts (4 hunks)
  • examples/example-twitter.ts (1 hunks)
  • packages/core/src/core/chain-of-thought.ts (18 hunks)
  • packages/core/src/core/env.ts (1 hunks)
  • packages/core/src/core/llm-client.ts (4 hunks)
  • packages/core/src/types/index.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • examples/example-goal.ts
  • examples/example-basic.ts
🔇 Additional comments (2)
examples/example-twitter.ts (1)

24-24: Document the model choice and verify compatibility.

While "deepseek/deepseek-r1" might be a "clutch model", it would be helpful to:

  1. Document why this specific model was chosen
  2. Verify its compatibility with OpenRouter
  3. Consider adding a constant for the model name to maintain consistency
packages/core/src/core/llm-client.ts (1)

2-11: LGTM! Clean import organization.

The imports are well-organized, with types separated from implementation imports.

Comment on lines +79 to 101
private async call(
prompt: string,
signal: AbortSignal
): Promise<LLMResponse> {
if (!this.anthropic) throw new Error("Anthropic client not initialized");

const response = await this.anthropic.messages.create(
{
model: this.config.model,
max_tokens: this.config.maxTokens,
temperature: this.config.temperature,
messages: [{ role: "user", content: prompt }],
},
{ signal },
);
const response = await generateText({
model: openrouter(this.config.model),
prompt,
abortSignal: signal,
});

return {
text: response.content[0].type === "text" ? response.content[0].text : "",
text: response.text,
model: this.config.model,
usage: {
prompt_tokens: response.usage.input_tokens,
completion_tokens: response.usage.output_tokens,
total_tokens: response.usage.input_tokens +
response.usage.output_tokens,
prompt_tokens: response.usage.promptTokens,
completion_tokens: response.usage.completionTokens,
total_tokens:
response.usage.promptTokens + response.usage.completionTokens,
},
metadata: {
stop_reason: response.stop_reason,
stop_reason: response.finishReason,
},
};
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

Add error handling for OpenRouter API responses.

The call method should include explicit error handling for OpenRouter-specific error cases. Also, consider adding type assertions or runtime checks for the response structure.

  private async call(
    prompt: string,
    signal: AbortSignal
  ): Promise<LLMResponse> {
+   try {
      const response = await generateText({
        model: openrouter(this.config.model),
        prompt,
        abortSignal: signal,
      });

+     if (!response?.usage) {
+       throw new Error('Invalid response format from OpenRouter API');
+     }

      return {
        text: response.text,
        model: this.config.model,
        usage: {
          prompt_tokens: response.usage.promptTokens,
          completion_tokens: response.usage.completionTokens,
          total_tokens:
            response.usage.promptTokens + response.usage.completionTokens,
        },
        metadata: {
          stop_reason: response.finishReason,
        },
      };
+   } catch (error) {
+     throw new LLMError(`OpenRouter API error: ${error.message}`, 'openrouter', error);
+   }
  }

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

Comment on lines +150 to +157
export type HorizonType = "long" | "medium" | "short";
export type GoalStatus =
| "pending"
| "active"
| "completed"
| "failed"
| "ready"
| "blocked";
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

Add explicit status transition constraints.

The GoalStatus type allows any transition between states, which could lead to invalid state changes (e.g., from "failed" to "active").

Consider adding a type to define valid status transitions:

export const VALID_GOAL_TRANSITIONS: Record<GoalStatus, GoalStatus[]> = {
  pending: ["active", "blocked"],
  active: ["completed", "failed", "blocked"],
  completed: [],  // Terminal state
  failed: ["pending"],  // Can retry
  ready: ["active", "blocked"],
  blocked: ["ready", "pending"]
};

Comment on lines 104 to +105
// Initialize single memory system
this.memory = new ChromaVectorDB("agent_memory", {
chromaUrl: config.chromaUrl,
logLevel: config.logLevel,
});
}

private registerDefaultActions() {
this.actionRegistry.set("SYSTEM_PROMPT", systemPromptAction);
this.memory = memory;
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 memory validation and error handling.

The memory initialization lacks validation and error handling for the VectorDB instance.

Add validation:

-    // Initialize single memory system
-    this.memory = memory;
+    if (!memory) {
+      throw new Error('VectorDB instance is required');
+    }
+    if (typeof memory.storeDocument !== 'function' || 
+        typeof memory.findSimilarDocuments !== 'function') {
+      throw new Error('Invalid VectorDB instance: missing required methods');
+    }
+    this.memory = memory;
📝 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
// Initialize single memory system
this.memory = new ChromaVectorDB("agent_memory", {
chromaUrl: config.chromaUrl,
logLevel: config.logLevel,
});
}
private registerDefaultActions() {
this.actionRegistry.set("SYSTEM_PROMPT", systemPromptAction);
this.memory = memory;
if (!memory) {
throw new Error('VectorDB instance is required');
}
if (typeof memory.storeDocument !== 'function' ||
typeof memory.findSimilarDocuments !== 'function') {
throw new Error('Invalid VectorDB instance: missing required methods');
}
this.memory = memory;

Comment on lines +1566 to +1571
while (attempts < maxRetries) {
try {
const response = await this.llmClient.analyze(formattedPrompt, {
system: systemPrompt,
});

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

Implement rate limiting for LLM requests.

The retry mechanism could potentially overwhelm the LLM service without proper rate limiting.

Add rate limiting:

+  private rateLimiter = new RateLimiter({
+    tokensPerInterval: 50,
+    interval: 'minute'
+  });
+
   while (attempts < maxRetries) {
     try {
+      await this.rateLimiter.removeTokens(1);
       const response = await this.llmClient.analyze(formattedPrompt, {
         system: systemPrompt,
       });

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

Comment on lines 64 to 71
private llmClient: LLMClient,
memory: VectorDB,
initialContext?: ChainOfThoughtContext,
config: {
chromaUrl?: string;
logLevel?: LogLevel;
} = {}
) {
super();
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

Refactor constructor to reduce complexity.

The constructor handles multiple initialization concerns, which violates the Single Responsibility Principle.

Consider breaking down the initialization:

 constructor(
   private llmClient: LLMClient,
   memory: VectorDB,
   initialContext?: ChainOfThoughtContext,
-  config: {
-    logLevel?: LogLevel;
-  } = {}
+  config: ChainOfThoughtConfig = {}
 ) {
   super();
+  this.initializeEventEmitter();
+  this.initializeLogger(config);
+  this.initializeMemory(memory);
+  this.initializeContext(initialContext);
+  this.initializeGoalManager();
+  this.registerDefaultActions();
 }

+interface ChainOfThoughtConfig {
+  logLevel?: LogLevel;
+  memoryConfig?: {
+    maxDocuments?: number;
+    pruningStrategy?: 'lru' | 'importance';
+  };
+  contextConfig?: {
+    snapshotInterval?: number;
+  };
+}

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

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.

1 participant