Skip to content

Added OpenAI compatible "input" field support in /v1/responses API#69

Merged
olesho merged 1 commit intomainfrom
feat/openai-compatible-api
Nov 14, 2025
Merged

Added OpenAI compatible "input" field support in /v1/responses API#69
olesho merged 1 commit intomainfrom
feat/openai-compatible-api

Conversation

@olesho
Copy link
Contributor

@olesho olesho commented Nov 13, 2025

Changes to /v1/responses API

  • Support for "endpoint" field for LiteLLM
  • Support for array of messages in the "input" field

Summary by CodeRabbit

Release Notes

New Features

  • Added LiteLLM provider support with per-model endpoint configuration
  • Enhanced multi-turn conversation support with conversation history preservation
  • Implemented system prompt override capability for personalized chat interactions
  • Extended input validation to support OpenAI-compatible conversation formats

Documentation

  • Updated API documentation with LiteLLM configuration examples and endpoint handling guidance

@olesho olesho requested a review from tysonthomas9 November 13, 2025 23:46
@claude
Copy link

claude bot commented Nov 13, 2025

Claude finished @olesho's task —— View job


Code Review Complete ✅

Reviewing PR #69: "Added OpenAI compatible 'input' field support in /v1/responses API"

Todo List:

  • Read repository CLAUDE.md files for guidelines
  • Analyze API server changes (agent-server/nodejs/src/api-server.js)
  • Review frontend changes (GraphHelpers.ts, LLMConfigurationManager.ts, EvaluationAgent.ts, EvaluationProtocol.ts)
  • Check documentation updates
  • Assess code quality and best practices
  • Look for potential bugs and security issues
  • Evaluate performance considerations
  • Check test coverage
  • Provide comprehensive feedback

Summary

This PR successfully adds OpenAI Responses API compatible input field support, enabling both string and conversation array formats. The implementation is comprehensive, covering backend validation, frontend message handling, and documentation updates.

✅ Strengths

Code Quality & Architecture

  • Clean separation of concerns: Backend validation separate from frontend processing
  • Consistent with existing patterns: Follows established API patterns in the codebase
  • Comprehensive input validation: Handles edge cases with proper error messages
  • Type safety: TypeScript interfaces properly updated in EvaluationProtocol.ts

Implementation Quality

  • Robust input validation (api-server.js:358-396): Validates array length (max 100), message structure, content length (max 10KB), and role values
  • Backwards compatibility: String format still supported alongside new array format
  • Proper message conversion (EvaluationAgent.ts:892-912): Cleanly converts OpenAI format to internal ChatMessage format
  • Good error handling: Clear error messages for validation failures

Security & Best Practices

  • Input sanitization: Validates message roles, content types, and array limits
  • API key redaction: Logs show redacted credentials (api-server.js:405-409)
  • No credential exposure: Sensitive data properly handled in logs

⚠️ Areas for Improvement

Code Quality Issues

  1. Unused import (api-server.js:9):

    import yaml from 'js-yaml';  // Used in loadConfigDefaults but could be more explicit
  2. Missing JSDoc documentation for new methods:

    • convertMessagesToChatMessages() - should document return type and purpose
    • extractSystemPrompt() - should document aggregation behavior
  3. Magic numbers: Consider extracting to constants:

    const MAX_MESSAGES = 100;
    const MAX_MESSAGE_LENGTH = 10000;

Potential Bugs

  1. Array mutation risk (EvaluationAgent.ts:918-922):

    const systemMessages = messages.filter(msg => msg.role === 'system');
    return systemMessages.length > 0
      ? systemMessages.map(m => m.content).join('\n\n')  // Could create very long prompts
      : undefined;

    Recommendation: Add length limit for aggregated system prompts

  2. Incomplete validation (api-server.js:392-395):

    const hasUserMessage = requestBody.input.some(msg => msg.role === 'user');
    if (!hasUserMessage) {
      throw new Error('Invalid "input" field. Conversation must contain at least one user message.');
    }

    Missing: Validation that user message isn't empty string

