Skip to content

fix: support custom LLM clients in agent mode#1678

Open
shrey150 wants to merge 4 commits intomainfrom
shrey/fix-custom-llm-agent-mode
Open

fix: support custom LLM clients in agent mode#1678
shrey150 wants to merge 4 commits intomainfrom
shrey/fix-custom-llm-agent-mode

Conversation

@shrey150
Copy link
Contributor

@shrey150 shrey150 commented Feb 11, 2026

Summary

Adds getLanguageModel() to CustomOpenAIClient using @ai-sdk/openai's createOpenAI, so custom OpenAI-compatible clients (ZhipuAI, Ollama, etc.) work with agent mode.

Closes #1669

Test plan

  • getLanguageModel() returns a valid LanguageModelV2 (correct modelId, doGenerate, doStream, specificationVersion: "v2")
  • getLanguageModel() preserves custom baseURL for OpenAI-compatible providers (ZhipuAI, Ollama)
  • Live API call through generateText() (the AI SDK path agent mode uses) succeeds with the model from getLanguageModel()
  • createChatCompletion() (act/extract/observe path) still works — no regression
  • MissingLLMConfigurationError no-arg constructor still works
  • All 341 existing vitest tests pass with zero regressions

🤖 Generated with Claude Code

CustomOpenAIClient now implements getLanguageModel() using @ai-sdk/openai,
allowing it to work with agent mode which requires a LanguageModelV2 instance.
Also improves the error message when getLanguageModel() is missing.

Closes #1669

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@changeset-bot
Copy link

changeset-bot bot commented Feb 11, 2026

⚠️ No Changeset found

Latest commit: 9cbe1a2

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Matches the OpenAI API spec. Currently dead code in Stagehand's
act/extract/observe paths (they don't pass tools), but should be
correct regardless.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 3 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.
Architecture diagram
sequenceDiagram
    participant User as User Code
    participant Agent as V3AgentHandler
    participant Client as CustomOpenAIClient
    participant AISDK as @ai-sdk/openai
    participant Errors as MissingLLMConfigurationError

    User->>Agent: stagehand.agent(options)
    
    Note over Agent,Client: Validation of LLM Client Capabilities
    
    alt NEW: llmClient.getLanguageModel is missing
        Agent->>Errors: CHANGED: throw with specific "agent mode" instructions
        Errors-->>User: Error: implement getLanguageModel()
    else llmClient.getLanguageModel is defined
        Agent->>Client: NEW: getLanguageModel()
        
        activate Client
        Client->>AISDK: createOpenAI({ apiKey, baseURL })
        AISDK-->>Client: provider instance
        Client->>Client: provider(modelName)
        Client-->>Agent: Return LanguageModelV2 instance
        deactivate Client

        Agent->>Agent: Use model for agent reasoning loop
        Agent-->>User: Result
    end
Loading

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 11, 2026

Greptile Overview

Greptile Summary

These changes make custom OpenAI-compatible clients usable with stagehand.agent() by adding a getLanguageModel() implementation to the CustomOpenAIClient example (built via @ai-sdk/openai’s createOpenAI). The agent handler’s error path is also improved to explicitly explain that agent mode requires LLMClient.getLanguageModel() returning a LanguageModelV2, and the MissingLLMConfigurationError type is updated to accept an optional custom message while preserving the prior default messaging used by act/extract/observe.

Confidence Score: 4/5

  • This PR is likely safe to merge but has one correctness issue in the new error-guard logic that should be clarified/fixed.
  • The changes are small and localized (optional error message + example method). Main concern is the new !this.llmClient?.getLanguageModel guard: given llmClient is a required constructor argument, this branch is effectively dead for the “missing client” scenario and may not achieve the intended improved error behavior. No other functional regressions are evident from the diff.
  • packages/core/lib/v3/handlers/v3AgentHandler.ts

Important Files Changed

Filename Overview
packages/core/examples/external_clients/customOpenAI.ts Adds getLanguageModel() to CustomOpenAIClient using @ai-sdk/openai createOpenAI to support agent mode.
packages/core/lib/v3/handlers/v3AgentHandler.ts Improves MissingLLMConfigurationError message when getLanguageModel() is absent before agent execution; logic change impacts error path.
packages/core/lib/v3/types/public/sdkErrors.ts Extends MissingLLMConfigurationError to accept an optional custom message while keeping previous default message.

Sequence Diagram

sequenceDiagram
  autonumber
  participant User
  participant V3AgentHandler
  participant LLMClient
  participant CustomOpenAIClient
  participant AISDK as ai-sdk/openai

  User->>V3AgentHandler: execute()/stream(instruction|options)
  V3AgentHandler->>V3AgentHandler: prepareAgent()
  V3AgentHandler->>LLMClient: getLanguageModel()
  alt getLanguageModel missing
    V3AgentHandler-->>User: throw MissingLLMConfigurationError(custom message)
  else getLanguageModel present
    Note over CustomOpenAIClient: example implementation
    CustomOpenAIClient->>AISDK: createOpenAI({ apiKey, baseURL })
    AISDK-->>CustomOpenAIClient: provider(modelName)
    CustomOpenAIClient-->>V3AgentHandler: LanguageModelV2
    V3AgentHandler->>LLMClient: generateText()/streamText(model,...)
  end
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

3 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

No longer needed now that CustomOpenAIClient implements
getLanguageModel().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/core/lib/v3/handlers/v3AgentHandler.ts">

<violation number="1">
P2: Reverting to `MissingLLMConfigurationError()` with no argument produces the generic message *"No LLM API key or LLM Client configured … required to use act, extract, or observe"*, which is misleading when called from agent mode — the user may have a client configured that simply lacks `getLanguageModel()`. The PR description says it improves this error message, but the class constructor takes no arguments and the hardcoded default doesn't mention agent mode or `getLanguageModel()`.

Consider updating `MissingLLMConfigurationError` to accept an optional message, then pass the agent-specific guidance here.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/core/examples/external_clients/customOpenAI.ts">

<violation number="1">
P0: Bug: `inputSchema` is not a valid field for OpenAI chat completion tool definitions. The OpenAI API expects `parameters`. This change silently breaks tool/function calling via `createChatCompletion()` — the model will see tools with no parameter schema.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@@ -11,6 +11,8 @@ import {
LLMClient,
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 11, 2026

Choose a reason for hiding this comment

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

P0: Bug: inputSchema is not a valid field for OpenAI chat completion tool definitions. The OpenAI API expects parameters. This change silently breaks tool/function calling via createChatCompletion() — the model will see tools with no parameter schema.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/core/examples/external_clients/customOpenAI.ts, line 199:

<comment>Bug: `inputSchema` is not a valid field for OpenAI chat completion tool definitions. The OpenAI API expects `parameters`. This change silently breaks tool/function calling via `createChatCompletion()` — the model will see tools with no parameter schema.</comment>

<file context>
@@ -196,7 +196,7 @@ export class CustomOpenAIClient extends LLMClient {
           name: tool.name,
           description: tool.description,
-          parameters: tool.parameters,
+          inputSchema: tool.parameters,
         },
         type: "function",
</file context>
Suggested change
LLMClient,
parameters: tool.parameters,
Fix with Cubic

Comment on lines +43 to +50
public getLanguageModel(): LanguageModelV2 {
const provider = createOpenAI({
apiKey: this.client.apiKey,
baseURL: this.client.baseURL,
});
return provider(this.modelName);
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

it seems this just uses openai client meaning the custom one is never actually used?

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.

The agent cannot use a custom LLM client model.

2 participants