summaryrefslogtreecommitdiff
path: root/agent
diff options
context:
space:
mode:
authorGrail Finder <wohilas@gmail.com>2026-03-09 07:50:11 +0300
committerGrail Finder <wohilas@gmail.com>2026-03-09 07:50:11 +0300
commit94769225cfbcd4b0a30acab913915f45d6cb9f4b (patch)
treee85ad71ed37da23db2ddc8289b44742354c057c7 /agent
parent0e42a6f069ceea40485162c014c04cf718568cfe (diff)
Enha: agent client redo [WIP]
Diffstat (limited to 'agent')
-rw-r--r--agent/agent.go7
-rw-r--r--agent/pw_agent.go40
-rw-r--r--agent/request.go104
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) {