ZovOS_AI/internal/agent/agent.go
Dan4ick 460d20539f feat: initial project structure - Phase 1 MVP
- Project infrastructure: go.mod, Makefile, .gitignore, README
- Configuration system with YAML parsing and env overrides
- Structured logging with slog
- AI provider interface with Ollama, OpenAI, Anthropic implementations
- Provider manager with runtime switching
- Agent orchestrator with ReAct loop (reason-act-observe)
- Tool registry with JSON Schema descriptions
- Terminal tool: shell command execution with safety controls
- Filesystem tools: read, write, list with path access control
- Conversation memory with session management
- Web UI server with WebSocket streaming
- Modern dark theme UI with glassmorphism, animations
- Frontend: WebSocket client, markdown rendering, tool call display
2026-03-20 00:38:22 +03:00

142 lines
3.5 KiB
Go

package agent
import (
"context"
"fmt"
"log/slog"
"time"
"git.bebrik.xyz/Dan4ick/ZovOS_AI/internal/ai"
"git.bebrik.xyz/Dan4ick/ZovOS_AI/internal/config"
"git.bebrik.xyz/Dan4ick/ZovOS_AI/internal/tools"
)
// StreamCallback is called for each chunk of streamed output.
type StreamCallback func(chunk string, toolCall *ai.ToolCall, toolResult *string, done bool)
// Agent orchestrates the AI reasoning loop.
type Agent struct {
provider ai.Provider
tools *tools.Registry
cfg config.AgentConfig
log *slog.Logger
}
// New creates a new Agent.
func New(provider ai.Provider, toolsRegistry *tools.Registry, cfg config.AgentConfig, log *slog.Logger) *Agent {
return &Agent{
provider: provider,
tools: toolsRegistry,
cfg: cfg,
log: log,
}
}
// SetProvider updates the active AI provider.
func (a *Agent) SetProvider(p ai.Provider) {
a.provider = p
}
// Run executes the agent loop: send messages → get response → execute tools → repeat.
func (a *Agent) Run(ctx context.Context, userMessage string, history []ai.Message, callback StreamCallback) ([]ai.Message, error) {
timeout := time.Duration(a.cfg.TimeoutSeconds) * time.Second
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
messages := make([]ai.Message, 0, len(history)+2)
// Add system prompt
if a.cfg.SystemPrompt != "" {
messages = append(messages, ai.Message{
Role: "system",
Content: a.cfg.SystemPrompt,
})
}
// Add conversation history
messages = append(messages, history...)
// Add new user message
messages = append(messages, ai.Message{
Role: "user",
Content: userMessage,
})
availableTools := a.tools.List()
for iteration := 0; iteration < a.cfg.MaxIterations; iteration++ {
a.log.Info("agent iteration", "iteration", iteration+1, "messages", len(messages))
// Try streaming first
streamCh, err := a.provider.StreamChat(ctx, messages, availableTools)
if err != nil {
return messages, fmt.Errorf("AI provider error: %w", err)
}
// Collect full response from stream
var fullContent string
var allToolCalls []ai.ToolCall
for chunk := range streamCh {
if chunk.Error != nil {
return messages, fmt.Errorf("stream error: %w", chunk.Error)
}
if chunk.Content != "" {
fullContent += chunk.Content
if callback != nil {
callback(chunk.Content, nil, nil, false)
}
}
if len(chunk.ToolCalls) > 0 {
allToolCalls = append(allToolCalls, chunk.ToolCalls...)
}
}
// Add assistant response to messages
assistantMsg := ai.Message{
Role: "assistant",
Content: fullContent,
ToolCalls: allToolCalls,
}
messages = append(messages, assistantMsg)
// If no tool calls, we're done
if len(allToolCalls) == 0 {
if callback != nil {
callback("", nil, nil, true)
}
return messages, nil
}
// Execute tool calls
for _, tc := range allToolCalls {
a.log.Info("executing tool call", "tool", tc.Function.Name, "id", tc.ID)
if callback != nil {
callback("", &tc, nil, false)
}
result, err := a.tools.Execute(ctx, tc.Function.Name, tc.Function.Arguments)
if err != nil {
result = fmt.Sprintf("Tool execution error: %v", err)
}
if callback != nil {
callback("", &tc, &result, false)
}
messages = append(messages, ai.Message{
Role: "tool",
Content: result,
ToolCallID: tc.ID,
Name: tc.Function.Name,
})
}
}
if callback != nil {
callback("", nil, nil, true)
}
return messages, fmt.Errorf("max iterations (%d) reached", a.cfg.MaxIterations)
}