Performance Considerations

  1. Inefficient filtering (EvaluationAgent.ts:942-943):

    const lastUserMsg = input.filter(m => m.role === 'user').pop();

    Recommendation: Use findLast() or reverse iteration to avoid filtering entire array

  2. Potential memory usage: No limit on total conversation size beyond individual message length

Test Coverage

Critical Gap: No tests found for the new OpenAI input format functionality. The agent-server directory has no test files.

Recommendations:

  • Add unit tests for input validation logic
  • Add integration tests for both string and array input formats
  • Test edge cases (empty arrays, malformed messages, oversized content)

🔒 Security Assessment

Overall: Good security practices observed

  • Input validation prevents injection attacks
  • Credential handling follows secure logging practices
  • No obvious security vulnerabilities

Minor concern: Aggregated system prompts could potentially become very large, but this is mitigated by individual message length limits.

📋 Recommendations

High Priority

  1. Add comprehensive tests for the new input format functionality
  2. Add input validation for empty user message content
  3. Add JSDoc documentation for new methods

Medium Priority

  1. Extract magic numbers to named constants
  2. Optimize array filtering performance
  3. Add system prompt length validation

Low Priority

  1. Review unused imports
  2. Consider adding telemetry for input format usage metrics

🎯 Overall Assessment

Recommendation: ✅ Approve with minor changes

This is a well-implemented feature that successfully adds OpenAI compatibility while maintaining backwards compatibility. The code quality is good overall, with comprehensive validation and proper error handling. The main concerns are the lack of tests and a few minor performance optimizations.

The implementation correctly follows the existing codebase patterns and adds significant value for API compatibility.


@coderabbitai
Copy link

coderabbitai bot commented Nov 13, 2025

Walkthrough

These changes add LiteLLM provider support alongside OpenAI, with per-model tier endpoints, enhanced input validation for conversation history, and system prompt configuration overrides integrated throughout the evaluation pipeline from frontend to backend.

Changes

Cohort / File(s) Change Summary
Documentation Updates
agent-server/README.md, agent-server/nodejs/CLAUDE.md, agent-server/nodejs/README.md
Added LiteLLM configuration examples, per-tier endpoint semantics, conversation state input formats, and endpoint priority/fallback guidance across eval-server and API documentation.
Backend API Validation & Configuration
agent-server/nodejs/src/api-server.js
Expanded input validation to support string or OpenAI-style conversation arrays with strict per-message checks (roles, content, constraints). Added endpoint extraction per tier with fallbacks, refactored model configuration handling via new helper extractModelTierConfig(), and enhanced logging with endpoint visibility.
Frontend Configuration Management
front_end/panels/ai_chat/core/LLMConfigurationManager.ts, front_end/panels/ai_chat/core/GraphHelpers.ts
Added optional systemPrompt field to LLMConfig interface. Modified system prompt selection in createSystemPromptAsync() to prioritize override config, with fallback to agent-type or default prompts. Clarified routeNextNode() return type to string.
Frontend Evaluation Protocol & Logic
front_end/panels/ai_chat/evaluation/remote/EvaluationProtocol.ts, front_end/panels/ai_chat/evaluation/remote/EvaluationAgent.ts
Introduced EvaluationMessage interface and expanded EvaluationParams input/model unions to support per-request api_key and endpoint. Reworked chat evaluation input handling to extract nested model config with optional override, added helpers for message conversion and system prompt extraction, and enhanced tracing with config/history metadata.

Sequence Diagram

