diff options
Diffstat (limited to 'agent')
| -rw-r--r-- | agent/agent.go | 7 | ||||
| -rw-r--r-- | agent/pw_agent.go | 40 | ||||
| -rw-r--r-- | agent/request.go | 104 |
3 files changed, 97 insertions, 54 deletions
diff --git a/agent/agent.go b/agent/agent.go index 2b0d457..8a6614f 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -4,11 +4,12 @@ package agent // ones who do their own tools calls // ones that works only with the output -// A: main chat -> agent (handles everything: tool + processing) +// A: main chat -> agent (handles everything: tool + processing), supports tool chaining // B: main chat -> tool -> agent (process tool output) -// AgenterA gets a task "find out weather in london" -// proceeds to make tool calls on its own +// AgenterA gets a task like "go to the webpage, login and take a screenshot (tell me what you see)" +// proceeds to make a plan and executes it. +// returns with final result or an error type AgenterA interface { ProcessTask(task string) []byte } diff --git a/agent/pw_agent.go b/agent/pw_agent.go new file mode 100644 index 0000000..8c1c2bf --- /dev/null +++ b/agent/pw_agent.go @@ -0,0 +1,40 @@ +package agent + +// PWAgent: is AgenterA type agent (enclosed with tool chaining) +// sysprompt explain tools and how to plan for execution +type PWAgent struct { + *AgentClient + sysprompt string +} + +// NewWebAgentB creates a WebAgentB that uses the given formatting function +func NewPWAgent(client *AgentClient, sysprompt string) *PWAgent { + return &PWAgent{AgentClient: client, sysprompt: sysprompt} +} + +func (a *PWAgent) ProcessTask(task string) []byte { + req, err := a.FormFirstMsg(a.sysprompt, task) + if err != nil { + a.Log().Error("PWAgent failed to process the request", "error", err) + return []byte("PWAgent failed to process the request; err: " + err.Error()) + } + toolCallLimit := 10 + for i := 0; i < toolCallLimit; i++ { + resp, err := a.LLMRequest(req) + if err != nil { + a.Log().Error("failed to process the request", "error", err) + return []byte("failed to process the request; err: " + err.Error()) + } + toolCall, hasToolCall := findToolCall(resp) + if !hasToolCall { + return resp + } + // check resp for tool calls + // make tool call + // add tool call resp to body + // send new request too lmm + tooResp := toolCall(resp) + req, err = a.FormMsg(toolResp) + } + return nil +} diff --git a/agent/request.go b/agent/request.go index c20d7dd..4ca619d 100644 --- a/agent/request.go +++ b/agent/request.go @@ -30,9 +30,13 @@ func detectAPI(api string) (isCompletion, isChat, isDeepSeek, isOpenRouter bool) } type AgentClient struct { - cfg *config.Config - getToken func() string - log *slog.Logger + cfg *config.Config + getToken func() string + log *slog.Logger + chatBody *models.ChatBody + sysprompt string + lastToolCallID string + tools []models.Tool } func NewAgentClient(cfg *config.Config, log *slog.Logger, gt func() string) *AgentClient { @@ -47,8 +51,29 @@ func (ag *AgentClient) Log() *slog.Logger { return ag.log } -func (ag *AgentClient) FormMsg(sysprompt, msg string) (io.Reader, error) { - b, err := ag.buildRequest(sysprompt, msg) +func (ag *AgentClient) FormFirstMsg(sysprompt, msg string) (io.Reader, error) { + ag.sysprompt = sysprompt + ag.chatBody = &models.ChatBody{ + Messages: []models.RoleMsg{ + {Role: "system", Content: ag.sysprompt}, + {Role: "user", Content: msg}, + }, + Stream: false, + Model: ag.cfg.CurrentModel, + } + b, err := ag.buildRequest() + if err != nil { + return nil, err + } + return bytes.NewReader(b), nil +} + +func (ag *AgentClient) FormMsg(msg string) (io.Reader, error) { + m := models.RoleMsg{ + Role: "tool", Content: msg, + } + ag.chatBody.Messages = append(ag.chatBody.Messages, m) + b, err := ag.buildRequest() if err != nil { return nil, err } @@ -56,75 +81,52 @@ func (ag *AgentClient) FormMsg(sysprompt, msg string) (io.Reader, error) { } // buildRequest creates the appropriate LLM request based on the current API endpoint. -func (ag *AgentClient) buildRequest(sysprompt, msg string) ([]byte, error) { - api := ag.cfg.CurrentAPI - model := ag.cfg.CurrentModel - messages := []models.RoleMsg{ - {Role: "system", Content: sysprompt}, - {Role: "user", Content: msg}, - } - // Determine API type - isCompletion, isChat, isDeepSeek, isOpenRouter := detectAPI(api) - ag.log.Debug("agent building request", "api", api, "isCompletion", isCompletion, "isChat", isChat, "isDeepSeek", isDeepSeek, "isOpenRouter", isOpenRouter) +func (ag *AgentClient) buildRequest() ([]byte, error) { + isCompletion, isChat, isDeepSeek, isOpenRouter := detectAPI(ag.cfg.CurrentAPI) + ag.log.Debug("agent building request", "api", ag.cfg.CurrentAPI, "isCompletion", isCompletion, "isChat", isChat, "isDeepSeek", isDeepSeek, "isOpenRouter", isOpenRouter) // Build prompt for completion endpoints if isCompletion { var sb strings.Builder - for i := range messages { - sb.WriteString(messages[i].ToPrompt()) + for i := range ag.chatBody.Messages { + sb.WriteString(ag.chatBody.Messages[i].ToPrompt()) sb.WriteString("\n") } prompt := strings.TrimSpace(sb.String()) switch { case isDeepSeek: // DeepSeek completion - req := models.NewDSCompletionReq(prompt, model, defaultProps["temperature"], []string{}) + req := models.NewDSCompletionReq(prompt, ag.chatBody.Model, defaultProps["temperature"], []string{}) req.Stream = false // Agents don't need streaming return json.Marshal(req) case isOpenRouter: // OpenRouter completion - req := models.NewOpenRouterCompletionReq(model, prompt, defaultProps, []string{}) + req := models.NewOpenRouterCompletionReq(ag.chatBody.Model, prompt, defaultProps, []string{}) req.Stream = false // Agents don't need streaming return json.Marshal(req) default: // Assume llama.cpp completion - req := models.NewLCPReq(prompt, model, nil, defaultProps, []string{}) + req := models.NewLCPReq(prompt, ag.chatBody.Model, nil, defaultProps, []string{}) req.Stream = false // Agents don't need streaming return json.Marshal(req) } } - // Chat completions endpoints - if isChat || !isCompletion { - chatBody := &models.ChatBody{ - Model: model, - Stream: false, // Agents don't need streaming - Messages: messages, + switch { + case isDeepSeek: + // DeepSeek chat + req := models.NewDSChatReq(*ag.chatBody) + return json.Marshal(req) + case isOpenRouter: + // OpenRouter chat - agents don't use reasoning by default + req := models.NewOpenRouterChatReq(*ag.chatBody, defaultProps, ag.cfg.ReasoningEffort) + return json.Marshal(req) + default: + // Assume llama.cpp chat (OpenAI format) + req := models.OpenAIReq{ + ChatBody: ag.chatBody, + Tools: ag.tools, } - switch { - case isDeepSeek: - // DeepSeek chat - req := models.NewDSChatReq(*chatBody) - return json.Marshal(req) - case isOpenRouter: - // OpenRouter chat - agents don't use reasoning by default - req := models.NewOpenRouterChatReq(*chatBody, defaultProps, "") - return json.Marshal(req) - default: - // Assume llama.cpp chat (OpenAI format) - req := models.OpenAIReq{ - ChatBody: chatBody, - Tools: nil, - } - return json.Marshal(req) - } - } - // Fallback (should not reach here) - ag.log.Warn("unknown API, using default chat completions format", "api", api) - chatBody := &models.ChatBody{ - Model: model, - Stream: false, // Agents don't need streaming - Messages: messages, + return json.Marshal(req) } - return json.Marshal(chatBody) } func (ag *AgentClient) LLMRequest(body io.Reader) ([]byte, error) { |
