Files
Cyrene_v2/internal/runtime/agent/request.go
T
AskaEth 8890c30bb5
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
feat(model): 按 provider 形状独立适配推理开关 + per-persona 提供商链真接线(裁决 03.4.2/08.4.1/03.4.1 余项)
- 新增 reasoning_shape.go:**每个 provider 形状一个 resolver,只发自己形状的字段**
  · deepseek:thinking{type} + reasoning_effort(依据 DevDocs/reference/deepseek 官方本地副本)
  · vllm:chat_template_kwargs.enable_thinking · dashscope:顶层 enable_thinking(**只允许显式声明**)
  · openai:仅 reasoning_effort · anthropic:自己的 thinking{budget_tokens}(未改)
  · 未知 kind → WARN 且一个字段都不发(不许静默失效)
- 形状载体:models.json 组级 `reasoning.kind`(可热更新,未声明则不写该键)+ env `LLM_REASONING_KIND`
- 红线:`effort:none` 在只有布尔开关的形状上翻成**显式关思考**,绝不降成任何强度档
- 03.4.1 余项:`Request.PersonaID` + `RuntimeLLM.ForPersona(pid)` 视图;
  每 persona 协议一条链并消费 `RouteProviderForPersona`;`agent/request.go` 非流式路径同一钩子
- **生产装配(主会话补)**:默认 agent 与 personaAgentPool 都改用 `ForPersona(pid)` 视图——
  否则角色级模型/提供商路由在生产里永不被选中(形同虚设)
- 复核:build/vet 绿 + model/agent/config/cmd 包测试绿;5 条变异反证先红后还原
  (其中一条「红但没钉住」已改用例:通道读取加 2s 超时,避免整包 panic 掩盖信号)
- 未落:DashScope 的 auto 推断(缺官方依据,只允许显式声明);Qwen `/think` 前导消息
  (design/01 §2.1 与 design/11 §3 口径冲突,停手报告)
2026-10-04 12:57:55 +08:00

89 lines
3.3 KiB
Go

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()
}