sequenceDiagram
    participant UI as Frontend UI
    participant Agent as EvaluationAgent
    participant Config as LLMConfigurationManager
    participant API as Backend API Server
    
    UI->>Agent: initiate chat evaluation<br/>(input: string|array|object)
    Agent->>Agent: extract nested model config<br/>(main/mini/nano/endpoint)
    Agent->>Config: getConfiguration()
    Config-->>Agent: return LLMConfig<br/>(with systemPrompt override)
    Agent->>Agent: merge model config + override
    Agent->>Agent: normalize input to unified shape<br/>(array→conversation, string→{message})
    Agent->>API: POST /v1/responses<br/>(mergedInput, modelConfig)
    API->>API: validate input format<br/>(string or OpenAI messages array)
    API->>API: extract per-tier endpoints<br/>(main/mini/nano or fallback)
    API->>API: create nested request<br/>(with provider+endpoint)
    API-->>Agent: response
    Agent->>Agent: attach tracing metadata<br/>(hasConversationHistory, hasSystemPrompt)
    Agent-->>UI: return result
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • api-server.js: Input validation logic with multiple conditional paths (string vs. array), endpoint extraction with fallbacks per tier, and helper method normalization require careful verification of edge cases and type handling.
  • EvaluationAgent.ts: Configuration merging/override logic, message conversion helpers, and tracing span enrichment introduce complex state management and input transformation; verify correct conversation history pruning and prompt concatenation.
  • Protocol and interface changes: Validate that union type expansions in EvaluationParams.input and new EvaluationMessage interface are correctly consumed across the call chain.
  • GraphHelpers.ts: System prompt override precedence logic and return type clarification need verification that the fallback chain (config → agent-type → default) is applied consistently.

Possibly related PRs

Suggested reviewers

  • tysonthomas9

Poem

🐰 Hops with glee — LiteLLM joins the fold,

With nested tiers and endpoints bold!

System prompts now override with grace,

Conversation threads find their place.

Configuration flows from end to end,

A rabbit's gift to AI blend! 🌟

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: adding OpenAI-compatible input field support to the /v1/responses API. This is evident across multiple files where input validation, conversation arrays, and message formats are added.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/openai-compatible-api

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@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: 2

🧹 Nitpick comments (2)
agent-server/nodejs/src/api-server.js (2)

574-582: Consider object property shorthand for consistency.

The provider variable is assigned and then used in the return object. While the code is correct, the static analysis tool suggests using ES6 object property shorthand for consistency with modern JavaScript conventions.

Apply this diff to use property shorthand:

-    const provider = defaults.provider || 'openai';
-
     return {
-      provider: provider,
+      provider: defaults.provider || 'openai',
       model: defaultModels[tier],
       api_key: process.env.OPENAI_API_KEY,
       // Add endpoint for litellm provider with env var fallback
-      endpoint: defaults.endpoint || (provider === 'litellm' ? process.env.LITELLM_ENDPOINT : undefined)
+      endpoint: defaults.endpoint || ((defaults.provider || 'openai') === 'litellm' ? process.env.LITELLM_ENDPOINT : undefined)
     };

Alternatively, if you prefer to keep the variable for clarity, you can use shorthand like this:

     const provider = defaults.provider || 'openai';
 
     return {
-      provider: provider,
+      provider,
       model: defaultModels[tier],
       api_key: process.env.OPENAI_API_KEY,
       // Add endpoint for litellm provider with env var fallback
       endpoint: defaults.endpoint || (provider === 'litellm' ? process.env.LITELLM_ENDPOINT : undefined)
     };

523-559: Minor: JSDoc formatting style.

The static analysis tool suggests removing hyphens before @param descriptions (lines 527-529) to align with JSDoc conventions. This is a style preference and doesn't affect functionality.

If you'd like to align with the linter rules, you can remove the hyphens:

  /**
   * Extract model tier configuration from request
   * Handles both nested format { provider: "litellm", model: "qwen3:14b", api_key: "..." }
   * and flat format "qwen3:14b"
-  * @param {'main' | 'mini' | 'nano'} tier - Model tier
-  * @param {any} tierConfig - Configuration from request (can be object or string)
-  * @param {Object} defaults - Default configuration from config.yaml
-  * @returns {Object} Extracted configuration (without endpoint)
+  * @param {'main' | 'mini' | 'nano'} tier Model tier
+  * @param {any} tierConfig Configuration from request (can be object or string)
+  * @param {Object} defaults Default configuration from config.yaml
+  * @returns {Object} Extracted configuration (without endpoint)
   */

