Skip to content

refact: openai-go#30

Merged
iohub merged 3 commits into
mainfrom
feat-openai-go
May 4, 2026
Merged

refact: openai-go#30
iohub merged 3 commits into
mainfrom
feat-openai-go

Conversation

@iohub
Copy link
Copy Markdown
Owner

@iohub iohub commented May 4, 2026

Summary by Sourcery

Replace langchaingo-based LLM integration with a custom OpenAI Engine abstraction and adapt agents, tools, and app code to use it.

Enhancements:

  • Introduce a unified LLM Engine interface and OpenAI-based implementation with streaming and tool-call support.
  • Refactor agents, tools, and the CodingAssistant to use the new Engine abstraction instead of langchaingo llms, simplifying message and tool representations.
  • Simplify HTTP error extraction and LLM logging while preserving key diagnostics.
  • Remove unused Bedrock and other transitive dependencies from go.mod in favor of the official openai-go SDK.

Build:

  • Switch dependency from github.com/tmc/langchaingo to github.com/openai/openai-go and prune related indirect dependencies.

Tests:

  • Update and adjust ConductorAgent tests to work with the new Engine abstraction and message model, adding assertions for tool-role messages and tool calls.

ydl and others added 3 commits May 4, 2026 15:51
- assistant消息携带reasoning时通过SetExtraFields注入reasoning_content字段,确保DeepSeek thinking模式多轮对话正常
- executor/conductor构建assistant消息时携带Reasoning字段,保持reasoning在多轮LLM调用中传递
- streaming模式下从delta raw JSON提取并累加reasoning_content
- 修复openai.ToolMessage(content, toolCallID)参数顺序反了导致tool_call_id不匹配

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

sourcery-ai Bot commented May 4, 2026

Reviewer's Guide

Refactors the LLM integration by introducing a provider-agnostic internal Engine interface backed by the official openai-go SDK, replacing langchaingo (and Bedrock), updating agents, tools, and tests to use the new abstractions, simplifying logging, and cleaning up dependencies.

Sequence diagram for ConductorAgent tool-calling loop using Engine

sequenceDiagram
    actor User
    participant CodingAssistant
    participant ConductorAgent
    participant Engine
    participant ToolAdapter
    participant ToolImpl as ToolImplementation
    participant Memory as ConversationMemory

    User->>CodingAssistant: ProcessCodingTaskWithCallback / ProcessConversation
    CodingAssistant->>CodingAssistant: Init(engine, workDir)
    CodingAssistant->>ConductorAgent: Run(ctx, input, memory)

    loop up to maxSteps
        ConductorAgent->>ConductorAgent: Build messages []Message
        ConductorAgent->>ConductorAgent: Build toolDefs []ToolDef
        ConductorAgent->>Engine: GenerateContent(ctx, messages, toolDefs, nil)
        Engine-->>ConductorAgent: Response{Choices[0]}

        alt assistant returns content
            ConductorAgent->>Memory: AddAssistantMessage(content, toolCalls)
        end

        alt no toolCalls
            ConductorAgent-->>CodingAssistant: final content
            CodingAssistant-->>User: final answer
        else has toolCalls
            loop for each ToolCall
                ConductorAgent->>ToolAdapter: Call(ctx, function.Arguments)
                ToolAdapter->>ToolImpl: Execute(ctx, params)
                ToolImpl-->>ToolAdapter: toolResult
                ToolAdapter-->>ConductorAgent: toolResult

                ConductorAgent->>Memory: AddToolMessage(toolResult, toolCall.ID)

                ConductorAgent->>ConductorAgent: Append tool Message {RoleTool, Content, ToolCallID, ToolName}
            end
        end
    end
Loading

Class diagram for new LLM Engine abstractions and agents

