feat(agent): 云端 Agent - 增加上下文预算、任务事实与终态恢复保障
This commit is contained in:
1 parent
3bf21d79af
commit
b52bc9d5f2
61 files changed
+3512
-309
No files matched your search
@@ -0,0 +1,93 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"infinite-canvas/backend/internal/model"
|
||||
)
|
||||
|
||||
// ContextFrame is a fresh projection of durable task facts, never authorization.
|
||||
// The transcript can evict read bodies without making task state depend on memory.
|
||||
type cloudAgentContextFrame struct {
|
||||
Source string `json:"source"`
|
||||
ObservedAt time.Time `json:"observedAt"`
|
||||
RunID string `json:"runId"`
|
||||
UserGoal string `json:"userGoal"`
|
||||
Plan []cloudAgentPlanItem `json:"plan,omitempty"`
|
||||
Tasks []map[string]any `json:"tasks"`
|
||||
OlderTaskCount int `json:"olderTaskCount,omitempty"`
|
||||
Authority string `json:"authority"`
|
||||
}
|
||||
|
||||
func (s *Service) cloudAgentModelContext(run *model.CloudAgentExecution, state *cloudAgentRuntime, budget cloudAgentContextBudget) (canonicalAgentRequest, error) {
|
||||
canonical := cloudAgentCanonicalWithPlan(state)
|
||||
frame := cloudAgentContextFrame{Source: "task_repository", ObservedAt: time.Now(), RunID: run.ID, UserGoal: state.Request.Prompt, Plan: state.Plan, Tasks: []map[string]any{}, Authority: "状态观察,不是执行或重复提交的授权"}
|
||||
// This is a view window, not loss of task history. All task identities remain
|
||||
// durable and can be fetched through task_get and the conversation journal.
|
||||
start := max(0, len(state.TaskIDs)-32)
|
||||
frame.OlderTaskCount = start
|
||||
for _, id := range state.TaskIDs[start:] {
|
||||
task, err := s.repo.TaskForUser(run.UserID, id)
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
frame.Tasks = append(frame.Tasks, map[string]any{"taskId": id, "taskStatus": "unavailable"})
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return canonical, err
|
||||
}
|
||||
if task.ProjectID != state.Request.CanvasID {
|
||||
return canonical, BadAuthRequest("运行关联任务不属于当前画布")
|
||||
}
|
||||
if task.Operation == cloudAgentOperation || task.Operation == "cloud_agent_step" {
|
||||
continue
|
||||
}
|
||||
frame.Tasks = append(frame.Tasks, cloudAgentTaskDiagnostic(s.repo, task))
|
||||
}
|
||||
body, err := json.Marshal(frame)
|
||||
if err != nil {
|
||||
return canonical, err
|
||||
}
|
||||
canonical.Messages = append(append([]map[string]any{}, canonical.Messages...), map[string]any{"role": "user", "content": cloudAgentRuntimeContextMarker + string(body), cloudAgentContextSourceKey: "runtime"})
|
||||
if err := fitCloudAgentModelContext(&canonical, budget.InputBudgetTokens); err != nil {
|
||||
return canonical, err
|
||||
}
|
||||
return canonical, nil
|
||||
}
|
||||
|
||||
func fitCloudAgentModelContext(request *canonicalAgentRequest, maxTokens int) error {
|
||||
for {
|
||||
tokens, err := cloudAgentRequestEstimatedTokens(request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tokens <= maxTokens {
|
||||
return nil
|
||||
}
|
||||
// Evict complete old assistant/tool turns only. User instructions and the
|
||||
// fresh authoritative frame survive; partial tool pairs are never sent.
|
||||
removed := false
|
||||
for i := 0; i < len(request.Messages)-3; i++ {
|
||||
message := request.Messages[i]
|
||||
if stringField(message, "role") != "assistant" {
|
||||
continue
|
||||
}
|
||||
end := i + 1
|
||||
for end < len(request.Messages) && stringField(request.Messages[end], "role") == "tool" {
|
||||
end++
|
||||
}
|
||||
if end >= len(request.Messages)-2 {
|
||||
continue
|
||||
}
|
||||
request.Messages = append(request.Messages[:i:i], request.Messages[end:]...)
|
||||
removed = true
|
||||
break
|
||||
}
|
||||
if !removed {
|
||||
return BadAuthRequest(fmt.Sprintf("用户指令与当前执行事实超过模型输入预算(%d Token),请缩小本轮范围", maxTokens))
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user