The same applies to the JSDoc for createDynamicRequestNested at lines 655-656.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a67e120 and 01fa264.

📒 Files selected for processing (8)
  • agent-server/README.md (2 hunks)
  • agent-server/nodejs/CLAUDE.md (2 hunks)
  • agent-server/nodejs/README.md (1 hunks)
  • agent-server/nodejs/src/api-server.js (6 hunks)
  • front_end/panels/ai_chat/core/GraphHelpers.ts (2 hunks)
  • front_end/panels/ai_chat/core/LLMConfigurationManager.ts (2 hunks)
  • front_end/panels/ai_chat/evaluation/remote/EvaluationAgent.ts (6 hunks)
  • front_end/panels/ai_chat/evaluation/remote/EvaluationProtocol.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
front_end/panels/ai_chat/evaluation/remote/EvaluationAgent.ts (4)
front_end/panels/ai_chat/core/AgentService.ts (1)
  • logger (378-389)
front_end/panels/ai_chat/models/ChatTypes.ts (1)
  • ChatMessage (74-75)
front_end/panels/ai_chat/ui/ChatView.ts (1)
  • m (1384-1406)
front_end/panels/ai_chat/tracing/TracingConfig.ts (1)
  • withTracingContext (313-315)
front_end/panels/ai_chat/core/GraphHelpers.ts (1)
front_end/panels/ai_chat/core/LLMConfigurationManager.ts (1)
  • LLMConfigurationManager (44-433)
🪛 ESLint
front_end/panels/ai_chat/evaluation/remote/EvaluationAgent.ts

[error] 19-19: There should be at least one empty line between import groups

(import/order)


[error] 345-345: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 559-559: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 564-564: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 565-565: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 566-566: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 567-567: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 568-568: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 568-568: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 569-569: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 569-569: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 901-908: Unnecessary 'else' after 'return'.

(no-else-return)


[error] 925-925: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 928-928: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 953-953: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 1017-1017: Expected property shorthand.

(object-shorthand)


[error] 1018-1018: Expected property shorthand.

(object-shorthand)


[error] 1019-1019: Expected property shorthand.

(object-shorthand)


[error] 1020-1020: Expected property shorthand.

(object-shorthand)


[error] 1028-1028: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 1029-1029: Expected property shorthand.

(object-shorthand)


[error] 1030-1030: Expected property shorthand.

(object-shorthand)


[error] 1031-1031: Expected property shorthand.

(object-shorthand)


[error] 1032-1032: Expected property shorthand.

(object-shorthand)


[error] 1071-1071: Forbidden non-null assertion.

(@typescript-eslint/no-non-null-assertion)


[error] 1072-1072: Forbidden non-null assertion.

(@typescript-eslint/no-non-null-assertion)

agent-server/nodejs/src/api-server.js

[error] 527-527: There must be no hyphen before @param description.

(jsdoc/require-hyphen-before-param-description)


[error] 528-528: There must be no hyphen before @param description.

(jsdoc/require-hyphen-before-param-description)


[error] 529-529: There must be no hyphen before @param description.

(jsdoc/require-hyphen-before-param-description)


[error] 577-577: Expected property shorthand.

(object-shorthand)


[error] 655-655: There must be no hyphen before @param description.

(jsdoc/require-hyphen-before-param-description)


[error] 656-656: There must be no hyphen before @param description.

(jsdoc/require-hyphen-before-param-description)

front_end/panels/ai_chat/evaluation/remote/EvaluationProtocol.ts

[error] 92-92: Expected a comma.

(@stylistic/member-delimiter-style)

front_end/panels/ai_chat/core/GraphHelpers.ts

[error] 10-10: ./LLMConfigurationManager.js import should occur before import of ./Logger.js

