Files
Cyrene_v2/internal/runtime/loop/request.go
T

82 lines
2.5 KiB
Go

package loop
import (
"encoding/json"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/agent"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
)
// buildRequest assembles a model.Request from the agent's public surface,
// mirroring agent.buildRequest so the streaming path behaves identically to
// RunStep (leading instruction message, tool schemas, model routing).
func (l *Loop) buildRequest(messages []model.Message) model.Request {
req := model.Request{
Model: l.agent.Model(),
Messages: l.assembleMessages(messages),
Temperature: l.temperature(),
MaxTokens: l.agent.MaxTokens(),
Purpose: l.agent.Purpose(),
Tools: l.toolSchemas(),
}
if l.agent.Router() != nil {
if m, _ := l.agent.Router().RouteRequest(req); m != "" {
req.Model = m
}
}
return req
}
// assembleMessages prepends the agent's instructions as a leading system message
// when they are non-empty, otherwise returns the input slice unchanged.
func (l *Loop) assembleMessages(messages []model.Message) []model.Message {
instr := l.agent.Instructions()
if instr == "" {
return messages
}
out := make([]model.Message, 0, len(messages)+1)
out = append(out, model.TextMessage(model.RoleSystem, instr))
out = append(out, messages...)
return out
}
// temperature returns a pointer to the configured temperature, mapping zero to
// nil so the provider default applies (matching the agent package).
func (l *Loop) temperature() *float64 {
t := l.agent.Temperature()
if t == 0 {
return nil
}
return &t
}
// toolSchemas advertises the registered tool schemas to the model, or nil when no
// dispatcher is configured.
func (l *Loop) toolSchemas() []model.ToolSpec {
d := l.dispatcher()
if d == nil {
return nil
}
return d.ToolSchemas()
}
// decodeToolCalls decodes tool calls from a model.Response using the same two
// conventions as the agent package: the normalized RoleTool placeholder, then a
// JSON envelope of the form {"tool_calls":[...]} in Content. It reports ok=true
// (with a non-empty slice) when a call is decoded.
func decodeToolCalls(resp model.Response) ([]agent.ToolCall, bool) {
if resp.Message.Role == model.RoleAssistant && resp.Message.ToolCallName != "" {
return []agent.ToolCall{{Name: resp.Message.ToolCallName, Args: map[string]any{}}}, true
}
var payload struct {
ToolCalls []agent.ToolCall `json:"tool_calls"`
}
if err := json.Unmarshal([]byte(resp.Content), &payload); err != nil {
return nil, false
}
if len(payload.ToolCalls) == 0 {
return nil, false
}
return payload.ToolCalls, true
}