Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import (

func main() {
model, _ := openai.New("gpt-4o")
agent := agentic.NewAgent[any]("You are a helpful assistant.", model)
agent := agentic.NewAgent[agentic.NoDeps]("You are a helpful assistant.", model)

result, err := agent.Run(context.Background(), "Hello!", nil)
if err != nil {
Expand Down Expand Up @@ -88,7 +88,7 @@ type Summary struct {
Points []string `json:"points" validate:"required,min=1"`
}

agent := agentic.NewTypedAgent[any, Summary](
agent := agentic.NewTypedAgent[agentic.NoDeps, Summary](
"Summarize the input as structured data.",
model,
"Return a structured summary.",
Expand Down Expand Up @@ -158,12 +158,12 @@ for event := range stream.Events {
Manage context window with built-in processors:

```go
agent := agentic.NewAgent[any]("You are helpful.", model,
agentic.WithHistoryProcessor[any](agentic.TruncateHistory(20)),
agent := agentic.NewAgent[agentic.NoDeps]("You are helpful.", model,
agentic.WithHistoryProcessor[agentic.NoDeps](agentic.TruncateHistory(20)),
)

// Or chain multiple processors
agentic.WithHistoryProcessor[any](agentic.ChainProcessors(
agentic.WithHistoryProcessor[agentic.NoDeps](agentic.ChainProcessors(
agentic.SlidingWindowHistory(4000, tokenCounter),
agentic.TruncateHistory(50),
))
Expand Down
6 changes: 3 additions & 3 deletions agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import (
)

// Agent orchestrates LLM interactions with tools and structured output.
// DepsT is the type of user-provided dependencies accessible in tool handlers.
// Use any as DepsT if you don't need dependencies.
// DepsT is the type of user-provided dependencies accessible in tool handlers
// and dynamic prompts. Use [NoDeps] when you do not inject dependencies.
type Agent[DepsT any] struct {
model Model
systemPrompt string
Expand All @@ -23,7 +23,7 @@ type Agent[DepsT any] struct {
//
// Example:
//
// agent := agentic.NewAgent[any]("You are a helpful assistant", model)
// agent := agentic.NewAgent[NoDeps]("You are a helpful assistant", model)
func NewAgent[DepsT any](systemPrompt string, model Model, opts ...AgentOption[DepsT]) *Agent[DepsT] {
config := defaultAgentConfig[DepsT]()
for _, opt := range opts {
Expand Down
6 changes: 3 additions & 3 deletions agent_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func TestNewAgentDynamicRegistersHandoffs(t *testing.T) {

func TestAgentRunAdditionalErrorPaths(t *testing.T) {
t.Run("no choices in response", func(t *testing.T) {
agent := NewAgent[any]("system", &testutil.StubModel{
agent := NewAgent[NoDeps]("system", &testutil.StubModel{
NameValue: "empty-model",
Response: &ChatResponse{},
})
Expand All @@ -39,7 +39,7 @@ func TestAgentRunAdditionalErrorPaths(t *testing.T) {
})

t.Run("tool call without registered tools", func(t *testing.T) {
agent := NewAgent[any]("system", &testutil.StubModel{
agent := NewAgent[NoDeps]("system", &testutil.StubModel{
NameValue: "tool-model",
Response: &ChatResponse{
Choices: []Choice{{
Expand All @@ -60,7 +60,7 @@ func TestAgentRunAdditionalErrorPaths(t *testing.T) {
})

t.Run("model error is wrapped", func(t *testing.T) {
agent := NewAgent[any]("system", &testutil.StubModel{
agent := NewAgent[NoDeps]("system", &testutil.StubModel{
NameValue: "error-model",
Err: errors.New("boom"),
})
Expand Down
10 changes: 5 additions & 5 deletions agent_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func WithOutputValidator[DepsT any](v OutputValidator[DepsT]) AgentOption[DepsT]
//
// Example:
//
// agentic.WithOutputValidatorFunc[any](func(ctx agentic.RunContext[any], output string) error {
// agentic.WithOutputValidatorFunc[NoDeps](func(ctx agentic.RunContext[NoDeps], output string) error {
// if !strings.Contains(output, "answer") {
// return agentic.NewValidationError("Response must contain the word 'answer'")
// }
Expand All @@ -135,8 +135,8 @@ func WithMaxValidationRetries[DepsT any](n int) AgentOption[DepsT] {
//
// Example:
//
// agent := agentic.NewAgent[any]("prompt", model,
// agentic.WithThinking[any](agentic.ThinkingConfig{
// agent := agentic.NewAgent[NoDeps]("prompt", model,
// agentic.WithThinking[NoDeps](agentic.ThinkingConfig{
// Enabled: true,
// BudgetTokens: 10000,
// }),
Expand All @@ -153,8 +153,8 @@ func WithThinking[DepsT any](config ThinkingConfig) AgentOption[DepsT] {
//
// Example:
//
// agent := agentic.NewAgent[any]("prompt", model,
// agentic.WithUsageLimits[any](agentic.UsageLimits{
// agent := agentic.NewAgent[NoDeps]("prompt", model,
// agentic.WithUsageLimits[NoDeps](agentic.UsageLimits{
// MaxRequests: intPtr(10),
// MaxTotalTokens: intPtr(50000),
// }),
Expand Down
22 changes: 11 additions & 11 deletions agent_options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,32 @@ import (
)

func TestWithTemperature(t *testing.T) {
cfg := defaultAgentConfig[any]()
WithTemperature[any](0.7)(&cfg)
cfg := defaultAgentConfig[NoDeps]()
WithTemperature[NoDeps](0.7)(&cfg)
if cfg.temperature == nil || *cfg.temperature != 0.7 {
t.Errorf("expected temperature 0.7, got %v", cfg.temperature)
}
}

func TestWithMaxTokens(t *testing.T) {
cfg := defaultAgentConfig[any]()
WithMaxTokens[any](500)(&cfg)
cfg := defaultAgentConfig[NoDeps]()
WithMaxTokens[NoDeps](500)(&cfg)
if cfg.maxTokens == nil || *cfg.maxTokens != 500 {
t.Errorf("expected max tokens 500, got %v", cfg.maxTokens)
}
}

func TestWithTopP(t *testing.T) {
cfg := defaultAgentConfig[any]()
WithTopP[any](0.9)(&cfg)
cfg := defaultAgentConfig[NoDeps]()
WithTopP[NoDeps](0.9)(&cfg)
if cfg.topP == nil || *cfg.topP != 0.9 {
t.Errorf("expected topP 0.9, got %v", cfg.topP)
}
}

func TestWithToolChoice(t *testing.T) {
cfg := defaultAgentConfig[any]()
WithToolChoice[any](ToolChoiceRequired)(&cfg)
cfg := defaultAgentConfig[NoDeps]()
WithToolChoice[NoDeps](ToolChoiceRequired)(&cfg)
if cfg.toolChoice == nil || *cfg.toolChoice != ToolChoiceRequired {
t.Errorf("expected tool choice required, got %v", cfg.toolChoice)
}
Expand Down Expand Up @@ -74,7 +74,7 @@ func TestWithRunMaxIterations(t *testing.T) {
}

func TestDefaultAgentConfig(t *testing.T) {
cfg := defaultAgentConfig[any]()
cfg := defaultAgentConfig[NoDeps]()
if cfg.maxIterations != 10 {
t.Errorf("expected default maxIterations 10, got %d", cfg.maxIterations)
}
Expand All @@ -88,8 +88,8 @@ func TestHistoryProcessorOptions(t *testing.T) {
return append(messages, NewTextMessage(RoleAssistant, "processed")), nil
})

cfg := defaultAgentConfig[any]()
WithHistoryProcessor[any](proc)(&cfg)
cfg := defaultAgentConfig[NoDeps]()
WithHistoryProcessor[NoDeps](proc)(&cfg)
if cfg.historyProcessor == nil {
t.Fatal("expected agent history processor to be set")
}
Expand Down
40 changes: 20 additions & 20 deletions agent_runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (

func TestAgentSetRegistry(t *testing.T) {
model := test.NewTestModel(test.ModelResponse{Text: "ok"})
agent := agentic.NewAgent[any]("test", model)
agent := agentic.NewAgent[agentic.NoDeps]("test", model)

reg := agentic.NewRegistry()
agent.SetRegistry(reg)
Expand All @@ -30,8 +30,8 @@ func TestAgentSetRegistry(t *testing.T) {
func TestAgentDynamicPromptError(t *testing.T) {
model := test.NewTestModel(test.ModelResponse{Text: "ok"})

agent := agentic.NewAgentDynamic[any](
func(ctx agentic.RunContext[any]) (string, error) {
agent := agentic.NewAgentDynamic[agentic.NoDeps](
func(ctx agentic.RunContext[agentic.NoDeps]) (string, error) {
return "", fmt.Errorf("prompt generation failed")
},
model,
Expand All @@ -56,7 +56,7 @@ func TestAgentNoToolsButToolCallRequested(t *testing.T) {
},
)

agent := agentic.NewAgent[any]("test", model)
agent := agentic.NewAgent[agentic.NoDeps]("test", model)

_, err := agent.Run(context.Background(), "go", nil)
if err == nil {
Expand All @@ -66,7 +66,7 @@ func TestAgentNoToolsButToolCallRequested(t *testing.T) {

func TestAgentWithRunOptions(t *testing.T) {
model := test.NewTestModel(test.ModelResponse{Text: "ok"})
agent := agentic.NewAgent[any]("test", model)
agent := agentic.NewAgent[agentic.NoDeps]("test", model)

result, err := agent.Run(context.Background(), "hello", nil,
agentic.WithRunTemperature(0.5),
Expand All @@ -84,7 +84,7 @@ func TestAgentWithRunOptions(t *testing.T) {

func TestAgentWithMessagesContainingSystemPrompt(t *testing.T) {
model := test.NewTestModel(test.ModelResponse{Text: "ok"})
agent := agentic.NewAgent[any]("test", model)
agent := agentic.NewAgent[agentic.NoDeps]("test", model)

// If messages already contain a system prompt, don't add another
result, err := agent.Run(context.Background(), "hello", nil,
Expand Down Expand Up @@ -115,7 +115,7 @@ func TestAgentWithMessagesContainingSystemPrompt(t *testing.T) {

func TestAgentModelRequestError(t *testing.T) {
model := &testutil.StubModel{NameValue: "error-model", Err: fmt.Errorf("api error")}
agent := agentic.NewAgent[any]("test", model)
agent := agentic.NewAgent[agentic.NoDeps]("test", model)

_, err := agent.Run(context.Background(), "hello", nil)
if err == nil {
Expand All @@ -131,7 +131,7 @@ func TestAgentNoChoicesInResponse(t *testing.T) {
NameValue: "empty-model",
Response: &agentic.ChatResponse{Choices: nil},
}
agent := agentic.NewAgent[any]("test", model)
agent := agentic.NewAgent[agentic.NoDeps]("test", model)

_, err := agent.Run(context.Background(), "hello", nil)
if err == nil {
Expand All @@ -144,11 +144,11 @@ func TestAgentNoChoicesInResponse(t *testing.T) {

func TestAgentWithTemperatureAndTopP(t *testing.T) {
model := test.NewTestModel(test.ModelResponse{Text: "ok"})
agent := agentic.NewAgent[any]("test", model,
agentic.WithTemperature[any](0.8),
agentic.WithMaxTokens[any](500),
agentic.WithTopP[any](0.9),
agentic.WithToolChoice[any](agentic.ToolChoiceAuto),
agent := agentic.NewAgent[agentic.NoDeps]("test", model,
agentic.WithTemperature[agentic.NoDeps](0.8),
agentic.WithMaxTokens[agentic.NoDeps](500),
agentic.WithTopP[agentic.NoDeps](0.9),
agentic.WithToolChoice[agentic.NoDeps](agentic.ToolChoiceAuto),
)

result, err := agent.Run(context.Background(), "hello", nil)
Expand All @@ -172,7 +172,7 @@ func TestAgentAddToolPanicsOnDuplicate(t *testing.T) {
type Out struct{}
tool, handler := agentic.MustToolPlain("dup", "duplicate", func(in In) (Out, error) { return Out{}, nil })

agent := agentic.NewAgent[any]("test", model)
agent := agentic.NewAgent[agentic.NoDeps]("test", model)
agent.AddTool(tool, handler)
agent.AddTool(tool, handler) // should panic
}
Expand All @@ -181,8 +181,8 @@ func TestFirstNonNil(t *testing.T) {
// Tested indirectly through agent options, but let's verify behavior
// by using run options that override agent options
model := test.NewTestModel(test.ModelResponse{Text: "ok"})
agent := agentic.NewAgent[any]("test", model,
agentic.WithTemperature[any](0.3),
agent := agentic.NewAgent[agentic.NoDeps]("test", model,
agentic.WithTemperature[agentic.NoDeps](0.3),
)

// Run with temperature override
Expand All @@ -208,13 +208,13 @@ func TestFirstNonNil(t *testing.T) {
func TestAgentNewAgentDynamicWithOptions(t *testing.T) {
model := test.NewTestModel(test.ModelResponse{Text: "ok"})

agent := agentic.NewAgentDynamic[any](
func(ctx agentic.RunContext[any]) (string, error) {
agent := agentic.NewAgentDynamic[agentic.NoDeps](
func(ctx agentic.RunContext[agentic.NoDeps]) (string, error) {
return "dynamic prompt", nil
},
model,
agentic.WithMaxIterations[any](5),
agentic.WithRetries[any](agentic.RetryConfig{MaxRetries: 3}),
agentic.WithMaxIterations[agentic.NoDeps](5),
agentic.WithRetries[agentic.NoDeps](agentic.RetryConfig{MaxRetries: 3}),
)

result, err := agent.Run(context.Background(), "hello", nil)
Expand Down
14 changes: 7 additions & 7 deletions agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (

func TestAgentSimpleRun(t *testing.T) {
model := test.NewTestModel(test.ModelResponse{Text: "Hello!"})
agent := agentic.NewAgent[any]("You are helpful", model)
agent := agentic.NewAgent[agentic.NoDeps]("You are helpful", model)

result, err := agent.Run(context.Background(), "Hi", nil)
if err != nil {
Expand Down Expand Up @@ -49,7 +49,7 @@ func TestAgentWithTools(t *testing.T) {
test.ModelResponse{Text: "The weather in Tokyo is 72F"},
)

agent := agentic.NewAgent[any]("You are a weather assistant", model).
agent := agentic.NewAgent[agentic.NoDeps]("You are a weather assistant", model).
AddTool(tool, handler)

result, err := agent.Run(context.Background(), "What's the weather in Tokyo?", nil)
Expand Down Expand Up @@ -127,8 +127,8 @@ func TestAgentMaxIterations(t *testing.T) {
return NoopOutput{OK: true}, nil
})

agent := agentic.NewAgent[any]("test", model,
agentic.WithMaxIterations[any](3),
agent := agentic.NewAgent[agentic.NoDeps]("test", model,
agentic.WithMaxIterations[agentic.NoDeps](3),
).AddTool(tool, handler)

result, err := agent.Run(context.Background(), "loop forever", nil)
Expand Down Expand Up @@ -230,8 +230,8 @@ func TestAgentModelRetry(t *testing.T) {
test.ModelResponse{Text: "Found the answer!"},
)

agent := agentic.NewAgent[any]("search assistant", model,
agentic.WithRetries[any](agentic.RetryConfig{MaxRetries: 2}),
agent := agentic.NewAgent[agentic.NoDeps]("search assistant", model,
agentic.WithRetries[agentic.NoDeps](agentic.RetryConfig{MaxRetries: 2}),
).AddTool(tool, handler)

result, err := agent.Run(context.Background(), "Find something", nil)
Expand Down Expand Up @@ -263,7 +263,7 @@ func TestAgentUsageAccumulation(t *testing.T) {
return NoopOutput{}, nil
})

agent := agentic.NewAgent[any]("test", model).AddTool(tool, handler)
agent := agentic.NewAgent[agentic.NoDeps]("test", model).AddTool(tool, handler)

result, err := agent.Run(context.Background(), "go", nil)
if err != nil {
Expand Down
4 changes: 4 additions & 0 deletions aliases.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ type ToolResult = core.ToolResult
type Model = core.Model
type StreamModel = core.StreamModel
type RunContext[DepsT any] = core.RunContext[DepsT]

// NoDeps is the empty dependency type for agents and tools without dependency injection.
// It implements json.Marshaler (always JSON null). See package core.
type NoDeps = core.NoDeps
type ToolHandler = core.ToolHandler
type ToolExecutionResult = core.ToolExecutionResult
type ToolConfig = core.ToolConfig
Expand Down
6 changes: 3 additions & 3 deletions aliases_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func TestAddToolset(t *testing.T) {
},
}

agent := NewAgent[any]("test", model).AddToolset(ts)
agent := NewAgent[NoDeps]("test", model).AddToolset(ts)

if agent.registry == nil {
t.Fatal("expected registry to be created")
Expand Down Expand Up @@ -150,7 +150,7 @@ func TestAliasAgentAndDeferredWrappers(t *testing.T) {
},
testprovider.ModelResponse{Text: "done"},
)
plainAgent := AddTool(NewAgent[any]("system", plainModel), func(input aliasInput) (aliasOutput, error) {
plainAgent := AddTool(NewAgent[NoDeps]("system", plainModel), func(input aliasInput) (aliasOutput, error) {
return aliasOutput{Greeting: "hello " + input.Name}, nil
})
if _, err := plainAgent.Run(context.Background(), "run tool", nil); err != nil {
Expand All @@ -174,7 +174,7 @@ func TestAliasAgentAndDeferredWrappers(t *testing.T) {
t.Fatalf("AddToolWithDeps run: %v", err)
}

typed := NewTypedAgent[any, aliasTypedOutput](
typed := NewTypedAgent[NoDeps, aliasTypedOutput](
"system",
testprovider.NewTestModel(testprovider.ModelResponse{Text: `{"result":"ok"}`}),
"Return typed output",
Expand Down
Loading
Loading