Agent SDK Go

repository·master·Indexed 41 days ago

https://github.com/pontus-espe/agent-sdk-go

An open-source framework for building, deploying, and scaling AI agents in Go. It supports multiple LLM providers including OpenAI, Anthropic, and LM Studio, and features tool calling, structured outputs, and advanced multi-agent orchestration patterns such as bidirectional flows and orchestrator-led workflows.

Tokens
21.4K
Snippets
69
Records
94
Agent score
51%

What's inside Agent SDK Go

  1. Overview of Agent SDK Go features

    master

    The Agent SDK Go is designed for building sophisticated AI agents with the following capabilities:

    • Multiple LLM providers: Support for OpenAI, Azure OpenAI, Gemini, Anthropic, Amazon Bedrock, and LM Studio, which can be mixed within a single workflow.
    • Tool integration: Ability to call Go functions directly from the model.
    • MCP support: Attach tools from any Model Context Protocol (MCP) server via stdio or HTTP.
    • Agent handoffs: Route work to specialized agents, supporting bidirectional delegation and task tracking.
    • Structured output: Parse model responses directly into Go structs.
    • Streaming: Stream content, tool calls, and handoffs in real-time.
    • Pluggable tracing: Use the default file tracer or implement custom tracers for observability.
    • Workflow state management: Persist and restore state between agent runs.
  2. Available SDK Examples

    master

    The repository provides several specialized examples covering different providers, workflows, and patterns:

    ExampleDescription
    multi_agent_exampleSpecialized agents collaborating via local LLM (LM Studio)
    openai_exampleOpenAI provider with function calling
    openai_multi_agent_exampleMulti-agent functionality with OpenAI, tool calling, and streaming
    anthropic_exampleAnthropic Claude API with tool calling
    anthropic_handoff_exampleAgent handoffs using Anthropic Claude
    bidirectional_flow_exampleBidirectional communication with task delegation and return handoffs
    typescript_code_review_exampleSpecialized code review agents using bidirectional handoffs
    azure_openai_exampleAgent running against an Azure OpenAI deployment
    gemini_exampleGemini via OpenAI compatible endpoint with tool calling
    bedrock_exampleAmazon Bedrock using the Converse API
    multi_provider_exampleWorkflow where agents run on different LLM providers
    mcp_exampleAgent using MCP server tools over stdio or HTTP
    custom_tracer_exampleReplacing the default file tracer with a custom implementation
    openai_advanced_workflowAdvanced workflow with state persistence between executions
  3. How Runners execute Agents

    master

    A Runner is responsible for executing the agent loop, managing tool calls, and handling handoffs. You can set a default provider for the runner, or pass specific options like MaxTurns to limit the conversation length during execution.

    // Create a runner
    runner := runner.NewRunner()
    runner.WithDefaultProvider(provider)
    
    // Run the agent
    result, err := runner.RunSync(agent, &runner.RunOptions{
        Input: "Hello, world!",
        MaxTurns: 10, // Optional: limit the number of turns
    })
  4. How providers work in agent-sdk-go

    master
    A provider is an abstraction that resolves model names into actual models. Every provider implements the model.Provider interface, allowing agents and runners to treat different LLM backends (OpenAI, Anthropic, Bedrock, etc.) uniformly. This enables multi-provider workflows where different agents in a single process can use different underlying models.
  5. How the Model Context Protocol (MCP) client works

    master

    The pkg/mcp package provides an MCP client that allows you to connect to MCP servers and expose their tools to an agent. Tools exposed by an MCP server implement the tool.Tool interface, meaning they can be attached to an agent just like locally defined tools.

    The client handles several lifecycle and protocol tasks automatically:

    • Handshake: Performs the initialize and notifications/initialized handshake before the first call.
    • Tool Listing: Lists tools using tools/list and handles pagination cursors.
    • Tool Execution: Calls tools via tools/call. It returns text content directly, while non-text blocks are returned as JSON.
    • Error Handling: Maps server-side tool errors (isError) and JSON-RPC errors into standard Go errors.
  6. How model resolution works in multi-provider setups

    master

    The runner resolves which model to use for every turn of an execution. The resolution logic follows this priority order:

    1. Agent-specific Provider: If the agent has its own provider configured via WithModelProvider (or the alias SetModelProvider), that provider is used to resolve the model name. Note that RunConfig.Model overrides are not applied to these agents, preventing model name mismatches between providers.
    2. Runner/Run Overrides: If no agent-specific provider is set, the runner checks RunConfig.Model. If set, it uses RunConfig.ModelProvider or the runner's default provider to resolve it.
    3. Direct Model Instance: If the agent's model is already a model.Model instance, it is used directly.
    4. Provider Default: If an agent has a provider assigned but no specific model name is set, the provider's default model is used.

    This resolution occurs per turn, ensuring consistency across Run, RunSync, and RunStreaming, even after handoffs occur.

  7. Implement Bidirectional Agent Flow

    master

    Bidirectional flow allows agents to delegate tasks to other agents and receive results back, enabling complex workflows with context management.

    To enable this, you must set up mutual handoffs between the orchestrator and the worker agents.

    Core Components:

    • TaskID: Unique identifier for tracking tasks across agents.
    • ReturnToAgent: Specifies which agent to return to after task completion.
    • IsTaskComplete: Flag indicating whether the task is complete.

    Example setup:

    1. Create an orchestratorAgent.
    2. Create a workerAgent.
    3. Call orchestratorAgent.WithHandoffs(workerAgent).
    4. Call workerAgent.WithHandoffs(orchestratorAgent) to allow the worker to return control to the orchestrator.
    // Create specialized agents
    orchestratorAgent := agent.NewAgent("Orchestrator")
    orchestratorAgent.SetModelProvider(provider)
    orchestratorAgent.WithModel("gpt-4")
    orchestratorstrAgent.SetSystemInstructions("You coordinate tasks and analyze results.")
    
    workerAgent := agent.NewAgent("Worker")
    workerAgent.SetModelProvider(provider)
    workerAgent.WithModel("gpt-3.5-turbo")
    workerAgent.SetSystemInstructions("You process data and return results.")
    workerAgent.WithTools(processingTool)
    
    // Set up bidirectional handoffs
    orchestratorAgent.WithHandoffs(workerAgent)
    workerAgent.WithHandoffs(orchestratorAgent)  // Allow worker to return to orchestrator
    
    // Run the orchestrator agent
    result, err := runner.RunSync(orchestratorAgent, &runner.RunOptions{
        Input: "Analyze this data: [complex data]",
        MaxTurns: 10,
    })
  8. How MCP tool name sanitization works

    master

    MCP servers often use namespaced tool names (e.g., files/read_file). Because many Model APIs reject forward slashes in tool names, the agent-sdk-go client sanitizes them for the model.

    • The model sees the sanitized name (e.g., files_read_file).
    • The client uses the original name (e.g., files/read_file) when making calls back to the server.

    You can inspect both names using the following methods on the tool object:

    tool.GetName()    // files_read_file - what the model sees
    tool.RemoteName() // files/read_file - what the server declared
  9. Configure tracer scope: Per run, Per agent, or Global

    master

    You can plug in a custom tracer at three different levels of granularity:

    1. Per run: Pass a Tracer directly into TracingConfig. The runner flushes the tracer at the end of the run but does not close it (the application owns the lifecycle).
    2. Per run, per agent: Provide a TracerFactory function in TracingConfig. The runner will close the tracers created by this factory when the run finishes.
    3. Globally: Use tracing.SetTracerFactory to set a factory for all runs. Passing nil to this function restores the default file tracer.
    // Per run
    RunConfig: &runner.RunConfig{
    	TracingConfig: &runner.TracingConfig{
    		Tracer: myTracer,
    	},
    }
    
    // Per run, per agent
    RunConfig: &runner.RunConfig{
    	TracingConfig: &runner.TracingConfig{
    		TracerFactory: func(agentName string) (tracing.Tracer, error) {
    			return newTracerFor(agentName)
    		},
    	},
    }
    
    // Globally
    tracing.SetTracerFactory(func(agentName string) (tracing.Tracer, error) {
    	return myTracerFor(agentName)
    })
  10. Create Multi-Agent Workflows with Handoffs

    master

    You can create specialized agents that collaborate by using WithHandoffs. When an agent is added via WithHandoffs, it is exposed to the model as a tool named handoff_to_<agent name>.

    Key Details:

    • Name Sanitization: Agent names are automatically sanitized to match Model API requirements (regex ^[a-zA-Z0-9_-]+$). For example, Weather Agent becomes handoff_to_Weather_Agent.
    • Validation: Use agent.ValidateName to check names manually.
    • Result Tracking: After a handoff sequence, result.LastAgent identifies the agent that produced the final output.

    To implement this, define specialized agents with their own tools and instructions, then define a coordinator (frontend) agent that includes the specialized agents in its handoffs.

    // Create specialized agents
    mathAgent := agent.NewAgent("Math Agent")
    mathAgent.SetModelProvider(provider)
    mathAgent.WithModel("gemma-3-4b-it")
    mathAgent.SetSystemInstructions("You are a specialized math agent.")
    mathAgent.WithTools(calculatorTool)
    
    weatherAgent := agent.NewAgent("Weather Agent")
    weatherAgent.SetModelProvider(provider)
    weatherAgent.WithModel("gemma-3-4b-it")
    weatherAgent.SetSystemInstructions("You provide weather information.")
    weatherAgent.WithTools(weatherTool)
    
    // Create a frontend agent that coordinates tasks
    frontendAgent := agent.NewAgent("Frontend Agent")
    frontendAgent.SetModelProvider(provider)
    frontendAgent.WithModel("gemma-3-4b-it")
    frontendAgent.SetSystemInstructions(`You coordinate requests by delegating to specialized agents. 
    For math calculations, delegate to the Math Agent. 
    For weather information, delegate to the Weather Agent. `)
    frontendAgent.WithHandoffs(mathAgent, weatherAgent)
    
    // Run the frontend agent
    result, err := runner.RunSync(frontendAgent, &runner.RunOptions{
        Input: "What is 42 divided by 6 and what's the weather in Paris?",
        MaxTurns: 20,
    })
  11. Managing Model Providers

    master

    The SDK supports multiple providers (OpenAI, Anthropic, Gemini, Bedrock, LM Studio). You can manage them in two ways:

    1. Runner-level: Set a default provider on the Runner using WithDefaultProvider(provider), which all agents used by that runner will share.
    2. Agent-level: Override the provider for a specific agent using agent.WithModelProvider(provider) and specify a model with agent.WithModel(modelName).
    // Set a provider as the default provider for every agent
    runner := runner.NewRunner()
    runner.WithDefaultProvider(openaiProvider)
    
    // Or give a single agent its own provider
    researcher.WithModelProvider(anthropicProvider)
    researcher.WithModel("claude-sonnet-4-20250514")