feat(ai): session persist + continue HTTP/UI, checkpoints, queue
落地 docs/16 P1:会话落盘与真续跑(prior tool 轨迹)、incomplete「加预算继续」、 awaiting_confirm outcome、同参拒+第4/6轮检查点、侧栏忙时单槽排队。 Ask 真 token 流式记缺口(三协议 SSE 成本高)。
This commit is contained in:
1 parent
1513f4bc2d
commit
d543f3b924
10 files changed
+783
-104
No files matched your search
+23
-12
@@ -59,6 +59,8 @@ type AgentRes struct {
|
||||
IncompleteReason string `json:"incompleteReason,omitempty"`
|
||||
Resumable bool `json:"resumable,omitempty"`
|
||||
StepsBudget int `json:"stepsBudget,omitempty"`
|
||||
// Trajectory 含 system/user/assistant.tool_calls/tool 的完整对话(供落盘续跑;JSON 默认省略大字段可选)
|
||||
Trajectory []Message `json:"trajectory,omitempty"`
|
||||
}
|
||||
|
||||
// RunAgent 多轮:LLM tool_call → aitools.Runtime.Call → 回灌。
|
||||
@@ -90,6 +92,11 @@ func runAgent(ctx context.Context, llm ToolChat, rt *aitools.Runtime, req AgentR
|
||||
if runID == "" {
|
||||
runID = NewRunID()
|
||||
}
|
||||
sessionID := strings.TrimSpace(req.SessionID)
|
||||
if sessionID == "" {
|
||||
sessionID = NewSessionID()
|
||||
req.SessionID = sessionID
|
||||
}
|
||||
rt.RunID = runID
|
||||
// Agent 路径禁止模型自行带确认令牌
|
||||
rt.RejectCallerConfirm = true
|
||||
@@ -119,13 +126,14 @@ func runAgent(ctx context.Context, llm ToolChat, rt *aitools.Runtime, req AgentR
|
||||
}
|
||||
}
|
||||
|
||||
baseMeta := func(steps []AgentStep, rounds int) AgentRes {
|
||||
finish := func(steps []AgentStep, rounds int, msgs []Message) AgentRes {
|
||||
return AgentRes{
|
||||
Steps: steps,
|
||||
Rounds: rounds,
|
||||
RunID: runID,
|
||||
SessionID: req.SessionID,
|
||||
SessionID: sessionID,
|
||||
StepsBudget: maxRounds,
|
||||
Trajectory: append([]Message{}, msgs...),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,19 +142,22 @@ func runAgent(ctx context.Context, llm ToolChat, rt *aitools.Runtime, req AgentR
|
||||
rounds := 0
|
||||
for round := 0; round < maxRounds; round++ {
|
||||
if err := ctx.Err(); err != nil {
|
||||
res := baseMeta(steps, rounds)
|
||||
res := finish(steps, rounds, msgs)
|
||||
res.Content = "已取消"
|
||||
res.Outcome = OutcomeCancelled
|
||||
res.Resumable = len(steps) > 0
|
||||
emitEvent(emit, AgentEvent{Type: AgentEventError, Error: "cancelled", Steps: steps, Rounds: rounds, RunID: runID, Outcome: OutcomeCancelled})
|
||||
emitEvent(emit, AgentEvent{Type: AgentEventError, Error: "cancelled", Steps: steps, Rounds: rounds, RunID: runID, SessionID: sessionID, Outcome: OutcomeCancelled})
|
||||
return res, err
|
||||
}
|
||||
rounds = round + 1
|
||||
if tip := CheckpointHint(rounds); tip != "" {
|
||||
msgs = append(msgs, Message{Role: "system", Content: tip})
|
||||
}
|
||||
turn, err := llm.ChatWithTools(ctx, req.Model, msgs, tools)
|
||||
if err != nil {
|
||||
res := baseMeta(steps, rounds)
|
||||
res := finish(steps, rounds, msgs)
|
||||
res.Outcome = OutcomeFailed
|
||||
emitEvent(emit, AgentEvent{Type: AgentEventError, Error: err.Error(), Steps: steps, Rounds: rounds, RunID: runID, Outcome: OutcomeFailed})
|
||||
emitEvent(emit, AgentEvent{Type: AgentEventError, Error: err.Error(), Steps: steps, Rounds: rounds, RunID: runID, SessionID: sessionID, Outcome: OutcomeFailed})
|
||||
return res, err
|
||||
}
|
||||
if len(turn.ToolCalls) == 0 {
|
||||
@@ -157,13 +168,13 @@ func runAgent(ctx context.Context, llm ToolChat, rt *aitools.Runtime, req AgentR
|
||||
for _, part := range chunkDelta(content, 36) {
|
||||
emitEvent(emit, AgentEvent{Type: AgentEventDelta, Delta: part})
|
||||
}
|
||||
res := baseMeta(steps, rounds)
|
||||
res := finish(steps, rounds, msgs)
|
||||
res.Content = content
|
||||
res.SQL = extractSQL(content)
|
||||
res.Outcome = OutcomeCompleted
|
||||
emitEvent(emit, AgentEvent{
|
||||
Type: AgentEventDone, Content: res.Content, SQL: res.SQL, Steps: steps, Rounds: rounds,
|
||||
RunID: runID, Outcome: OutcomeCompleted, StepsBudget: maxRounds,
|
||||
RunID: runID, SessionID: sessionID, Outcome: OutcomeCompleted, StepsBudget: maxRounds,
|
||||
})
|
||||
return res, nil
|
||||
}
|
||||
@@ -217,13 +228,13 @@ func runAgent(ctx context.Context, llm ToolChat, rt *aitools.Runtime, req AgentR
|
||||
}
|
||||
if awaiting {
|
||||
content := "需要确认写操作后才能继续。"
|
||||
res := baseMeta(steps, rounds)
|
||||
res := finish(steps, rounds, msgs)
|
||||
res.Content = content
|
||||
res.Outcome = OutcomeAwaitingConfirm
|
||||
res.Resumable = true
|
||||
emitEvent(emit, AgentEvent{
|
||||
Type: AgentEventDone, Content: content, Steps: steps, Rounds: rounds,
|
||||
RunID: runID, Outcome: OutcomeAwaitingConfirm, Resumable: true, StepsBudget: maxRounds,
|
||||
RunID: runID, SessionID: sessionID, Outcome: OutcomeAwaitingConfirm, Resumable: true, StepsBudget: maxRounds,
|
||||
})
|
||||
return res, nil
|
||||
}
|
||||
@@ -234,14 +245,14 @@ func runAgent(ctx context.Context, llm ToolChat, rt *aitools.Runtime, req AgentR
|
||||
for _, part := range chunkDelta(content, 36) {
|
||||
emitEvent(emit, AgentEvent{Type: AgentEventDelta, Delta: part})
|
||||
}
|
||||
res := baseMeta(steps, rounds)
|
||||
res := finish(steps, rounds, msgs)
|
||||
res.Content = content
|
||||
res.Outcome = OutcomeIncomplete
|
||||
res.IncompleteReason = ReasonMaxSteps
|
||||
res.Resumable = true
|
||||
emitEvent(emit, AgentEvent{
|
||||
Type: AgentEventIncomplete, Content: content, Steps: steps, Rounds: rounds,
|
||||
RunID: runID, Outcome: OutcomeIncomplete, IncompleteReason: ReasonMaxSteps,
|
||||
RunID: runID, SessionID: sessionID, Outcome: OutcomeIncomplete, IncompleteReason: ReasonMaxSteps,
|
||||
Resumable: true, StepsBudget: maxRounds,
|
||||
})
|
||||
return res, nil
|
||||
|
||||
Reference in new issue
Block a user