openaiexecutor

package
v0.9.22 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 22, 2026 License: Apache-2.0 Imports: 21 Imported by: 0

Documentation

Overview

Package openaiexecutor provides a multi-turn conversation executor for OpenAI-compatible chat completion APIs, including Vertex AI's partner model endpoint.

The executor manages the full conversation lifecycle: sending prompts, processing tool calls, recording metrics, and extracting structured results. It mirrors the claudeexecutor and googleexecutor patterns, enabling the metaagent to route to any model available via the OpenAI chat completions API.

Basic Usage

client := openai.NewClient(
	option.WithBaseURL(vertexBaseURL),
	option.WithHTTPClient(authedClient),
	option.WithAPIKey("placeholder"),
)

prompt := promptbuilder.MustParse("Analyze {{.Input}}")

exec, err := openaiexecutor.New[MyRequest, MyResponse](client, prompt,
	openaiexecutor.WithModel[MyRequest, MyResponse]("deepseek-ai/deepseek-v3.2-maas"),
	openaiexecutor.WithMaxTokens[MyRequest, MyResponse](32768),
	openaiexecutor.WithTemperature[MyRequest, MyResponse](0.2),
)

Options

Thinking Models

Models that return reasoning_content in their responses (e.g. kimi-k2-thinking-maas) are supported. The executor captures reasoning content into the agent trace automatically.

Submit Result Redirect

When a submit_result tool is configured but the model responds with text instead of calling the tool, the executor sends a redirect message asking the model to call submit_result. Unlike the claudeexecutor, the openaiexecutor does not use a forced tool_choice for the redirect — some models (e.g. reasoning models) return 400 on named tool_choice constraints.

Index

Examples

Constants

View Source
const DefaultMaxTurns = 200

DefaultMaxTurns is the default maximum number of conversation turns before aborting.

View Source
const DefaultToolCallConcurrency = 10

DefaultToolCallConcurrency is the default bound on how many of a single turn's tool calls run concurrently. Models routinely emit several independent tool calls in one turn (parallel tool calls); dispatching their handlers concurrently cuts wall-clock latency. Override with WithToolCallConcurrency — a value of 1 restores strictly sequential dispatch.

Variables

This section is empty.

Functions

This section is empty.

Types

type Interface

type Interface[Request promptbuilder.Bindable, Response any] interface {
	// Execute runs the agent conversation with the given request and tools.
	Execute(ctx context.Context, request Request, tools map[string]openaistool.Metadata[Response]) (Response, error)
}

Interface is the public interface for OpenAI-compatible agent execution.

func New

func New[Request promptbuilder.Bindable, Response any](
	client openai.Client,
	prompt *promptbuilder.Prompt,
	opts ...Option[Request, Response],
) (Interface[Request, Response], error)

New creates a new OpenAI-compatible executor.

Example
package main

import (
	"fmt"
	"log"

	"chainguard.dev/driftlessaf/agents/executor/openaiexecutor"
	"chainguard.dev/driftlessaf/agents/promptbuilder"
	"github.com/openai/openai-go"
	"github.com/openai/openai-go/option"
)

type myRequest struct {
	Input string
}

func (r myRequest) Bind(p *promptbuilder.Prompt) (*promptbuilder.Prompt, error) {
	return p.BindJSON("input", r.Input)
}

type myResponse struct {
	Summary string `json:"summary"`
}

func main() {
	prompt := promptbuilder.MustNewPrompt("Summarize: {{input}}")

	client := openai.NewClient(
		option.WithAPIKey("placeholder"),
	)

	exec, err := openaiexecutor.New[myRequest, myResponse](client, prompt,
		openaiexecutor.WithModel[myRequest, myResponse]("google/gemini-2.5-flash"),
		openaiexecutor.WithMaxTokens[myRequest, myResponse](8192),
		openaiexecutor.WithTemperature[myRequest, myResponse](0.1),
	)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("executor created: %v\n", exec != nil)
}
Output:
executor created: true