(import/order)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: claude-review
🔇 Additional comments (10)
agent-server/nodejs/CLAUDE.md (3)

73-117: LGTM! Clear documentation for LiteLLM support.

The request examples for OpenAI and LiteLLM providers are well-structured and clearly illustrate the endpoint configuration options. The fallback chain (per-tier endpoint → top-level endpoint → environment variable) is properly documented.


118-159: Well-documented conversation format with clear constraints.

The Conversation State format documentation clearly explains the message structure, roles, and requirements. The constraints (max 100 messages, 10,000 characters each, at least one user message) are explicitly stated.


160-177: Response format documentation is clear and complete.

The OpenAI-compatible response format with metadata is well-documented. The note about using clientId and tabId for screenshot capture provides important context for API consumers.

agent-server/README.md (1)

78-136: Comprehensive documentation for LiteLLM provider support.

The documentation clearly explains both OpenAI and LiteLLM request formats, with explicit endpoint configuration guidance. The priority order (per-tier → top-level → environment variable) helps users understand the fallback behavior.

agent-server/nodejs/src/api-server.js (4)

344-396: Robust input validation for conversation arrays.

The validation logic thoroughly checks the conversation array format:

  • Type validation (string or array)
  • Array length constraints (1-100 messages)
  • Per-message validation (role, content, type, length)
  • Required user message check

The error messages are clear and include context (message index), which aids debugging.


405-429: Improved logging with privacy-conscious formatting.

The logging enhancements appropriately handle sensitive data:

  • Endpoint information is marked as '[configured]' rather than exposed
  • Long inputs are truncated
  • Conversation arrays are summarized by message count
  • Input type is explicitly logged for debugging

486-521: Proper endpoint configuration extraction with fallback chain.

The endpoint extraction logic correctly implements the documented priority:

  1. Tier-specific endpoint (e.g., main_model.endpoint)
  2. Top-level endpoint (model.endpoint)
  3. Undefined (falls back to environment variable later)

The use of extractModelTierConfig helper promotes consistency across tiers.


659-701: Input format handling is well-implemented with clear logic.

The method correctly handles both input formats:

  • String format: Wraps in { message: input } for legacy compatibility
  • Array format: Passes through directly for conversation history

The comments referencing EvaluationAgent.ts methods provide helpful context for understanding how the formats are processed downstream.

agent-server/nodejs/README.md (2)

390-420: Complete LiteLLM provider documentation with clear endpoint priority.

The LiteLLM example and endpoint configuration priority are well-documented. The 4-level priority chain (per-tier → top-level → env var → default) provides clear guidance for users configuring custom endpoints.


421-466: Comprehensive conversation format documentation.

The Conversation State format is thoroughly documented with:

  • Example showing all message roles (system, user, assistant)
  • Clear role descriptions and use cases
  • Explicit constraints matching the validation logic
  • Requirements for message structure

This provides users with complete information to use the conversation history feature.

Comment on lines +548 to +555
// If it's just a string (model name), use default provider
if (typeof tierConfig === 'string') {
return {
provider: defaults.provider || 'openai',
model: tierConfig,
api_key: process.env.OPENAI_API_KEY
};
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify API key selection for string format.

When tierConfig is a string (model name only), the code uses process.env.OPENAI_API_KEY even though the provider is determined from defaults.provider. If the default provider is not 'openai' (e.g., 'litellm'), this could result in using the wrong API key.

Run this script to check if there are scenarios where default provider is not 'openai':


🏁 Script executed:

#!/bin/bash
# Search for config.yaml files that might set a different default provider
rg -n -C3 'provider:.*(?!openai)' --type yaml

# Check if LITELLM_ENDPOINT or other provider env vars are used as defaults
rg -n 'provider.*litellm|groq|openrouter' --type js --type yaml

Length of output: 750


🏁 Script executed:

#!/bin/bash
# Get the full extractModelTierConfig function
ast-grep --pattern $'function extractModelTierConfig($_) {
  $$$
}'

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look for where defaults object is initialized/set
rg -n -B5 -A10 'defaults\s*=.*provider' --type js

