Enha: agent client redo [WIP]
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
40
agent/pw_agent.go
Normal file
40
agent/pw_agent.go
Normal 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
|
||||
}
|
||||
@@ -33,6 +33,10 @@ type AgentClient struct {
|
||||
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(*chatBody)
|
||||
req := models.NewDSChatReq(*ag.chatBody)
|
||||
return json.Marshal(req)
|
||||
case isOpenRouter:
|
||||
// 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)
|
||||
default:
|
||||
// Assume llama.cpp chat (OpenAI format)
|
||||
req := models.OpenAIReq{
|
||||
ChatBody: chatBody,
|
||||
Tools: nil,
|
||||
ChatBody: ag.chatBody,
|
||||
Tools: ag.tools,
|
||||
}
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user