type Option

type Option[Request promptbuilder.Bindable, Response any] func(*executor[Request, Response]) error

Option is a functional option for configuring the executor.

func WithEffort added in v0.9.17

func WithEffort[Request promptbuilder.Bindable, Response any](level effort.Level) Option[Request, Response]

WithEffort sets the provider-neutral reasoning-effort level, sent as the reasoning_effort request parameter. OpenAI's scale is a subset of the shared one — low, medium, and high — so effort.XHigh and effort.Max clamp to "high". The parameter is only valid for reasoning models; sending it to a non-reasoning model is rejected by the API, so leave it unset for those.

func WithMaxTokens

func WithMaxTokens[Request promptbuilder.Bindable, Response any](tokens int64) Option[Request, Response]

WithMaxTokens sets the maximum completion tokens.

func WithMaxTurns

func WithMaxTurns[Request promptbuilder.Bindable, Response any](turns int) Option[Request, Response]

WithMaxTurns sets the maximum number of conversation turns before aborting.

Example
package main

import (
	"fmt"
	"log"

	"chainguard.dev/driftlessaf/agents/executor/openaiexecutor"
	"chainguard.dev/driftlessaf/agents/promptbuilder"
	"github.com/openai/openai-go"
	"github.com/openai/openai-go/option"
)

type myRequest struct {
	Input string
}

func (r myRequest) Bind(p *promptbuilder.Prompt) (*promptbuilder.Prompt, error) {
	return p.BindJSON("input", r.Input)
}

type myResponse struct {
	Summary string `json:"summary"`
}

func main() {
	prompt := promptbuilder.MustNewPrompt("Process: {{input}}")

	client := openai.NewClient(
		option.WithAPIKey("placeholder"),
	)

	exec, err := openaiexecutor.New[myRequest, myResponse](client, prompt,
		openaiexecutor.WithMaxTurns[myRequest, myResponse](50),
	)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("executor created: %v\n", exec != nil)
}
Output:
executor created: true

func WithModel

func WithModel[Request promptbuilder.Bindable, Response any](model string) Option[Request, Response]

WithModel sets the model name.

Example
package main

import (
	"fmt"
	"log"

	"chainguard.dev/driftlessaf/agents/executor/openaiexecutor"
	"chainguard.dev/driftlessaf/agents/promptbuilder"
	"github.com/openai/openai-go"
	"github.com/openai/openai-go/option"
)

type myRequest struct {
	Input string
}

func (r myRequest) Bind(p *promptbuilder.Prompt) (*promptbuilder.Prompt, error) {
	return p.BindJSON("input", r.Input)
}

type myResponse struct {
	Summary string `json:"summary"`
}

func main() {
	prompt := promptbuilder.MustNewPrompt("Analyze: {{input}}")

	client := openai.NewClient(
		option.WithAPIKey("placeholder"),
	)

	exec, err := openaiexecutor.New[myRequest, myResponse](client, prompt,
		openaiexecutor.WithModel[myRequest, myResponse]("deepseek-ai/deepseek-v3.2-maas"),
	)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("executor created: %v\n", exec != nil)
}
Output:
executor created: true

func WithResourceLabels

func WithResourceLabels[Request promptbuilder.Bindable, Response any](labels map[string]string) Option[Request, Response]

WithResourceLabels sets labels for observability attribution. Automatically includes default labels from environment variables:

  • service_name: from K_SERVICE, falling back to CLOUD_RUN_JOB (defaults to "unknown")
  • product: from CHAINGUARD_PRODUCT (defaults to "unknown")
  • team: from CHAINGUARD_TEAM (defaults to "unknown")

func WithResultValidator added in v0.7.59

func WithResultValidator[Request promptbuilder.Bindable, Response any](v callbacks.ResultValidator[Response]) Option[Request, Response]

