Skip to content

Latest commit

 

History

History
95 lines (80 loc) · 14 KB

File metadata and controls

95 lines (80 loc) · 14 KB

Differences from the Python SDK

agents-go tracks openai-agents-python v0.17.7: the run loop, item model, defaults (max turns 10, strict schemas on, tool errors fed back to the model, tool_choice reset after tool use) and most names map one-to-one. This page lists everything that intentionally differs — first how the same concepts look in Go, then what each side has that the other lacks.

API mapping

Python Go
Agent(name=..., instructions=...) &agents.Agent{Name: ..., Instructions: agents.StaticInstructions(...)}
instructions= callable agents.InstructionsFunc(func(ctx, rc, agent) (string, error))
Runner.run / Runner.run_sync agents.Run(ctx, agent, input, opts) (Go has no sync/async split)
Runner.run_streamed agents.RunStreamed(ctx, agent, input, opts)
run_config / Runner.run(...) kwargs agents.RunOptions{...}
@function_tool decorator agents.NewFunctionTool[Args, Result](name, desc, fn)
pydantic argument model + docstring argument struct + json:"..."/jsonschema:"..." tags
output_type=MyModel OutputType: agents.OutputType[MyModel]()
ToolOutputText / ToolOutputImage / ToolOutputFileContent agents.ToolOutputText / ToolOutputImage / ToolOutputFile (return one, or []agents.ToolOutputContent, from a function tool)
result.final_output_as(T) agents.FinalOutputAs[T](res)
handoff(agent) / agent.handoffs agents.HandoffTo(agent) / Agent.Handoffs
agent.as_tool(...) agent.AsTool(agents.AgentToolConfig{...})
@input_guardrail / @output_guardrail agents.InputGuardrail{Name, Run} / agents.OutputGuardrail{Name, Run} struct values, or agents.NewInputGuardrail(name, fn) / agents.NewOutputGuardrail(name, fn) simplified constructors
RunContextWrapper[T] *agents.RunContext with Context any (type-assert back)
SQLiteSession memory.FileSession (JSONL file; same Session interface)
reset_tool_choice=True (default) DisableToolChoiceReset (zero value = Python's default behavior)
max_turns=10 RunOptions.MaxTurns (0 means the same default of 10)
exceptions (MaxTurnsExceeded, …) error values (*MaxTurnsError, …) matched with errors.As
RunErrorDetails on exceptions AgentsError.Details, reachable via agents.AsAgentsError(err)
set_default_openai_key / globals none — pass openai.NewProvider(...) explicitly in RunOptions
custom_data_extractor= (function tools) FunctionTool.CustomDataExtractor (SDK-only tool output metadata; tools)
RunConfig.tool_execution.pre_approval_tool_input_guardrails RunOptions.PreApprovalToolInputGuardrails
resume a paused run (state as input to Runner.run / Runner.run_streamed) agents.ResumeRun(ctx, state, opts) / agents.ResumeRunStreamed(ctx, state, opts)

Language-level differences

Generics and reflection instead of pydantic. Tool schemas come from struct reflection at construction time (NewFunctionTool[A, R]), structured outputs from OutputType[T](). Validation on the way back in uses encoding/json plus a root-level required-key check — looser than pydantic's full validation (nested required fields are not enforced). Two schema-shape limits surface at construction time as explicit errors rather than as API 400s: any/interface{} fields (no strict-mode schema exists for "anything") and recursive types (pydantic emits $defs/$ref for these; the Go reflector rejects cycles).

Two contexts instead of one wrapper. Python's RunContextWrapper[T] carries both your data and run state. Go splits them: context.Context handles cancellation/deadlines (and is honored mid-run, mid-stream and inside tools), while RunContext.Context any carries your data without generics on every type.

Errors instead of exceptions. Every failure is a returned error. SDK error types embed AgentsError; errors.As matches concrete types even through %w wrapping, and agents.AsAgentsError extracts the embedded base (with RunErrorDetails) generically.

Concurrency is explicit. Tools requested in one turn run concurrently via goroutines (Python interleaves on the event loop). Hooks and shared context values must be goroutine-safe. Streaming uses iter.Seq2 (for event, err := range sr.Events()) instead of async for, and there is no run_sync because Run is already synchronous.

Sealed interfaces instead of unions. Tool, StreamEvent, RunItem and ToolUseBehavior are closed interfaces you type-switch on, mirroring Python's Union types.

Behavioral differences

Area Python v0.17.7 Go
Tool errors failure_error_function default feeds the error to the model Same default (DefaultToolErrorFunction); set the field to nil for fatal
Tool timeout timeout_seconds + timeout_behavior (error_as_result / raise_exception) FunctionTool.Timeout*ToolTimeoutError, fed back via FailureErrorFunction when set (≈ error_as_result), else fatal (≈ raise_exception). Enforced by the runner: the call returns at the deadline even if the tool ignores its context (the tool goroutine finishes in the background, its late result discarded)
Tool panics tool exceptions flow into failure_error_function same: a panicking tool (or guardrail) is recovered and converted to an error instead of crashing the process
HITL interruption scope tools not needing approval still execute in the interrupted turn; only approval-gated calls pause all tool calls in the turn wait until ResumeRun when any of them needs approval (keeps RunState free of partial results; side effect: "safe" tools run with post-approval context)
Model refusal refusal text surfaces as plain content run fails with *ModelRefusalError carrying the refusal
Handoff input filter receives input_history / pre_handoff_items / new_items separately receives one flattened InputHistory; the session always keeps the unfiltered conversation. NestHandoffHistory ports nest_handoff_history (fold + flatten) on top of this
HITL state RunState JSON (Python format) RunState JSON round-trips Go↔Go only, and rebuilding needs an agent-name registry (Go functions don't serialize). The state carries max_turns so ResumeRun continues under the original budget; resumed NewItems deserialize as raw items (ItemType() survives, concrete type assertions don't)
Input guardrail timing parallel with the first model call same for Run; RunStreamed runs them synchronously before the first call
Streamed text items message_output_created fires once per completed message same (use raw delta events for token-level UI)
Session backends SQLite / SQLAlchemy / Redis / encrypted / OpenAI Conversations / compaction InMemorySession + FileSession (JSONL) in core; sessions module adds SQLite/PostgreSQL via bun; openai.ConversationsSession (server-side via the Conversations API); openai.CompactionSession (responses.compact decorator, attempted once per run vs Python's per turn); implement Session for anything else
Tracing backend OpenAI traces dashboard by default generic tracer → processor → exporter pipeline (console/HTTP/custom); not the OpenAI dashboard wire format. Traces export at start, spans at finish
Sensitive trace data RunConfig.trace_include_sensitive_data (env OPENAI_AGENTS_TRACE_INCLUDE_SENSITIVE_DATA) gates span content RunOptions.TraceIncludeSensitiveData *bool, same env var and default (true); gates the generation span's request/response data keys (model, system_instructions, input, tools, model_settings, handoffs, output_schema, output, …) and the function span's input/output
Compaction tracing not traced Go-only: the runner wraps RunCompaction in a "compaction" span, opened lazily via CompactionArgs.StartSpan so no-op passes emit nothing; sessions annotate before/after item counts
Compaction failure raises, failing the run best-effort: the run's items are already saved and the final output produced, so the error is recorded on the compaction span and the run still succeeds
MCP tool errors AgentsException aborts the run fed back to the model as tool output by default (fast-fail is opt-in via the tool's error function); strict-schema normalization failures silently fall back to non-strict for that tool (Python logs). Duplicate tool names across servers/local tools are a UserError on both sides
Server-side conversation state previous_response_id / conversation_id parameters RunOptions.UsePreviousResponseID and RunOptions.ConversationID (both send only deltas; neither combines with a local Session). openai.ConversationsSession also persists history server-side via the Conversations API
Stored prompts Agent(prompt=Prompt(id, version, variables)) / DynamicPromptFunction Agent.Prompt = StaticPrompt(agents.Prompt{...}) or PromptFunc(...) (OpenAI Responses backend only)
Usage of nested as_tool runs separate from parent same (separate), but nested spans join the parent trace

Not implemented in Go

  • Hosted OpenAI tools: web search, file search, code interpreter, computer use, image generation, local_shell, apply_patch — deliberately not modeled; tools are provider-agnostic function tools, and a non-standard tool_choice is sent as a function name. (For file editing, Go provides apply_patch as a sandbox-backed function tool — Codex-style patches applied through the Sandbox abstraction, not the hosted OpenAI apply_patch; tools)
  • Chat Completions model layer — only the Responses API (use a Responses-compatible gateway, or implement Model)
  • LiteLLM adapter — but native multi-provider routing, retry and fallback are supported via Model decorators (models)
  • Redis / encrypted / SQLAlchemy session backends — only SQLite & PostgreSQL are provided (sessions module); implement Session for others. (OpenAIConversationsSession and OpenAIResponsesCompactionSession are ported, as openai.ConversationsSession and openai.CompactionSession.)
  • Realtime and voice agents
  • REPL utility (run_demo_loop) and visualization (Graphviz)
  • MCP-level custom_data_extractor — Python's MCP servers (and hosted tools) accept their own custom-data extractors with access to the raw CallToolResult; in Go only FunctionTool.CustomDataExtractor exists, and MCP-bridged tools don't expose the raw result to it

Go-only additions

  • Self-hosted sandboxes: run model-written code in your own infrastructure — locked-down Docker containers (sandbox/docker) or a remote host over SSH (sandbox/ssh) — exposed via sandbox.CodeTool. Python's sandboxes target hosted providers (e2b / modal / blaxel) rather than self-hosted backends
  • Hooks can veto: any hook returning an error aborts the run (Python hooks are observe-only)
  • FileSession: zero-dependency JSONL persistence with per-path locking and atomic rewrites
  • Skills (skills module): the open Agent Skills SKILL.md format implemented on Instructions + a function tool — provider-agnostic and sandbox-free, unlike Python's sandbox-capability skills
  • Session forking (ForkSession / ForkSessionAt / IndexOfItemID): clone a conversation or branch at a specific point — works across any Session backend pair. Python's closest is AdvancedSQLiteSession's branch support, which is tied to that one backend
  • ItemsReplacer / ReplaceSessionItems: optional Session capability for atomically swapping the whole history, used by compaction and summarization so a failure mid-rewrite cannot leave the session empty; all built-in backends implement it
  • SlidingWindowSession: provider-agnostic history summarization (any Model, pair-aware split points) as an alternative to the OpenAI-only responses.compact decorator
  • Provider-level decorators: NewRetryProvider(inner, policy) and NewFallbackProvider(primary, fallbacks...) wrap a ModelProvider so every Model it produces automatically retries or falls back — the provider-level counterparts of NewRetryModel / NewFallbackModel, useful when you know the policy at configuration time but not the model name. Fallback error classification is configurable via WithShouldFallback (default: everything except context cancellation advances the chain)
  • NewDynamicOutputSchema: builds an OutputSchema from a map[string]any JSON Schema at runtime, complementing the compile-time OutputType[T]() for config-driven agents
  • WrapInstructions: decorates an Instructions value with a prefix and/or suffix applied at resolution time, eliminating the GetInstructions(ctx, nil, nil) + concatenate + re-wrap pattern
  • CompositeRunHooks: combines multiple RunHooks into one, dispatching each callback to every hook in order with first-error short-circuit
  • RetryPolicy JSON round-trip: RetryPolicy implements json.Unmarshaler / json.Marshaler with millisecond-based fields (base_delay_ms, max_delay_ms), making it directly usable with json.Unmarshal from configuration stores
  • Simplified guardrail constructors: NewInputGuardrail(name, fn) and NewOutputGuardrail(name, fn) accept a callback that receives only the input/output, skipping ctx/rc/agent when you don't need them
  • Session item helpers: MarshalItems / UnmarshalItems handle the common JSON ↔ []TResponseInputItem round-trip (including nil/empty/"null" edge cases) so DB session backends don't rewrite it
  • NewRawFunctionTool: builds a FunctionTool from a pre-built JSON Schema map[string]any and a raw-JSON callback, for tools whose schema is loaded at runtime rather than reflected from a Go type
  • Enum parse helpers: ParseToolNotFoundBehavior(string) / ToolNotFoundBehavior.String() and ParseToolUseBehavior(string) convert between configuration strings and SDK enum types