classDiagram
    direction LR

    class Engine {
        <<interface>>
        +GenerateContent(ctx context.Context, messages []Message, tools []ToolDef, opts *CallOptions) *Response
    }

    class OpenAIEngine {
        -client *openai.Client
        -model string
        +NewOpenAIEngine(baseURL string, apiKey string, model string) OpenAIEngine
        +GenerateContent(ctx context.Context, messages []Message, tools []ToolDef, opts *CallOptions) *Response
        -buildParams(messages []Message, tools []ToolDef, opts *CallOptions) openai.ChatCompletionNewParams
        -generateStreaming(ctx context.Context, params openai.ChatCompletionNewParams, handler StreamHandler) *Response
        -convertMessages(messages []Message) []openai.ChatCompletionMessageParamUnion
        -convertMessage(msg Message) openai.ChatCompletionMessageParamUnion
        -buildAssistantWithToolCalls(msg Message) openai.ChatCompletionMessageParamUnion
        -convertTools(tools []ToolDef) []openai.ChatCompletionToolUnionParam
        -toResponse(completion *openai.ChatCompletion) *Response
    }

    class LoggingEngine {
        -inner Engine
        +GenerateContent(ctx context.Context, messages []Message, tools []ToolDef, opts *CallOptions) *Response
    }

    class Client {
        +Engine Engine
        +Config *config.Config
        +NewClient(config *config.Config) *Client
        +GenerateCompletionWithMemory(ctx context.Context, memory []Message, streamHandler func(context.Context, []byte) error) string
    }

    class CallOptions {
        +MaxTokens int
        +Temperature float64
        +StreamHandler StreamHandler
    }

    class Message {
        +Role Role
        +Content string
        +ToolCalls []ToolCall
        +ToolCallID string
        +ToolName string
        +Reasoning string
    }

    class Role {
        <<enumeration>>
        RoleSystem
        RoleUser
        RoleAssistant
        RoleTool
    }

    class ToolDef {
        +Type string
        +Function FunctionDef
    }

    class FunctionDef {
        +Name string
        +Description string
        +Parameters map~string,any~
    }

    class ToolCall {
        +ID string
        +Type string
        +Function FunctionCall
    }

    class FunctionCall {
        +Name string
        +Arguments string
    }

    class Response {
        +Choices []Choice
    }

    class Choice {
        +Content string
        +ToolCalls []ToolCall
        +Reasoning string
    }

    class CodingAssistant {
        -engine Engine
        -config *config.Config
        -conductor *agents.ConductorAgent
        +Init(engine Engine, workDir string)
        +ProcessCodingTaskWithCallback(req *TaskRequest) string
        +ProcessConversation(req *TaskRequest) string
    }

    class BaseAgent {
        +LLM Engine
        +Publisher *messaging.MessagePublisher
    }

    class ConductorAgent {
        +BaseAgent
        +Run(ctx context.Context, input string, mem *memory.ConversationMemory) string
    }

    class RepoAgent {
        +BaseAgent
    }

    class CodingAgent {
        +BaseAgent
    }

    class ChatAgent {
        +BaseAgent
    }

    class MetaAgent {
        +BaseAgent
        +Run(ctx context.Context, input string) string
    }

    class ExecutorConfig {
        +SystemPrompt string
        +UserInput string
        +Adapters []*tools.Adapter
        +LLM Engine
        +MaxSteps int
        +Publisher *messaging.MessagePublisher
        +AgentName string
        +StopOnFinish bool
        +SystemAsHuman bool
        +OnToolResult func(name string, result string)
    }

    class MicroAgentTool {
        +LLM Engine
        +NewMicroAgentTool(llm Engine) *MicroAgentTool
        +Execute(ctx context.Context, params map~string,any~) any
    }

    class Adapter {
        -name string
        -description string
        -schema map~string,any~
        -fn ToolFunc
        -guard *WorkspaceGuard
        +ToToolDef() ToolDef
    }

    Engine <|.. OpenAIEngine
    Engine <|.. LoggingEngine

    Client o--> Engine
    Client --> CallOptions
    Client --> Message
    Client --> Response

    CodingAssistant o--> Engine
    CodingAssistant --> ConductorAgent

    BaseAgent o--> Engine
    ConductorAgent --> BaseAgent
    RepoAgent --> BaseAgent
    CodingAgent --> BaseAgent
    ChatAgent --> BaseAgent
    MetaAgent --> BaseAgent

    ExecutorConfig o--> Engine

    MicroAgentTool o--> Engine

    Message --> Role
    Message --> ToolCall

    ToolDef --> FunctionDef
    ToolCall --> FunctionCall
    Response --> Choice
    Choice --> ToolCall

    Adapter ..> ToolDef
Loading

File-Level Changes

Change Details Files
Replace langchaingo-based LLM client with a custom Engine abstraction backed by the official openai-go SDK.
  • Introduce Engine, Message, ToolDef, ToolCall, Response, CallOptions, and related types in a new llm engine package.
  • Implement OpenAIEngine using openai-go/v3, including chat completions, tool calls, streaming support, and optional reasoning_content handling.
  • Refactor Client to construct an OpenAIEngine from config, validate API keys, wrap it in a LoggingEngine, and remove Bedrock/OpenAI langchaingo client creation paths.
  • Simplify LLM logging (messages, tools, responses, and errors) and HTTP error extraction to a generic formatter.
internal/llm/engine.go
internal/llm/engine_openai.go
internal/llm/llm.go
Update agents and tools to use the new Engine abstraction and simplified message/tool representations instead of langchaingo llms.
  • Change BaseAgent, ExecutorConfig, ConductorAgent, RepoAgent, CodingAgent, ChatAgent, MetaAgent, CodingAssistant, and MicroAgentTool to depend on llm.Engine and new llm.Message/ToolDef types.
  • Refactor conductor and generic executor loops to build llm.Message slices, pass llm.ToolDef slices, propagate reasoning and tool calls, and handle tool messages via ToolCallID/ToolName instead of llms.ContentPart.
  • Adjust memory conversion helpers to map memory.ChatMessage to llm.Message and convert llm.ToolCall into memory.ToolCallData.
  • Update prompts and comments where necessary to reflect new tool naming conventions and remove unused examples.
internal/agents/types.go
internal/agents/conductor.go
internal/agents/executor.go
internal/agents/meta.go
internal/agents/chat.go
internal/agents/coding.go
internal/agents/repo.go
internal/app/app.go
internal/tools/adapter.go
internal/tools/micro_agent.go
internal/agents/conductor.prompt.md
internal/agents/coding.prompt.md
Adjust tests and tooling adapters to align with the new Engine and message model.
  • Replace mockLLM with mockEngine in conductor tests, including updated GenerateContent signatures and response structures.
  • Update tests that assert on message parts (llms.ContentPart) to assert on roles, content, and ToolCalls/ToolCallID fields in llm.Message.
  • Adjust meta-agent integration tests to use mockEngine and the new tool-call structure.
  • Refactor tools.Adapter to produce llm.ToolDef instead of llms.Tool, including FunctionDef mapping.
internal/agents/conductor_test.go
internal/tools/adapter.go
Simplify module dependencies by removing langchaingo and AWS/Bedrock-related packages and adding openai-go and its helpers.
  • Remove github.com/tmc/langchaingo and all AWS SDK/Bedrock, delve, and other unused indirect dependencies from go.mod.
  • Add github.com/openai/openai-go/v3 and supporting libraries such as tidwall/gjson and google/go-cmp as indirect dependencies.
  • Update go.sum accordingly to reflect the new dependency graph.
go.mod
go.sum

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@iohub iohub merged commit a6e1d80 into main May 4, 2026
1 check passed
Copy link
Copy Markdown

@sourcery-ai sourcery-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.

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

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