# Check what API key env vars are used for different providers
rg -n 'process\.env\.[A-Z_]*API|process\.env\.LITELLM|process\.env\.GROQ|process\.env\.OPENROUTER' --type js | head -20

Length of output: 883


🏁 Script executed:

#!/bin/bash
# Check how defaults are passed to extractModelTierConfig
rg -n 'extractModelTierConfig' --type js -B2 -A2

Length of output: 1427


🏁 Script executed:

#!/bin/bash
# Get the complete extractModelTierConfig function (lines 532-559)
sed -n '532,559p' agent-server/nodejs/src/api-server.js

Length of output: 851


🏁 Script executed:

#!/bin/bash
# Find where defaults object is created before calling extractModelTierConfig
rg -n -B20 'extractModelTierConfig.*main' agent-server/nodejs/src/api-server.js | head -50

Length of output: 1204


🏁 Script executed:

#!/bin/bash
# Check if there's a mapping of provider to API key env var name
rg -n -C5 'LITELLM_API_KEY|GROQ_API_KEY|OPENROUTER_API_KEY' agent-server/nodejs/src/api-server.js

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Find where this.configDefaults is set
rg -n 'this\.configDefaults\s*=' agent-server/nodejs/src/api-server.js

Length of output: 161


🏁 Script executed:

#!/bin/bash
# Find constructor or initialization of configDefaults
rg -n 'configDefaults' agent-server/nodejs/src/api-server.js -B2 -A2

Length of output: 939


🏁 Script executed:

#!/bin/bash
# Check config.js to see what default providers can be
rg -n -A5 'model.*:' agent-server/nodejs/src/config.js | head -30

Length of output: 205


🏁 Script executed:

#!/bin/bash
# Get the complete hardcoded defaults (lines 36-54)
sed -n '36,54p' agent-server/nodejs/src/api-server.js

Length of output: 511


🏁 Script executed:

#!/bin/bash
# Look for config.yaml file or template in the repo
fd -e yaml -e yml | grep -i config | head -10

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check if createDefaultModelConfig uses provider from defaults
rg -n 'createDefaultModelConfig' agent-server/nodejs/src/api-server.js -A20 | head -30

Length of output: 1301


🏁 Script executed:

#!/bin/bash
# Get the full createDefaultModelConfig function
rg -n 'createDefaultModelConfig' agent-server/nodejs/src/api-server.js -A15 | head -40

Length of output: 1582


🏁 Script executed:

#!/bin/bash
# Check if there's any provider-to-api-key mapping logic anywhere
rg -n 'provider.*api_key|api_key.*provider' agent-server/nodejs/src/api-server.js

Length of output: 174


🏁 Script executed:

#!/bin/bash
# Get the full createDefaultModelConfig function implementation
sed -n '561,590p' agent-server/nodejs/src/api-server.js

Length of output: 1013


🏁 Script executed:

#!/bin/bash
# Check if config.yaml in repository can have a different provider set
find . -name "*.yaml" -o -name "*.yml" | xargs grep -l "provider:" 2>/dev/null | head -5

Length of output: 0


The API key selection hardcodes OpenAI credentials even when using other providers.

The issue extends beyond line 553. Both extractModelTierConfig (line 553) and createDefaultModelConfig (line 579) hardcode process.env.OPENAI_API_KEY regardless of the provider. This is inconsistent with:

  • The endpoint logic (line 581) which correctly checks provider === 'litellm'
  • The supported providers (config.js: openai, litellm, groq, openrouter), each with their own API key env vars (LITELLM_API_KEY, GROQ_API_KEY, OPENROUTER_API_KEY)

When a non-OpenAI provider is configured via defaults.provider (e.g., from config.yaml), the code will use the wrong API key at runtime, causing authentication failures.

