Version: 0.8.8.dev.260403
后端: 1.新建Deliver节点:LLM生成任务总结,失败降级到机械格式化,伪流式输出 2.新建Confirm节点:确认卡片推送与状态持久化 3.新建Interrupt节点:追问/确认/默认中断三种处理路径 4.实现状态持久化体系:model层定义AgentStateStore接口+AgentStateSnapshot快照,dao/cache.go新增Redis CRUD,agent_nodes层每节点自动存快照、Deliver完成后清理 5.所有model struct补充JSON tags,支持Redis序列化/反序列化 前端:无 仓库:无
This commit is contained in:
@@ -15,27 +15,27 @@ const DefaultMaxRounds = 30
|
||||
// CommonState 承载可持久化的主流程状态。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 负责记录“当前处于哪个阶段、当前计划是什么、执行到了第几步、已经消耗了多少轮”;
|
||||
// 1. 负责记录"当前处于哪个阶段、当前计划是什么、执行到了第几步、已经消耗了多少轮";
|
||||
// 2. 负责提供最小必要的安全访问方法,避免 graph/node/prompt 层到处手写切片越界判断;
|
||||
// 3. 不负责承载对话历史、tool schema、pinned context 这类模型输入材料,它们仍然属于 ConversationContext。
|
||||
type CommonState struct {
|
||||
// 身份信息
|
||||
TraceID string
|
||||
UserID int
|
||||
ConversationID string
|
||||
TraceID string `json:"trace_id"`
|
||||
UserID int `json:"user_id"`
|
||||
ConversationID string `json:"conversation_id"`
|
||||
|
||||
// 流程阶段
|
||||
Phase Phase
|
||||
Phase Phase `json:"phase"`
|
||||
|
||||
// 计划状态
|
||||
// 1. 这里直接使用结构化的 PlanStep,避免 planning -> execute 之间丢失 done_when。
|
||||
// 2. CurrentStep 表示“当前 plan 步骤下标”,不是 execute 内部 ReAct 的思考轮次。
|
||||
PlanSteps []PlanStep
|
||||
CurrentStep int
|
||||
// 2. CurrentStep 表示"当前 plan 步骤下标",不是 execute 内部 ReAct 的思考轮次。
|
||||
PlanSteps []PlanStep `json:"plan_steps"`
|
||||
CurrentStep int `json:"current_step"`
|
||||
|
||||
// 安全边界
|
||||
MaxRounds int
|
||||
RoundUsed int
|
||||
MaxRounds int `json:"max_rounds"`
|
||||
RoundUsed int `json:"round_used"`
|
||||
}
|
||||
|
||||
func NewCommonState(traceID string, userID int, conversationID string) *CommonState {
|
||||
@@ -97,7 +97,7 @@ func (s *CommonState) Done() {
|
||||
// HasPlan 判断当前 state 是否已经持有一份完整计划。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 负责收口“是否存在 plan”这一层判断,避免外层到处写 len(PlanSteps) > 0;
|
||||
// 1. 负责收口"是否存在 plan"这一层判断,避免外层到处写 len(PlanSteps) > 0;
|
||||
// 2. 不判断 CurrentStep 当前是否有效,当前步骤是否合法由 HasCurrentPlanStep 回答;
|
||||
// 3. state 为空时统一返回 false,调用方可据此决定是否回退到 planning。
|
||||
func (s *CommonState) HasPlan() bool {
|
||||
@@ -123,7 +123,7 @@ func (s *CommonState) CurrentPlanStep() (PlanStep, bool) {
|
||||
return s.PlanSteps[s.CurrentStep], true
|
||||
}
|
||||
|
||||
// HasCurrentPlanStep 判断“当前步骤”是否存在且可安全读取。
|
||||
// HasCurrentPlanStep 判断"当前步骤"是否存在且可安全读取。
|
||||
func (s *CommonState) HasCurrentPlanStep() bool {
|
||||
_, ok := s.CurrentPlanStep()
|
||||
return ok
|
||||
|
||||
@@ -6,45 +6,42 @@ import (
|
||||
"github.com/cloudwego/eino/schema"
|
||||
)
|
||||
|
||||
// ConversationContext 承载“本轮要喂给模型的输入材料”。
|
||||
// ConversationContext 承载"本轮要喂给模型的输入材料"。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 负责保存 system prompt、对话历史、置顶注入块、工具 schema 摘要;
|
||||
// 2. 负责提供最小必要的安全访问方法,避免 node / prompt 层直接散落切片操作;
|
||||
// 3. 不负责流程推进,phase / round / current step 仍归 CommonState 管;
|
||||
// 4. 不负责真正的 prompt 组装,消息如何拼接仍应放在 prompt 层处理。
|
||||
//
|
||||
// TODO(newagent/prompt): 后续由 plan / execute 的 prompt builder 读取这里的数据,组装真正发给 LLM 的 messages。
|
||||
// TODO(newagent/node): 后续 planNode / executeNode 只通过这里的访问方法读写上下文,避免多处直接改切片。
|
||||
type ConversationContext struct {
|
||||
SystemPrompt string
|
||||
History []*schema.Message
|
||||
PinnedBlocks []ContextBlock
|
||||
ToolSchemas []ToolSchemaContext
|
||||
SystemPrompt string `json:"system_prompt"`
|
||||
History []*schema.Message `json:"history"`
|
||||
PinnedBlocks []ContextBlock `json:"pinned_blocks"`
|
||||
ToolSchemas []ToolSchemaContext `json:"-"` // 每次请求由 Service 层重新注入,不持久化
|
||||
}
|
||||
|
||||
// ContextBlock 表示一段可被“置顶注入”的自然语言上下文。
|
||||
// ContextBlock 表示一段可被"置顶注入"的自然语言上下文。
|
||||
//
|
||||
// 设计目的:
|
||||
// 1. Key 用于让调用方按语义覆盖,例如 current_plan / current_step / execution_rule;
|
||||
// 2. Title 用于 prompt 层后续决定是否渲染成小标题;
|
||||
// 3. Content 存真正的自然语言内容,保持你当前“plan 用自然语言表达”的思路。
|
||||
// 3. Content 存真正的自然语言内容,保持你当前"plan 用自然语言表达"的思路。
|
||||
type ContextBlock struct {
|
||||
Key string
|
||||
Title string
|
||||
Content string
|
||||
Key string `json:"key"`
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
// ToolSchemaContext 是工具描述的轻量快照。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 这里只保留 prompt 注入真正需要的摘要信息;
|
||||
// 2. SchemaText 约定存“已经整理好的自然语言 / JSON schema 摘要”;
|
||||
// 2. SchemaText 约定存"已经整理好的自然语言 / JSON schema 摘要";
|
||||
// 3. 不直接耦合具体 tool registry 里的复杂结构,避免 model 层反向依赖工具实现。
|
||||
type ToolSchemaContext struct {
|
||||
Name string
|
||||
Desc string
|
||||
SchemaText string
|
||||
Name string `json:"name"`
|
||||
Desc string `json:"desc"`
|
||||
SchemaText string `json:"schema_text"`
|
||||
}
|
||||
|
||||
// NewConversationContext 创建最小上下文容器。
|
||||
@@ -65,7 +62,7 @@ func (c *ConversationContext) SetSystemPrompt(systemPrompt string) {
|
||||
// ReplaceHistory 整体替换对话历史。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 负责把“会话快照恢复”这类场景需要的一次性覆盖入口收口到这里;
|
||||
// 1. 负责把"会话快照恢复"这类场景需要的一次性覆盖入口收口到这里;
|
||||
// 2. 只复制消息切片本身,避免调用方后续 append 污染同一底层数组;
|
||||
// 3. 不深拷贝每个 message 指针,消息对象本身仍默认由上游按只读方式使用。
|
||||
func (c *ConversationContext) ReplaceHistory(history []*schema.Message) {
|
||||
@@ -105,7 +102,7 @@ func (c *ConversationContext) HistorySnapshot() []*schema.Message {
|
||||
//
|
||||
// 步骤说明:
|
||||
// 1. Key 为空时直接忽略,因为后续无法做稳定覆盖;
|
||||
// 2. 若已存在同 Key block,则原位覆盖,保证“当前 plan / 当前步骤”这类上下文始终只有一份;
|
||||
// 2. 若已存在同 Key block,则原位覆盖,保证"当前 plan / 当前步骤"这类上下文始终只有一份;
|
||||
// 3. 若不存在,则追加到末尾,至于渲染顺序由 prompt 层统一决定;
|
||||
// 4. 此处不自动裁剪旧内容,避免 model 层擅自丢信息。
|
||||
func (c *ConversationContext) UpsertPinnedBlock(block ContextBlock) {
|
||||
|
||||
@@ -39,6 +39,7 @@ type AgentGraphDeps struct {
|
||||
ExecuteClient *newagentllm.Client
|
||||
DeliverClient *newagentllm.Client
|
||||
ChunkEmitter *newagentstream.ChunkEmitter
|
||||
StateStore AgentStateStore
|
||||
}
|
||||
|
||||
// EnsureChunkEmitter 保证 graph 运行时始终有一个可用的 chunk 发射器。
|
||||
|
||||
@@ -6,7 +6,7 @@ const (
|
||||
// PhaseChatting 表示当前请求只需正常聊天,不进入 plan / execute 主链路。
|
||||
PhaseChatting Phase = "chatting"
|
||||
|
||||
// PhaseInterrupted 表示本轮执行被“待用户交互”显式打断,当前连接应结束并等待恢复。
|
||||
// PhaseInterrupted 表示本轮执行被"待用户交互"显式打断,当前连接应结束并等待恢复。
|
||||
PhaseInterrupted Phase = "interrupted"
|
||||
)
|
||||
|
||||
@@ -30,49 +30,52 @@ const (
|
||||
PendingInteractionStatusCanceled PendingInteractionStatus = "canceled"
|
||||
)
|
||||
|
||||
// PendingToolCallSnapshot 保存“待确认工具调用”的最小快照。
|
||||
// PendingToolCallSnapshot 保存"待确认工具调用"的最小快照。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 负责保存真正落库 / 落缓存恢复执行所需的最小信息;
|
||||
// 2. ArgsJSON 约定存已经序列化好的参数快照,避免此处反向依赖具体 tool 参数结构;
|
||||
// 3. 不负责工具执行,不负责幂等校验,不负责回滚。
|
||||
type PendingToolCallSnapshot struct {
|
||||
ToolName string
|
||||
ArgsJSON string
|
||||
Summary string
|
||||
ToolName string `json:"tool_name"`
|
||||
ArgsJSON string `json:"args_json"`
|
||||
Summary string `json:"summary"`
|
||||
}
|
||||
|
||||
// PendingInteraction 保存“本轮需要中断并等待用户后续动作”的交互快照。
|
||||
// PendingInteraction 保存"本轮需要中断并等待用户后续动作"的交互快照。
|
||||
//
|
||||
// 设计目的:
|
||||
// 1. ask_user 与 confirm 都不是业务 tool,而是流程级中断,所以单独建模;
|
||||
// 2. ResumeNode / ResumePhase / ResumeStep 用来记录恢复点,避免用户回答后整条链路从头乱跑;
|
||||
// 3. 该结构设计成可被 Redis + MySQL 直接存储的快照骨架,后续只需要补序列化与持久化接线。
|
||||
//
|
||||
// TODO(newagent/store): 后续把该结构整体快照到 Redis + MySQL,形成双保险恢复点。
|
||||
// TODO(newagent/api): 后续由“用户追问回复接口 / 确认回调接口”读取这份快照并恢复运行。
|
||||
// TODO(newagent/api): 后续由"用户追问回复接口 / 确认回调接口"读取这份快照并恢复运行。
|
||||
type PendingInteraction struct {
|
||||
Version int
|
||||
InteractionID string
|
||||
Type PendingInteractionType
|
||||
Status PendingInteractionStatus
|
||||
DisplayText string
|
||||
ResumeNode string
|
||||
ResumePhase Phase
|
||||
ResumeStep int
|
||||
PendingTool *PendingToolCallSnapshot
|
||||
Metadata map[string]any
|
||||
Version int `json:"version"`
|
||||
InteractionID string `json:"interaction_id"`
|
||||
Type PendingInteractionType `json:"type"`
|
||||
Status PendingInteractionStatus `json:"status"`
|
||||
DisplayText string `json:"display_text"`
|
||||
ResumeNode string `json:"resume_node"`
|
||||
ResumePhase Phase `json:"resume_phase"`
|
||||
ResumeStep int `json:"resume_step"`
|
||||
PendingTool *PendingToolCallSnapshot `json:"pending_tool,omitempty"`
|
||||
Metadata map[string]any `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
// AgentRuntimeState 是 graph 运行时真正流转的状态容器。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. CommonState 继续只负责主流程控制;
|
||||
// 2. PendingInteraction 负责承载“需要中断后恢复”的交互快照;
|
||||
// 2. PendingInteraction 负责承载"需要中断后恢复"的交互快照;
|
||||
// 3. 这样既不污染 CommonState 的职责,又能让 graph 在一次入参里拿到完整运行态。
|
||||
type AgentRuntimeState struct {
|
||||
*CommonState
|
||||
PendingInteraction *PendingInteraction
|
||||
*CommonState `json:"common_state"`
|
||||
// PendingInteraction 承载挂起交互的持久化快照。
|
||||
PendingInteraction *PendingInteraction `json:"pending_interaction,omitempty"`
|
||||
// PendingConfirmTool 是 Execute → Confirm 之间传递待确认工具信息的临时邮箱。
|
||||
// Execute 节点写入,Confirm 节点读出并清空,不参与持久化。
|
||||
PendingConfirmTool *PendingToolCallSnapshot `json:"-"`
|
||||
}
|
||||
|
||||
// NewAgentRuntimeState 创建 graph 运行态。
|
||||
@@ -120,7 +123,7 @@ func (s *AgentRuntimeState) PendingInteractionType() PendingInteractionType {
|
||||
return s.PendingInteraction.Type
|
||||
}
|
||||
|
||||
// OpenAskUserInteraction 打开一个“向用户追问”的中断快照。
|
||||
// OpenAskUserInteraction 打开一个"向用户追问"的中断快照。
|
||||
func (s *AgentRuntimeState) OpenAskUserInteraction(interactionID, question, resumeNode string) {
|
||||
s.openPendingInteraction(
|
||||
PendingInteractionTypeAskUser,
|
||||
@@ -131,7 +134,7 @@ func (s *AgentRuntimeState) OpenAskUserInteraction(interactionID, question, resu
|
||||
)
|
||||
}
|
||||
|
||||
// OpenConfirmInteraction 打开一个“写操作待确认”的中断快照。
|
||||
// OpenConfirmInteraction 打开一个"写操作待确认"的中断快照。
|
||||
func (s *AgentRuntimeState) OpenConfirmInteraction(interactionID, confirmText, resumeNode string, pendingTool *PendingToolCallSnapshot) {
|
||||
s.openPendingInteraction(
|
||||
PendingInteractionTypeConfirm,
|
||||
@@ -166,7 +169,7 @@ func (s *AgentRuntimeState) ResumeFromPending() bool {
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 仅负责粗暴清空快照;
|
||||
// 2. 不自动恢复 phase / step,避免误把“取消交互”与“恢复执行”混为一谈;
|
||||
// 2. 不自动恢复 phase / step,避免误把"取消交互"与"恢复执行"混为一谈;
|
||||
// 3. 若需要恢复流程,应优先使用 ResumeFromPending。
|
||||
func (s *AgentRuntimeState) ClearPendingInteraction() {
|
||||
if s == nil || s.PendingInteraction == nil {
|
||||
@@ -207,7 +210,7 @@ func (s *AgentRuntimeState) openPendingInteraction(
|
||||
|
||||
// 1. 一旦进入 pending 状态,当前连接上的 graph 应立即停止向后执行。
|
||||
// 2. 这里先统一把 Phase 置为 interrupted,后续恢复时再按快照写回原阶段。
|
||||
// 3. 这样分支函数只需要判断 HasPendingInteraction(),无需猜测“当前 phase 是否仍可信”。
|
||||
// 3. 这样分支函数只需要判断 HasPendingInteraction(),无需猜测"当前 phase 是否仍可信"。
|
||||
flowState.Phase = PhaseInterrupted
|
||||
}
|
||||
|
||||
|
||||
49
backend/newAgent/model/state_store.go
Normal file
49
backend/newAgent/model/state_store.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package model
|
||||
|
||||
import "context"
|
||||
|
||||
// AgentStateSnapshot 是需要持久化的 agent 运行态最小快照。
|
||||
//
|
||||
// 设计说明:
|
||||
// 1. 只保存恢复执行所需的 RuntimeState 和 ConversationContext;
|
||||
// 2. 不保存 Request(每轮请求级,天然不跨连接);
|
||||
// 3. 不保存 Deps(依赖注入,每次由 Service 层重建);
|
||||
// 4. 不保存 ToolSchemas(每次请求由 Service 层重新注入)。
|
||||
type AgentStateSnapshot struct {
|
||||
RuntimeState *AgentRuntimeState `json:"runtime_state"`
|
||||
ConversationContext *ConversationContext `json:"conversation_context"`
|
||||
}
|
||||
|
||||
// AgentStateStore 定义 agent 状态持久化的最小接口。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 只负责"存 / 取 / 删"三个原子操作;
|
||||
// 2. 不负责序列化细节(由实现层决定 JSON / protobuf);
|
||||
// 3. 不负责业务级状态校验,校验仍在 node / graph 层完成。
|
||||
//
|
||||
// 实现层:
|
||||
// 1. dao/cache.go 上的 CacheDAO 隐式实现该接口(Go duck typing);
|
||||
// 2. newAgent 包不直接 import dao,由 Service 层在组装 Deps 时注入。
|
||||
type AgentStateStore interface {
|
||||
// Save 序列化并保存一份 agent 状态快照。
|
||||
//
|
||||
// 语义:
|
||||
// 1. 同一 conversationID 被覆盖写入,保证 Redis 里始终只有最新快照;
|
||||
// 2. 实现层应设 TTL,避免已完成的任务快照永不清理。
|
||||
Save(ctx context.Context, conversationID string, snapshot *AgentStateSnapshot) error
|
||||
|
||||
// Load 读取并反序列化 agent 状态快照。
|
||||
//
|
||||
// 返回值语义:
|
||||
// 1. (snapshot, true, nil):命中快照,正常返回;
|
||||
// 2. (nil, false, nil):未命中,不是错误,调用方应走新建对话路径;
|
||||
// 3. (nil, false, error):真正的存储层错误。
|
||||
Load(ctx context.Context, conversationID string) (*AgentStateSnapshot, bool, error)
|
||||
|
||||
// Delete 删除指定会话的 agent 状态快照。
|
||||
//
|
||||
// 语义:
|
||||
// 1. 删除是幂等的,key 不存在也视为成功;
|
||||
// 2. 典型调用时机:Deliver 节点任务完成后清理。
|
||||
Delete(ctx context.Context, conversationID string) error
|
||||
}
|
||||
Reference in New Issue
Block a user