Enha: agent client redo [WIP]

This commit is contained in:
Grail Finder
2026-03-09 07:50:11 +03:00
parent 0e42a6f069
commit 94769225cf
3 changed files with 97 additions and 54 deletions

View File

@@ -4,11 +4,12 @@ package agent
// ones who do their own tools calls // ones who do their own tools calls
// ones that works only with the output // 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) // B: main chat -> tool -> agent (process tool output)
// AgenterA gets a task "find out weather in london" // AgenterA gets a task like "go to the webpage, login and take a screenshot (tell me what you see)"
// proceeds to make tool calls on its own // proceeds to make a plan and executes it.
// returns with final result or an error
type AgenterA interface { type AgenterA interface {
ProcessTask(task string) []byte ProcessTask(task string) []byte
} }

40
agent/pw_agent.go Normal file
View File

@@ -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
}

View File

@@ -33,6 +33,10 @@ type AgentClient struct {
cfg *config.Config cfg *config.Config
getToken func() string getToken func() string
log *slog.Logger 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 { 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 return ag.log
} }
func (ag *AgentClient) FormMsg(sysprompt, msg string) (io.Reader, error) { func (ag *AgentClient) FormFirstMsg(sysprompt, msg string) (io.Reader, error) {
b, err := ag.buildRequest(sysprompt, msg) 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 { if err != nil {
return nil, err return nil, err
} }
@@ -56,76 +81,53 @@ func (ag *AgentClient) FormMsg(sysprompt, msg string) (io.Reader, error) {
} }
// buildRequest creates the appropriate LLM request based on the current API endpoint. // buildRequest creates the appropriate LLM request based on the current API endpoint.
func (ag *AgentClient) buildRequest(sysprompt, msg string) ([]byte, error) { func (ag *AgentClient) buildRequest() ([]byte, error) {
api := ag.cfg.CurrentAPI isCompletion, isChat, isDeepSeek, isOpenRouter := detectAPI(ag.cfg.CurrentAPI)
model := ag.cfg.CurrentModel ag.log.Debug("agent building request", "api", ag.cfg.CurrentAPI, "isCompletion", isCompletion, "isChat", isChat, "isDeepSeek", isDeepSeek, "isOpenRouter", isOpenRouter)
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)
// Build prompt for completion endpoints // Build prompt for completion endpoints
if isCompletion { if isCompletion {
var sb strings.Builder var sb strings.Builder
for i := range messages { for i := range ag.chatBody.Messages {
sb.WriteString(messages[i].ToPrompt()) sb.WriteString(ag.chatBody.Messages[i].ToPrompt())
sb.WriteString("\n") sb.WriteString("\n")
} }
prompt := strings.TrimSpace(sb.String()) prompt := strings.TrimSpace(sb.String())
switch { switch {
case isDeepSeek: case isDeepSeek:
// DeepSeek completion // 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 req.Stream = false // Agents don't need streaming
return json.Marshal(req) return json.Marshal(req)
case isOpenRouter: case isOpenRouter:
// OpenRouter completion // 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 req.Stream = false // Agents don't need streaming
return json.Marshal(req) return json.Marshal(req)
default: default:
// Assume llama.cpp completion // 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 req.Stream = false // Agents don't need streaming
return json.Marshal(req) 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 { switch {
case isDeepSeek: case isDeepSeek:
// DeepSeek chat // DeepSeek chat
req := models.NewDSChatReq(*chatBody) req := models.NewDSChatReq(*ag.chatBody)
return json.Marshal(req) return json.Marshal(req)
case isOpenRouter: case isOpenRouter:
// OpenRouter chat - agents don't use reasoning by default // OpenRouter chat - agents don't use reasoning by default
req := models.NewOpenRouterChatReq(*chatBody, defaultProps, "") req := models.NewOpenRouterChatReq(*ag.chatBody, defaultProps, ag.cfg.ReasoningEffort)
return json.Marshal(req) return json.Marshal(req)
default: default:
// Assume llama.cpp chat (OpenAI format) // Assume llama.cpp chat (OpenAI format)
req := models.OpenAIReq{ req := models.OpenAIReq{
ChatBody: chatBody, ChatBody: ag.chatBody,
Tools: nil, Tools: ag.tools,
} }
return json.Marshal(req) 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(chatBody)
}
func (ag *AgentClient) LLMRequest(body io.Reader) ([]byte, error) { func (ag *AgentClient) LLMRequest(body io.Reader) ([]byte, error) {
// Read the body for debugging (but we need to recreate it for the request) // Read the body for debugging (but we need to recreate it for the request)