Required fix: Map provider to the corresponding API key environment variable:

  • 'litellm' → process.env.LITELLM_API_KEY
  • 'groq' → process.env.GROQ_API_KEY
  • 'openrouter' → process.env.OPENROUTER_API_KEY
  • 'openai' (default) → process.env.OPENAI_API_KEY

Comment on lines +1049 to +1066
// This prevents duplication in the UI and LLM calls
const historyWithoutLastUser = conversationHistory.filter(msg => {
// Keep all model/assistant messages and all user messages except the last one
if (msg.entity === ChatMessageEntity.MODEL) {
return true;
}
// For user messages, only keep if it's not the last user message
if (msg.entity === ChatMessageEntity.USER) {
return msg.text !== userMessage;
}
return true; // Keep other message types
});
state.messages = [...historyWithoutLastUser];
logger.info('Loaded history without last user message', {
originalLength: conversationHistory.length,
filteredLength: historyWithoutLastUser.length
});
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Do not drop earlier duplicate user turns when trimming history

The current filter removes every ChatMessageEntity.USER whose text matches the latest user input. If a conversation contained the same prompt twice (e.g., “retry?”), both turns vanish from the history sent to AgentService, so the model no longer sees the earlier context. Please remove only the single most recent user item instead of filtering by value.

Apply this diff to keep all earlier duplicates:

-          const historyWithoutLastUser = conversationHistory.filter(msg => {
-            // Keep all model/assistant messages and all user messages except the last one
-            if (msg.entity === ChatMessageEntity.MODEL) {
-              return true;
-            }
-            // For user messages, only keep if it's not the last user message
-            if (msg.entity === ChatMessageEntity.USER) {
-              return msg.text !== userMessage;
-            }
-            return true; // Keep other message types
-          });
+          let lastUserIndex = -1;
+          for (let i = conversationHistory.length - 1; i >= 0; i--) {
+            if (conversationHistory[i].entity === ChatMessageEntity.USER) {
+              lastUserIndex = i;
+              break;
+            }
+          }
+          const historyWithoutLastUser =
+            lastUserIndex === -1
+              ? conversationHistory
+              : conversationHistory.filter((_, index) => index !== lastUserIndex);
📝 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
// This prevents duplication in the UI and LLM calls
const historyWithoutLastUser = conversationHistory.filter(msg => {
// Keep all model/assistant messages and all user messages except the last one
if (msg.entity === ChatMessageEntity.MODEL) {
return true;
}
// For user messages, only keep if it's not the last user message
if (msg.entity === ChatMessageEntity.USER) {
return msg.text !== userMessage;
}
return true; // Keep other message types
});
state.messages = [...historyWithoutLastUser];
logger.info('Loaded history without last user message', {
originalLength: conversationHistory.length,
filteredLength: historyWithoutLastUser.length
});
}
// This prevents duplication in the UI and LLM calls
let lastUserIndex = -1;
for (let i = conversationHistory.length - 1; i >= 0; i--) {
if (conversationHistory[i].entity === ChatMessageEntity.USER) {
lastUserIndex = i;
break;
}
}
const historyWithoutLastUser =
lastUserIndex === -1
? conversationHistory
: conversationHistory.filter((_, index) => index !== lastUserIndex);
state.messages = [...historyWithoutLastUser];
logger.info('Loaded history without last user message', {
originalLength: conversationHistory.length,
filteredLength: historyWithoutLastUser.length
});
}
🤖 Prompt for AI Agents
In front_end/panels/ai_chat/evaluation/remote/EvaluationAgent.ts around lines
1049 to 1066, the current filter removes all user messages whose text equals the
latest input; change it to remove only the single most recent user message that
matches userMessage. Specifically, find the index of the last
ChatMessageEntity.USER whose text === userMessage (e.g., iterate from the end or
use lastIndex logic), create a new array that omits only that one index (keep
all other messages, including earlier duplicates), assign that array to
state.messages, and retain the existing logging of originalLength and
filteredLength.

@olesho olesho merged commit 3e9469d into main Nov 14, 2025
6 checks passed
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