package agent import "git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model" // defaultPurpose labels CallRecords when Options.Purpose is empty. const defaultPurpose = "agent" // buildRequest assembles a model.Request for one step: the agent's instructions // prepended as a leading system message, plus the conversation messages, the // configured model/temperature/max-tokens/purpose and the registered tool // schemas. When a router is injected it overrides the model from the request // contents, which is how vision requests are routed to the vision model. // // 本路径与流式主路径(loop.buildRequest)共用同一处 persona 钩子(裁决 03.4.1): // 角色由**适配器视图**携带(model.PersonaScoped,见 model/persona_stack.go), // 写成请求上的 PersonaID,模型路由也按它走。为什么角色不在本函数参数里:RunStep // 由 loop 与 automations 调用,角色只存在于 loop 的会话环境 ctx 里,而 agent // 不能反向 import loop;按角色分开注入的适配器视图(一角色一 agent, // design/22 §4.2 方案 A)是唯一不依赖 ctx 的载体。 // // 角色**已知**时才切到 RouteRequestForPersona:未知角色(普通 *model.Router 或 // 未绑角色的视图)保持原来的 RouteRequest —— 后者带 vision 模型的改写规则, // RouteRequestForPersona 刻意不含(图像路由已归 vision.Router,裁决 14.4.1/14.4.5)。 func (a *Agent) buildRequest(messages []model.Message) model.Request { req := model.Request{ Model: a.opts.Model, Messages: a.assembleMessages(messages), Temperature: a.temperature(), MaxTokens: a.opts.MaxTokens, Purpose: a.purpose(), Tools: a.toolSchemas(), } if ps, ok := a.adapter.(model.PersonaScoped); ok { req.PersonaID = ps.ScopedPersona() } if a.router != nil { if pr, ok := a.router.(model.PersonaRouter); ok && req.PersonaID != "" { if routeModel, _ := pr.RouteRequestForPersona(req.PersonaID, req); routeModel != "" { req.Model = routeModel } return req } if routeModel, _ := a.router.RouteRequest(req); routeModel != "" { req.Model = routeModel } } return req } // assembleMessages returns messages with the agent's instructions prepended as a // leading system message when instructions are non-empty, otherwise the input // slice is returned unchanged. func (a *Agent) assembleMessages(messages []model.Message) []model.Message { if a.instructions == "" { return messages } out := make([]model.Message, 0, len(messages)+1) out = append(out, model.TextMessage(model.RoleSystem, a.instructions)) out = append(out, messages...) return out } // temperature returns the temperature pointer to place in a Request. Zero maps // to nil so the provider default applies. func (a *Agent) temperature() *float64 { if a.opts.Temperature == 0 { return nil } t := a.opts.Temperature return &t } // purpose returns the CallRecord purpose label, defaulting to defaultPurpose. func (a *Agent) purpose() string { if a.opts.Purpose == "" { return defaultPurpose } return a.opts.Purpose } // toolSchemas returns the registered tool schemas advertised to the model, or // nil when no dispatcher is configured. func (a *Agent) toolSchemas() []model.ToolSpec { if a.tools == nil { return nil } return a.tools.ToolSchemas() }