2026-07-01 06:53:26 +00:00
|
|
|
package agent_test
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"os"
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
"github.com/VictorVargas/rony-llm-agent/pkg/agent"
|
|
|
|
|
"github.com/VictorVargas/rony-llm-agent/pkg/llm"
|
|
|
|
|
"github.com/VictorVargas/rony-llm-agent/pkg/llm/providers/llamacpp"
|
|
|
|
|
"github.com/VictorVargas/rony-llm-agent/pkg/persona"
|
|
|
|
|
"github.com/VictorVargas/rony-llm-agent/pkg/tools"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// TestIntegration_LlamaCPP_Generate is an integration test that requires llama.cpp running on localhost:8080.
|
Fix tool call tracking and streaming assembly for all providers
- agent/loop.go: Record assistant message with ToolCalls before tool results,
and set ToolCallID on tool-result messages so follow-up requests have complete
context (prevents models from losing track of already-attempted tools).
- llm/providers/llamacpp/client.go: Buffer fragmented tool call deltas during
streaming, assemble them into complete calls when finish_reason arrives.
Add ToolCalls, ToolCallID, Name fields to request building.
- llm/providers/openai/client.go: Send ToolCalls, ToolCallID, Name when
building chat requests so messages are wire-format correct.
- llm/types.go: Add ToolCalls field to Message struct for serialization
back into conversation history.
- agent/integration_test.go: Move integration test skip from TestMain to a
per-test skipUnlessIntegration() so it doesn't hide other package tests.
- sandbox & tools: Add edge-case tests (relative traversal, array paths,
non-path strings, zero-value guards, sentinel errors).
2026-07-08 23:11:57 +00:00
|
|
|
// Run with: INTEGRATION_TESTS=1 go test ./pkg/agent/ -run TestIntegration_LlamaCPP_Generate
|
2026-07-01 06:53:26 +00:00
|
|
|
func TestIntegration_LlamaCPP_Generate(t *testing.T) {
|
Fix tool call tracking and streaming assembly for all providers
- agent/loop.go: Record assistant message with ToolCalls before tool results,
and set ToolCallID on tool-result messages so follow-up requests have complete
context (prevents models from losing track of already-attempted tools).
- llm/providers/llamacpp/client.go: Buffer fragmented tool call deltas during
streaming, assemble them into complete calls when finish_reason arrives.
Add ToolCalls, ToolCallID, Name fields to request building.
- llm/providers/openai/client.go: Send ToolCalls, ToolCallID, Name when
building chat requests so messages are wire-format correct.
- llm/types.go: Add ToolCalls field to Message struct for serialization
back into conversation history.
- agent/integration_test.go: Move integration test skip from TestMain to a
per-test skipUnlessIntegration() so it doesn't hide other package tests.
- sandbox & tools: Add edge-case tests (relative traversal, array paths,
non-path strings, zero-value guards, sentinel errors).
2026-07-08 23:11:57 +00:00
|
|
|
skipUnlessIntegration(t)
|
2026-07-01 06:53:26 +00:00
|
|
|
client, err := llamacpp.New(llamacpp.Config{
|
|
|
|
|
BaseURL: "http://localhost:8080/v1",
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("failed to create client: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resp, err := client.Generate(context.Background(), llm.CompletionRequest{
|
|
|
|
|
Messages: []llm.Message{
|
|
|
|
|
{Role: llm.RoleUser, Content: "What is 2+2? Answer with just the number."},
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("generate failed: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if resp.Content == "" {
|
|
|
|
|
t.Fatal("expected non-empty response")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestIntegration_LlamaCPP_Stream is an integration test that requires llama.cpp running on localhost:8080.
|
|
|
|
|
func TestIntegration_LlamaCPP_Stream(t *testing.T) {
|
Fix tool call tracking and streaming assembly for all providers
- agent/loop.go: Record assistant message with ToolCalls before tool results,
and set ToolCallID on tool-result messages so follow-up requests have complete
context (prevents models from losing track of already-attempted tools).
- llm/providers/llamacpp/client.go: Buffer fragmented tool call deltas during
streaming, assemble them into complete calls when finish_reason arrives.
Add ToolCalls, ToolCallID, Name fields to request building.
- llm/providers/openai/client.go: Send ToolCalls, ToolCallID, Name when
building chat requests so messages are wire-format correct.
- llm/types.go: Add ToolCalls field to Message struct for serialization
back into conversation history.
- agent/integration_test.go: Move integration test skip from TestMain to a
per-test skipUnlessIntegration() so it doesn't hide other package tests.
- sandbox & tools: Add edge-case tests (relative traversal, array paths,
non-path strings, zero-value guards, sentinel errors).
2026-07-08 23:11:57 +00:00
|
|
|
skipUnlessIntegration(t)
|
2026-07-01 06:53:26 +00:00
|
|
|
client, err := llamacpp.New(llamacpp.Config{
|
|
|
|
|
BaseURL: "http://localhost:8080/v1",
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("failed to create client: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stream := client.Stream(context.Background(), llm.CompletionRequest{
|
|
|
|
|
Messages: []llm.Message{
|
|
|
|
|
{Role: llm.RoleUser, Content: "Say hello in 5 words."},
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
var chunks []llm.StreamChunk
|
|
|
|
|
for chunk, err := range stream {
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("stream error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
chunks = append(chunks, chunk)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(chunks) == 0 {
|
|
|
|
|
t.Fatal("expected at least one chunk")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestIntegration_AgentLoop_Generate is an integration test for the agent loop with llama.cpp.
|
|
|
|
|
func TestIntegration_AgentLoop_Generate(t *testing.T) {
|
Fix tool call tracking and streaming assembly for all providers
- agent/loop.go: Record assistant message with ToolCalls before tool results,
and set ToolCallID on tool-result messages so follow-up requests have complete
context (prevents models from losing track of already-attempted tools).
- llm/providers/llamacpp/client.go: Buffer fragmented tool call deltas during
streaming, assemble them into complete calls when finish_reason arrives.
Add ToolCalls, ToolCallID, Name fields to request building.
- llm/providers/openai/client.go: Send ToolCalls, ToolCallID, Name when
building chat requests so messages are wire-format correct.
- llm/types.go: Add ToolCalls field to Message struct for serialization
back into conversation history.
- agent/integration_test.go: Move integration test skip from TestMain to a
per-test skipUnlessIntegration() so it doesn't hide other package tests.
- sandbox & tools: Add edge-case tests (relative traversal, array paths,
non-path strings, zero-value guards, sentinel errors).
2026-07-08 23:11:57 +00:00
|
|
|
skipUnlessIntegration(t)
|
2026-07-01 06:53:26 +00:00
|
|
|
client, err := llamacpp.New(llamacpp.Config{
|
|
|
|
|
BaseURL: "http://localhost:8080/v1",
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("failed to create client: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
registry := tools.NewRegistry()
|
|
|
|
|
registry.Register(tools.Tool{
|
|
|
|
|
Name: "add_numbers",
|
|
|
|
|
Description: "Add two numbers together",
|
|
|
|
|
InputSchema: json.RawMessage(`{"type": "function", "function": {"name": "add_numbers", "description": "Add two numbers together", "parameters": {"type": "object", "properties": {"a": {"type": "number"}, "b": {"type": "number"}}}}}`),
|
|
|
|
|
Handler: func(ctx context.Context, args json.RawMessage) (tools.ToolResult, error) {
|
|
|
|
|
var input struct {
|
|
|
|
|
A float64 `json:"a"`
|
|
|
|
|
B float64 `json:"b"`
|
|
|
|
|
}
|
|
|
|
|
if err := json.Unmarshal(args, &input); err != nil {
|
|
|
|
|
return tools.ToolResult{IsError: true}, err
|
|
|
|
|
}
|
|
|
|
|
return tools.ToolResult{Content: "42"}, nil
|
|
|
|
|
},
|
|
|
|
|
Permission: tools.Allow,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
loop := agent.New(agent.Config{
|
2026-07-12 23:14:15 +00:00
|
|
|
LLM: client,
|
|
|
|
|
Persona: persona.DefaultPersona(),
|
|
|
|
|
Tools: registry,
|
|
|
|
|
Sandbox: &mockSandbox{},
|
2026-07-01 06:53:26 +00:00
|
|
|
MaxIters: 3,
|
|
|
|
|
})
|
|
|
|
|
|
2026-07-17 05:24:03 +00:00
|
|
|
resp, err := loop.Run(context.Background(), llm.Message{Role: llm.RoleUser, Content: "What is 20+22? Use the add_numbers tool."})
|
2026-07-01 06:53:26 +00:00
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("run failed: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if resp.Content == "" {
|
|
|
|
|
t.Fatal("expected non-empty response")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestIntegration_AgentLoop_Stream is an integration test for the agent loop with streaming.
|
|
|
|
|
func TestIntegration_AgentLoop_Stream(t *testing.T) {
|
Fix tool call tracking and streaming assembly for all providers
- agent/loop.go: Record assistant message with ToolCalls before tool results,
and set ToolCallID on tool-result messages so follow-up requests have complete
context (prevents models from losing track of already-attempted tools).
- llm/providers/llamacpp/client.go: Buffer fragmented tool call deltas during
streaming, assemble them into complete calls when finish_reason arrives.
Add ToolCalls, ToolCallID, Name fields to request building.
- llm/providers/openai/client.go: Send ToolCalls, ToolCallID, Name when
building chat requests so messages are wire-format correct.
- llm/types.go: Add ToolCalls field to Message struct for serialization
back into conversation history.
- agent/integration_test.go: Move integration test skip from TestMain to a
per-test skipUnlessIntegration() so it doesn't hide other package tests.
- sandbox & tools: Add edge-case tests (relative traversal, array paths,
non-path strings, zero-value guards, sentinel errors).
2026-07-08 23:11:57 +00:00
|
|
|
skipUnlessIntegration(t)
|
2026-07-01 06:53:26 +00:00
|
|
|
client, err := llamacpp.New(llamacpp.Config{
|
|
|
|
|
BaseURL: "http://localhost:8080/v1",
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("failed to create client: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
loop := agent.New(agent.Config{
|
2026-07-12 23:14:15 +00:00
|
|
|
LLM: client,
|
|
|
|
|
Persona: persona.DefaultPersona(),
|
|
|
|
|
Tools: tools.NewRegistry(),
|
2026-07-01 06:53:26 +00:00
|
|
|
MaxIters: 3,
|
|
|
|
|
})
|
|
|
|
|
|
2026-07-17 05:24:03 +00:00
|
|
|
stream := loop.RunStream(context.Background(), llm.Message{Role: llm.RoleUser, Content: "Say something interesting."})
|
2026-07-01 06:53:26 +00:00
|
|
|
|
|
|
|
|
var chunks []llm.StreamChunk
|
|
|
|
|
for chunk, err := range stream {
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("stream error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
chunks = append(chunks, chunk)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(chunks) == 0 {
|
|
|
|
|
t.Fatal("expected at least one chunk")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// mockSandbox is a simple sandbox that allows all calls.
|
|
|
|
|
type mockSandbox struct{}
|
|
|
|
|
|
|
|
|
|
func (m *mockSandbox) ValidateToolCall(tool tools.Tool, call llm.ToolCall) error {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
Fix tool call tracking and streaming assembly for all providers
- agent/loop.go: Record assistant message with ToolCalls before tool results,
and set ToolCallID on tool-result messages so follow-up requests have complete
context (prevents models from losing track of already-attempted tools).
- llm/providers/llamacpp/client.go: Buffer fragmented tool call deltas during
streaming, assemble them into complete calls when finish_reason arrives.
Add ToolCalls, ToolCallID, Name fields to request building.
- llm/providers/openai/client.go: Send ToolCalls, ToolCallID, Name when
building chat requests so messages are wire-format correct.
- llm/types.go: Add ToolCalls field to Message struct for serialization
back into conversation history.
- agent/integration_test.go: Move integration test skip from TestMain to a
per-test skipUnlessIntegration() so it doesn't hide other package tests.
- sandbox & tools: Add edge-case tests (relative traversal, array paths,
non-path strings, zero-value guards, sentinel errors).
2026-07-08 23:11:57 +00:00
|
|
|
// skipUnlessIntegration skips a single integration test (one that needs a
|
|
|
|
|
// live llama.cpp server on localhost:8080) unless explicitly enabled.
|
|
|
|
|
//
|
|
|
|
|
// This used to be done in TestMain by returning early without calling
|
|
|
|
|
// m.Run() when INTEGRATION_TESTS wasn't set - but a package's TestMain
|
|
|
|
|
// covers its *entire* test binary (both package agent_test, here, and
|
|
|
|
|
// package agent, e.g. loop_test.go, get linked together), so that skipped
|
|
|
|
|
// every test in the package, not just the four integration ones. In
|
|
|
|
|
// practice that meant `go test ./...` reported this package as passing
|
|
|
|
|
// while silently running zero of its tests, including all the mock-based
|
|
|
|
|
// coverage in loop_test.go for approvals, sandboxing, and tool-call
|
|
|
|
|
// handling.
|
|
|
|
|
func skipUnlessIntegration(t *testing.T) {
|
|
|
|
|
t.Helper()
|
2026-07-01 06:53:26 +00:00
|
|
|
if os.Getenv("INTEGRATION_TESTS") != "1" {
|
Fix tool call tracking and streaming assembly for all providers
- agent/loop.go: Record assistant message with ToolCalls before tool results,
and set ToolCallID on tool-result messages so follow-up requests have complete
context (prevents models from losing track of already-attempted tools).
- llm/providers/llamacpp/client.go: Buffer fragmented tool call deltas during
streaming, assemble them into complete calls when finish_reason arrives.
Add ToolCalls, ToolCallID, Name fields to request building.
- llm/providers/openai/client.go: Send ToolCalls, ToolCallID, Name when
building chat requests so messages are wire-format correct.
- llm/types.go: Add ToolCalls field to Message struct for serialization
back into conversation history.
- agent/integration_test.go: Move integration test skip from TestMain to a
per-test skipUnlessIntegration() so it doesn't hide other package tests.
- sandbox & tools: Add edge-case tests (relative traversal, array paths,
non-path strings, zero-value guards, sentinel errors).
2026-07-08 23:11:57 +00:00
|
|
|
t.Skip("set INTEGRATION_TESTS=1 to run (requires a live llama.cpp server on localhost:8080)")
|
2026-07-01 06:53:26 +00:00
|
|
|
}
|
|
|
|
|
}
|