WithResultValidator registers a validator that gates the terminal submit tool. When the model calls the submit tool with a payload that parses, every registered validator runs concurrently against the parsed response; any findings reject the submission back to the model as the tool's result — the loop continues until a submission passes — and a validator error aborts the run. Repeatable: each call appends a validator, and their findings are concatenated in registration order. Only meaningful when a submit tool is configured via WithSubmitResultProvider; without one there is nothing to gate.

func WithRetryConfig

func WithRetryConfig[Request promptbuilder.Bindable, Response any](cfg retry.RetryConfig) Option[Request, Response]

WithRetryConfig sets the retry configuration for transient API errors.

func WithSubmitResultProvider

func WithSubmitResultProvider[Request promptbuilder.Bindable, Response any](provider SubmitResultProvider[Response]) Option[Request, Response]

WithSubmitResultProvider registers the submit_result tool.

func WithSystemInstructions

func WithSystemInstructions[Request promptbuilder.Bindable, Response any](prompt *promptbuilder.Prompt) Option[Request, Response]

WithSystemInstructions sets the system prompt.

func WithTemperature

func WithTemperature[Request promptbuilder.Bindable, Response any](temp float64) Option[Request, Response]

WithTemperature sets the sampling temperature (0.0–2.0).

Example
package main

import (
	"fmt"
	"log"

	"chainguard.dev/driftlessaf/agents/executor/openaiexecutor"
	"chainguard.dev/driftlessaf/agents/promptbuilder"
	"github.com/openai/openai-go"
	"github.com/openai/openai-go/option"
)

type myRequest struct {
	Input string
}

func (r myRequest) Bind(p *promptbuilder.Prompt) (*promptbuilder.Prompt, error) {
	return p.BindJSON("input", r.Input)
}

type myResponse struct {
	Summary string `json:"summary"`
}

func main() {
	prompt := promptbuilder.MustNewPrompt("Summarize: {{input}}")

	client := openai.NewClient(
		option.WithAPIKey("placeholder"),
	)

	exec, err := openaiexecutor.New[myRequest, myResponse](client, prompt,
		openaiexecutor.WithTemperature[myRequest, myResponse](0.5),
	)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("executor created: %v\n", exec != nil)
}
Output:
executor created: true

func WithToolCallConcurrency added in v0.7.10

func WithToolCallConcurrency[Request promptbuilder.Bindable, Response any](n int) Option[Request, Response]

WithToolCallConcurrency bounds how many of a single turn's tool calls run concurrently when the model emits more than one in a turn (parallel tool calls). Defaults to DefaultToolCallConcurrency.

Tool result messages are always appended in the order the model emitted the calls. A value of 1 forces strictly sequential dispatch. Set it to 1 for agents whose tool handlers mutate shared state (a worktree, a cache) without their own synchronization; concurrent dispatch is otherwise safe because handlers share only the trace, which is concurrency-safe.

Note: some OpenAI-compatible models disable parallel tool calls when strict structured output is in force, in which case the model emits one tool call per turn and this option has no effect.

func WithUserPromptSuffix added in v0.7.56

func WithUserPromptSuffix[Request promptbuilder.Bindable, Response any](suffix *promptbuilder.Prompt) Option[Request, Response]

WithUserPromptSuffix appends a static, operator-authored prompt to the end of the built user prompt, separated by a blank line. It is the OpenAI-compatible counterpart of the Claude executor's user-prompt-suffix option: agents that share one large payload but vary a small trailing instruction (for example multi-pass reviewers examining one changeset through different lenses) keep the payload in the main prompt and the varying instruction in the suffix. The OpenAI-compatible API has no per-block prompt-cache semantics, so the suffix is simply concatenated and there is no cache-shaping side effect. The suffix must be fully bound by the caller; the request is never bound into it.

type SubmitResultProvider

type SubmitResultProvider[Response any] func() (openaistool.SubmitMetadata[Response], error)

SubmitResultProvider constructs tool metadata for submit_result.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL