Version: 0.8.6.dev.260401
后端: 新建了chat和execute节点,完善了相关逻辑,代码尚未review 前端: 无 仓库: 无
This commit is contained in:
@@ -35,7 +35,7 @@ func RunAgentGraph(ctx context.Context, input newagentmodel.AgentGraphRunInput)
|
||||
g := compose.NewGraph[*newagentmodel.AgentGraphState, *newagentmodel.AgentGraphState]()
|
||||
|
||||
// --- 注册节点 ---
|
||||
if err := g.AddLambdaNode(NodeChat, compose.InvokableLambda(chatNode)); err != nil {
|
||||
if err := g.AddLambdaNode(NodeChat, compose.InvokableLambda(nodes.Chat)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := g.AddLambdaNode(NodePlan, compose.InvokableLambda(nodes.Plan)); err != nil {
|
||||
@@ -44,7 +44,7 @@ func RunAgentGraph(ctx context.Context, input newagentmodel.AgentGraphRunInput)
|
||||
if err := g.AddLambdaNode(NodeConfirm, compose.InvokableLambda(confirmNode)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := g.AddLambdaNode(NodeExecute, compose.InvokableLambda(executeNode)); err != nil {
|
||||
if err := g.AddLambdaNode(NodeExecute, compose.InvokableLambda(nodes.Execute)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := g.AddLambdaNode(NodeInterrupt, compose.InvokableLambda(interruptNode)); err != nil {
|
||||
@@ -56,11 +56,11 @@ func RunAgentGraph(ctx context.Context, input newagentmodel.AgentGraphRunInput)
|
||||
|
||||
// --- 连边 ---
|
||||
// 1. 所有请求统一先过 chat 入口,这样普通聊天、首次任务、恢复执行都走同一入口。
|
||||
// 2. chat 不再负责旧式“多业务图路由”,只负责决定后续应该进入哪个统一节点。
|
||||
// 2. chat 不再负责旧式多业务图路由,只负责决定后续应该进入哪个统一节点。
|
||||
if err := g.AddEdge(compose.START, NodeChat); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Chat → END(普通聊天) / Plan / Confirm / Execute / Deliver / Interrupt
|
||||
// Chat -> END(普通聊天) / Plan / Confirm / Execute / Deliver / Interrupt
|
||||
if err := g.AddBranch(NodeChat, compose.NewGraphBranch(
|
||||
branchAfterChat,
|
||||
map[string]bool{
|
||||
@@ -74,7 +74,7 @@ func RunAgentGraph(ctx context.Context, input newagentmodel.AgentGraphRunInput)
|
||||
)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Plan → Plan(继续规划) / Confirm(规划完成) / Interrupt(需要追问用户)
|
||||
// Plan -> Plan(继续规划) / Confirm(规划完成) / Interrupt(需要追问用户)
|
||||
if err := g.AddBranch(NodePlan, compose.NewGraphBranch(
|
||||
branchAfterPlan,
|
||||
map[string]bool{
|
||||
@@ -85,7 +85,7 @@ func RunAgentGraph(ctx context.Context, input newagentmodel.AgentGraphRunInput)
|
||||
)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Confirm → Plan(用户拒绝或重规划) / Execute(确认后继续执行) / Interrupt(产出确认中断并等待外部回调)
|
||||
// Confirm -> Plan(用户拒绝或重规划) / Execute(确认后继续执行) / Interrupt(产出确认中断并等待外部回调)
|
||||
if err := g.AddBranch(NodeConfirm, compose.NewGraphBranch(
|
||||
branchAfterConfirm,
|
||||
map[string]bool{
|
||||
@@ -96,7 +96,7 @@ func RunAgentGraph(ctx context.Context, input newagentmodel.AgentGraphRunInput)
|
||||
)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Execute → Execute(继续 ReAct) / Confirm(写操作待确认) / Deliver(完成) / Interrupt(需要追问用户)
|
||||
// Execute -> Execute(继续 ReAct) / Confirm(写操作待确认) / Deliver(完成) / Interrupt(需要追问用户)
|
||||
if err := g.AddBranch(NodeExecute, compose.NewGraphBranch(
|
||||
branchAfterExecute,
|
||||
map[string]bool{
|
||||
@@ -108,11 +108,11 @@ func RunAgentGraph(ctx context.Context, input newagentmodel.AgentGraphRunInput)
|
||||
)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Interrupt → END:当前连接必须在这里收口,等待用户输入或确认回调恢复。
|
||||
// Interrupt -> END:当前连接必须在这里收口,等待用户输入或确认回调恢复。
|
||||
if err := g.AddEdge(NodeInterrupt, compose.END); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Deliver → END
|
||||
// Deliver -> END
|
||||
if err := g.AddEdge(NodeDeliver, compose.END); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -132,23 +132,6 @@ func RunAgentGraph(ctx context.Context, input newagentmodel.AgentGraphRunInput)
|
||||
|
||||
// --- 占位节点,后续逐步由 node 层替换 ---
|
||||
|
||||
func chatNode(_ context.Context, st *newagentmodel.AgentGraphState) (*newagentmodel.AgentGraphState, error) {
|
||||
if st == nil {
|
||||
return nil, errors.New("chat node: state is nil")
|
||||
}
|
||||
st.EnsureFlowState()
|
||||
st.EnsureConversationContext()
|
||||
st.EnsureChunkEmitter()
|
||||
|
||||
// TODO:
|
||||
// 1. 识别当前请求是普通聊天、首次任务进入,还是从 pending interaction 恢复。
|
||||
// 2. 若只是普通聊天,则生成回复并把 Phase 设为 PhaseChatting,后续直接 END。
|
||||
// 3. 若识别到任务意图,则把 Phase 切到 planning / waiting_confirm / executing 对应阶段。
|
||||
// 4. 若本轮是恢复请求,则这里只负责吞掉用户最新输入并准备恢复,不再重复输出闲聊回复。
|
||||
// 5. 后续 chatNode 可直接读取 st.Request.UserInput、st.ConversationContext 与 st.Deps.ResolveChatClient()。
|
||||
return st, nil
|
||||
}
|
||||
|
||||
func confirmNode(_ context.Context, st *newagentmodel.AgentGraphState) (*newagentmodel.AgentGraphState, error) {
|
||||
if st == nil {
|
||||
return nil, errors.New("confirm node: state is nil")
|
||||
@@ -158,34 +141,13 @@ func confirmNode(_ context.Context, st *newagentmodel.AgentGraphState) (*newagen
|
||||
st.EnsureChunkEmitter()
|
||||
|
||||
// TODO:
|
||||
// 1. 这里不再做“confirm 节点内自循环等待”,而是统一走中断恢复模式。
|
||||
// 1. 这里不再做 confirm 节点内自循环等待,而是统一走中断恢复模式。
|
||||
// 2. 节点职责是生成确认事件、固化待执行工具快照,并调用 st.OpenConfirmInteraction(...)。
|
||||
// 3. 当前连接随后会流向 interrupt 节点收口;用户确认/取消后,由外部回调恢复到 executing 或 planning。
|
||||
// 4. 这里不要直接执行写工具,必须先把待执行工具调用固化为 pending snapshot。
|
||||
return st, nil
|
||||
}
|
||||
|
||||
func executeNode(_ context.Context, st *newagentmodel.AgentGraphState) (*newagentmodel.AgentGraphState, error) {
|
||||
if st == nil {
|
||||
return nil, errors.New("execute node: state is nil")
|
||||
}
|
||||
flowState := st.EnsureFlowState()
|
||||
st.EnsureConversationContext()
|
||||
st.EnsureChunkEmitter()
|
||||
|
||||
// TODO:
|
||||
// 1. 让 LLM 在“当前步骤”约束下做一轮 ReAct:思考 → 调工具/观察 → reflection。
|
||||
// 1.1 执行阶段所需上下文应直接从 st.ConversationContext 读取。
|
||||
// 1.2 执行阶段模型依赖应通过 st.Deps.ResolveExecuteClient() 获取。
|
||||
// 2. 若执行中发现缺少关键用户信息,则调用 st.OpenAskUserInteraction(...) 并走 interrupt。
|
||||
// 3. 若命中写工具确认闸门:
|
||||
// 3.1 若走同连接确认,则把 Phase 置为 waiting_confirm 并跳到 confirm;
|
||||
// 3.2 若走短连接恢复,则调用 st.OpenConfirmInteraction(...) 并走 interrupt。
|
||||
// 4. 若当前步骤已完成,则由 node 层决定是 AdvanceStep() 继续,还是 Done() 进入交付。
|
||||
flowState.NextRound()
|
||||
return st, nil
|
||||
}
|
||||
|
||||
func interruptNode(_ context.Context, st *newagentmodel.AgentGraphState) (*newagentmodel.AgentGraphState, error) {
|
||||
if st == nil {
|
||||
return nil, errors.New("interrupt node: state is nil")
|
||||
|
||||
@@ -14,7 +14,8 @@ import (
|
||||
// 2. 不负责承载可持久化流程状态,流程状态仍归 AgentRuntimeState;
|
||||
// 3. 不负责承载 LLM / emitter / store 等依赖,这些统一放进 AgentGraphDeps。
|
||||
type AgentGraphRequest struct {
|
||||
UserInput string
|
||||
UserInput string
|
||||
ConfirmAction string // "accept" / "reject" / "",仅 confirm 恢复场景由前端传入
|
||||
}
|
||||
|
||||
// Normalize 统一清洗请求级输入中的字符串字段。
|
||||
@@ -23,6 +24,7 @@ func (r *AgentGraphRequest) Normalize() {
|
||||
return
|
||||
}
|
||||
r.UserInput = strings.TrimSpace(r.UserInput)
|
||||
r.ConfirmAction = strings.TrimSpace(r.ConfirmAction)
|
||||
}
|
||||
|
||||
// AgentGraphDeps 描述 graph/node 层运行时真正依赖的可插拔能力。
|
||||
|
||||
@@ -20,6 +20,33 @@ func NewAgentNodes() *AgentNodes {
|
||||
return &AgentNodes{}
|
||||
}
|
||||
|
||||
// Chat 是聊天入口的正式节点方法。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 这里只做 graph -> node 的参数转接;
|
||||
// 2. 真正的入口逻辑仍由 RunChatNode 负责;
|
||||
// 3. 这样 graph 层后续只需挂 n.Chat,而不再自己维护占位 chatNode。
|
||||
func (n *AgentNodes) Chat(ctx context.Context, st *newagentmodel.AgentGraphState) (*newagentmodel.AgentGraphState, error) {
|
||||
if st == nil {
|
||||
return nil, errors.New("chat node: state is nil")
|
||||
}
|
||||
|
||||
if err := RunChatNode(
|
||||
ctx,
|
||||
ChatNodeInput{
|
||||
RuntimeState: st.EnsureRuntimeState(),
|
||||
ConversationContext: st.EnsureConversationContext(),
|
||||
UserInput: st.Request.UserInput,
|
||||
ConfirmAction: st.Request.ConfirmAction,
|
||||
Client: st.Deps.ResolveChatClient(),
|
||||
ChunkEmitter: st.EnsureChunkEmitter(),
|
||||
},
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return st, nil
|
||||
}
|
||||
|
||||
// Plan 是规划阶段的正式节点方法。
|
||||
//
|
||||
// 职责边界:
|
||||
@@ -46,3 +73,35 @@ func (n *AgentNodes) Plan(ctx context.Context, st *newagentmodel.AgentGraphState
|
||||
}
|
||||
return st, nil
|
||||
}
|
||||
|
||||
// Execute 是执行阶段的正式节点方法。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 这里只做 graph -> node 的参数转接;
|
||||
// 2. 真正的单轮执行逻辑仍由 RunExecuteNode 负责;
|
||||
// 3. 这样 graph 层后续只需挂 n.Execute,而不再自己维护占位 executeNode。
|
||||
//
|
||||
// 设计原则:
|
||||
// 1. LLM 主导:LLM 自己判断 done_when 是否满足,自己决定何时推进/完成;
|
||||
// 2. 后端兜底:只做资源控制、安全兜底、证据记录;
|
||||
// 3. 不做硬校验:后端不质疑 LLM 的 advance/complete 决策。
|
||||
func (n *AgentNodes) Execute(ctx context.Context, st *newagentmodel.AgentGraphState) (*newagentmodel.AgentGraphState, error) {
|
||||
if st == nil {
|
||||
return nil, errors.New("execute node: state is nil")
|
||||
}
|
||||
|
||||
if err := RunExecuteNode(
|
||||
ctx,
|
||||
ExecuteNodeInput{
|
||||
RuntimeState: st.EnsureRuntimeState(),
|
||||
ConversationContext: st.EnsureConversationContext(),
|
||||
UserInput: st.Request.UserInput,
|
||||
Client: st.Deps.ResolveExecuteClient(),
|
||||
ChunkEmitter: st.EnsureChunkEmitter(),
|
||||
ResumeNode: "execute",
|
||||
},
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return st, nil
|
||||
}
|
||||
|
||||
261
backend/newAgent/node/chat.go
Normal file
261
backend/newAgent/node/chat.go
Normal file
@@ -0,0 +1,261 @@
|
||||
package newagentnode
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cloudwego/eino/schema"
|
||||
|
||||
newagentllm "github.com/LoveLosita/smartflow/backend/newAgent/llm"
|
||||
newagentmodel "github.com/LoveLosita/smartflow/backend/newAgent/model"
|
||||
newagentprompt "github.com/LoveLosita/smartflow/backend/newAgent/prompt"
|
||||
newagentstream "github.com/LoveLosita/smartflow/backend/newAgent/stream"
|
||||
)
|
||||
|
||||
const (
|
||||
chatStageName = "chat"
|
||||
chatStatusBlockID = "chat.status"
|
||||
chatSpeakBlockID = "chat.speak"
|
||||
)
|
||||
|
||||
// ChatNodeInput 描述聊天节点单轮运行所需的最小依赖。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 只承载"本轮 chat"需要的输入,不负责持久化;
|
||||
// 2. RuntimeState 提供 pending interaction 与流程状态;
|
||||
// 3. ConversationContext 提供历史对话;
|
||||
// 4. ConfirmAction 仅在 confirm 恢复场景下由前端传入 "accept" / "reject"。
|
||||
type ChatNodeInput struct {
|
||||
RuntimeState *newagentmodel.AgentRuntimeState
|
||||
ConversationContext *newagentmodel.ConversationContext
|
||||
UserInput string
|
||||
ConfirmAction string
|
||||
Client *newagentllm.Client
|
||||
ChunkEmitter *newagentstream.ChunkEmitter
|
||||
}
|
||||
|
||||
// chatIntentDecision 是意图分类的结构化输出。
|
||||
type chatIntentDecision struct {
|
||||
Intent string `json:"intent"`
|
||||
Reply string `json:"reply,omitempty"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
}
|
||||
|
||||
// Normalize 清洗意图分类结果中的字符串字段。
|
||||
func (d *chatIntentDecision) Normalize() {
|
||||
if d == nil {
|
||||
return
|
||||
}
|
||||
d.Intent = strings.TrimSpace(d.Intent)
|
||||
d.Reply = strings.TrimSpace(d.Reply)
|
||||
d.Reason = strings.TrimSpace(d.Reason)
|
||||
}
|
||||
|
||||
// Validate 校验意图分类结果的最小合法性。
|
||||
func (d *chatIntentDecision) Validate() error {
|
||||
if d == nil {
|
||||
return fmt.Errorf("chat intent decision 不能为空")
|
||||
}
|
||||
d.Normalize()
|
||||
switch d.Intent {
|
||||
case "chat", "task":
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("未知 intent: %s", d.Intent)
|
||||
}
|
||||
}
|
||||
|
||||
// RunChatNode 执行一轮聊天节点逻辑。
|
||||
//
|
||||
// 核心职责:
|
||||
// 1. 恢复判定:有 pending interaction 则处理恢复,不生成 speak;
|
||||
// 2. 意图分流:无 pending 时,调 LLM 分类 chat / task;
|
||||
// 3. 闲聊回复:纯 chat 场景直接生成回复并流式推送,phase → chatting → END;
|
||||
// 4. 任务路由:task 场景 phase → planning,交给后续 Plan 节点处理。
|
||||
//
|
||||
// 保守原则:分类失败或意图不明时,一律走 task,不丢失用户意图。
|
||||
func RunChatNode(ctx context.Context, input ChatNodeInput) error {
|
||||
runtimeState, conversationContext, emitter, err := prepareChatNodeInput(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 1. 有 pending interaction → 纯状态传递,不生成 speak。
|
||||
if runtimeState.HasPendingInteraction() {
|
||||
return handleChatResume(input, runtimeState, conversationContext, emitter)
|
||||
}
|
||||
|
||||
// 2. 无 pending → 调 LLM 做意图分类。
|
||||
messages := newagentprompt.BuildChatIntentMessages(conversationContext, input.UserInput)
|
||||
decision, _, err := newagentllm.GenerateJSON[chatIntentDecision](
|
||||
ctx,
|
||||
input.Client,
|
||||
messages,
|
||||
newagentllm.GenerateOptions{
|
||||
Temperature: 0.1,
|
||||
MaxTokens: 300,
|
||||
Thinking: newagentllm.ThinkingModeDisabled,
|
||||
},
|
||||
)
|
||||
if err != nil || decision.Validate() != nil {
|
||||
// 分类失败 → 保守:走 task。
|
||||
runtimeState.EnsureCommonState().Phase = newagentmodel.PhasePlanning
|
||||
return nil
|
||||
}
|
||||
|
||||
// 3. 按意图分流。
|
||||
flowState := runtimeState.EnsureCommonState()
|
||||
switch decision.Intent {
|
||||
case "task":
|
||||
flowState.Phase = newagentmodel.PhasePlanning
|
||||
return nil
|
||||
case "chat":
|
||||
return handleChatReply(ctx, decision, conversationContext, emitter, flowState)
|
||||
default:
|
||||
flowState.Phase = newagentmodel.PhasePlanning
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// handleChatResume 处理 pending interaction 恢复。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 只做状态传递:吞掉用户输入、写回历史、恢复 phase;
|
||||
// 2. 不生成 speak,真正的回复由下游 Plan / Execute 节点产出;
|
||||
// 3. 只推送轻量 status 通知前端"已收到回复,正在继续"。
|
||||
func handleChatResume(
|
||||
input ChatNodeInput,
|
||||
runtimeState *newagentmodel.AgentRuntimeState,
|
||||
conversationContext *newagentmodel.ConversationContext,
|
||||
emitter *newagentstream.ChunkEmitter,
|
||||
) error {
|
||||
pending := runtimeState.PendingInteraction
|
||||
flowState := runtimeState.EnsureCommonState()
|
||||
|
||||
// 把用户本轮输入写回历史(ask_user 回复、confirm 附言等)。
|
||||
if strings.TrimSpace(input.UserInput) != "" {
|
||||
conversationContext.AppendHistory(schema.UserMessage(input.UserInput))
|
||||
}
|
||||
|
||||
switch pending.Type {
|
||||
case newagentmodel.PendingInteractionTypeAskUser:
|
||||
// 用户回答了问题 → 恢复 phase,交给下游节点继续。
|
||||
runtimeState.ResumeFromPending()
|
||||
_ = emitter.EmitStatus(
|
||||
chatStatusBlockID, chatStageName,
|
||||
"resumed", "收到回复,继续处理。", false,
|
||||
)
|
||||
return nil
|
||||
|
||||
case newagentmodel.PendingInteractionTypeConfirm:
|
||||
return handleConfirmResume(input, runtimeState, flowState, pending, emitter)
|
||||
|
||||
default:
|
||||
// connection_lost 等其他类型 → 直接恢复。
|
||||
runtimeState.ResumeFromPending()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// handleConfirmResume 处理 confirm 类型恢复。
|
||||
//
|
||||
// 分支逻辑:
|
||||
// 1. accept → 恢复后 phase 设为 executing,下游 Execute 节点接管;
|
||||
// 2. reject + 有 PendingTool(工具确认)→ 回到 executing 让 Execute 节点换策略;
|
||||
// 3. reject + 无 PendingTool(计划确认)→ 清空计划,回到 planning 重新规划。
|
||||
func handleConfirmResume(
|
||||
input ChatNodeInput,
|
||||
runtimeState *newagentmodel.AgentRuntimeState,
|
||||
flowState *newagentmodel.CommonState,
|
||||
pending *newagentmodel.PendingInteraction,
|
||||
emitter *newagentstream.ChunkEmitter,
|
||||
) error {
|
||||
action := strings.ToLower(strings.TrimSpace(input.ConfirmAction))
|
||||
|
||||
switch action {
|
||||
case "accept":
|
||||
runtimeState.ResumeFromPending()
|
||||
flowState.Phase = newagentmodel.PhaseExecuting
|
||||
_ = emitter.EmitStatus(
|
||||
chatStatusBlockID, chatStageName,
|
||||
"confirmed", "已确认,开始执行。", false,
|
||||
)
|
||||
|
||||
case "reject":
|
||||
runtimeState.ResumeFromPending()
|
||||
if pending.PendingTool != nil {
|
||||
// 工具确认被拒 → 回到 executing 换策略。
|
||||
flowState.Phase = newagentmodel.PhaseExecuting
|
||||
} else {
|
||||
// 计划确认被拒 → 清空计划,回到 planning。
|
||||
flowState.RejectPlan()
|
||||
}
|
||||
_ = emitter.EmitStatus(
|
||||
chatStatusBlockID, chatStageName,
|
||||
"rejected", "已取消,准备重新规划。", false,
|
||||
)
|
||||
|
||||
default:
|
||||
// 无合法 confirm action → 保守:等同于 reject。
|
||||
runtimeState.ResumeFromPending()
|
||||
if pending.PendingTool != nil {
|
||||
flowState.Phase = newagentmodel.PhaseExecuting
|
||||
} else {
|
||||
flowState.RejectPlan()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleChatReply 处理纯闲聊意图 — 把分类时产出的 reply 流式推给前端。
|
||||
func handleChatReply(
|
||||
ctx context.Context,
|
||||
decision *chatIntentDecision,
|
||||
conversationContext *newagentmodel.ConversationContext,
|
||||
emitter *newagentstream.ChunkEmitter,
|
||||
flowState *newagentmodel.CommonState,
|
||||
) error {
|
||||
reply := strings.TrimSpace(decision.Reply)
|
||||
|
||||
if reply != "" {
|
||||
if err := emitter.EmitPseudoAssistantText(
|
||||
ctx, chatSpeakBlockID, chatStageName,
|
||||
reply,
|
||||
newagentstream.DefaultPseudoStreamOptions(),
|
||||
); err != nil {
|
||||
return fmt.Errorf("闲聊回复推送失败: %w", err)
|
||||
}
|
||||
conversationContext.AppendHistory(schema.AssistantMessage(reply, nil))
|
||||
}
|
||||
|
||||
flowState.Phase = newagentmodel.PhaseChatting
|
||||
return nil
|
||||
}
|
||||
|
||||
// prepareChatNodeInput 校验并准备聊天节点的运行态依赖。
|
||||
func prepareChatNodeInput(input ChatNodeInput) (
|
||||
*newagentmodel.AgentRuntimeState,
|
||||
*newagentmodel.ConversationContext,
|
||||
*newagentstream.ChunkEmitter,
|
||||
error,
|
||||
) {
|
||||
if input.RuntimeState == nil {
|
||||
return nil, nil, nil, fmt.Errorf("chat node: runtime state 不能为空")
|
||||
}
|
||||
if input.Client == nil {
|
||||
return nil, nil, nil, fmt.Errorf("chat node: chat client 未注入")
|
||||
}
|
||||
|
||||
input.RuntimeState.EnsureCommonState()
|
||||
if input.ConversationContext == nil {
|
||||
input.ConversationContext = newagentmodel.NewConversationContext("")
|
||||
}
|
||||
if input.ChunkEmitter == nil {
|
||||
input.ChunkEmitter = newagentstream.NewChunkEmitter(
|
||||
newagentstream.NoopPayloadEmitter(), "", "", time.Now().Unix(),
|
||||
)
|
||||
}
|
||||
return input.RuntimeState, input.ConversationContext, input.ChunkEmitter, nil
|
||||
}
|
||||
100
backend/newAgent/node/correction.go
Normal file
100
backend/newAgent/node/correction.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package newagentnode
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
newagentmodel "github.com/LoveLosita/smartflow/backend/newAgent/model"
|
||||
"github.com/cloudwego/eino/schema"
|
||||
)
|
||||
|
||||
// AppendLLMCorrection 追加 LLM 修正提示到对话历史。
|
||||
//
|
||||
// 设计目的:
|
||||
// 1. 当 LLM 输出不符合预期(如不支持的 action、格式错误等),不应直接报错终止;
|
||||
// 2. 应该给 LLM 一个自我修正的机会,把错误反馈写回历史,让它重新生成;
|
||||
// 3. 该函数封装了"追加 assistant 消息 + 追加纠正提示"的通用流程。
|
||||
//
|
||||
// 参数说明:
|
||||
// - conversationContext: 对话上下文,用于追加历史消息;
|
||||
// - llmOutput: LLM 的原始输出内容,会作为 assistant 消息追加;
|
||||
// - validOptionsDesc: 合法选项的描述,用于构造纠正提示。
|
||||
//
|
||||
// 使用示例:
|
||||
//
|
||||
// AppendLLMCorrection(conversationContext, decision.Speak, "合法的 action 包括:continue、ask_user、next_plan、done")
|
||||
//
|
||||
// 返回值:
|
||||
// - 返回 nil 表示修正流程完成,调用方应继续 Graph 循环;
|
||||
// - 该函数不会返回 error,因为追加历史失败不影响主流程。
|
||||
func AppendLLMCorrection(
|
||||
conversationContext *newagentmodel.ConversationContext,
|
||||
llmOutput string,
|
||||
validOptionsDesc string,
|
||||
) {
|
||||
if conversationContext == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 1. 构造 assistant 消息,让 LLM 知道自己刚才输出了什么。
|
||||
// 如果 llmOutput 为空,则生成一个占位描述。
|
||||
assistantContent := strings.TrimSpace(llmOutput)
|
||||
if assistantContent == "" {
|
||||
assistantContent = "[LLM 输出为空或无法解析]"
|
||||
}
|
||||
conversationContext.AppendHistory(&schema.Message{
|
||||
Role: schema.Assistant,
|
||||
Content: assistantContent,
|
||||
})
|
||||
|
||||
// 2. 构造纠正提示,明确告知 LLM 哪里错了、合法选项有哪些。
|
||||
// 不做硬编码的错误类型,由调用方通过 validOptionsDesc 传入。
|
||||
correctionContent := fmt.Sprintf(
|
||||
"你的输出不符合预期。%s 请重新分析当前状态,输出正确的内容。",
|
||||
validOptionsDesc,
|
||||
)
|
||||
conversationContext.AppendHistory(&schema.Message{
|
||||
Role: schema.User,
|
||||
Content: correctionContent,
|
||||
})
|
||||
}
|
||||
|
||||
// AppendLLMCorrectionWithHint 追加 LLM 修正提示(带自定义错误描述)。
|
||||
//
|
||||
// 相比 AppendLLMCorrection,该函数允许调用方提供更详细的错误描述,
|
||||
// 适用于需要明确告知 LLM 具体哪里出错的场景。
|
||||
//
|
||||
// 参数说明:
|
||||
// - conversationContext: 对话上下文;
|
||||
// - llmOutput: LLM 的原始输出内容;
|
||||
// - errorDesc: 具体的错误描述,如 "action \"invalid\" 不是合法的执行动作";
|
||||
// - validOptionsDesc: 合法选项的描述。
|
||||
func AppendLLMCorrectionWithHint(
|
||||
conversationContext *newagentmodel.ConversationContext,
|
||||
llmOutput string,
|
||||
errorDesc string,
|
||||
validOptionsDesc string,
|
||||
) {
|
||||
if conversationContext == nil {
|
||||
return
|
||||
}
|
||||
|
||||
assistantContent := strings.TrimSpace(llmOutput)
|
||||
if assistantContent == "" {
|
||||
assistantContent = "[LLM 输出为空或无法解析]"
|
||||
}
|
||||
conversationContext.AppendHistory(&schema.Message{
|
||||
Role: schema.Assistant,
|
||||
Content: assistantContent,
|
||||
})
|
||||
|
||||
correctionContent := fmt.Sprintf(
|
||||
"%s %s 请重新分析当前状态,输出正确的内容。",
|
||||
errorDesc,
|
||||
validOptionsDesc,
|
||||
)
|
||||
conversationContext.AppendHistory(&schema.Message{
|
||||
Role: schema.User,
|
||||
Content: correctionContent,
|
||||
})
|
||||
}
|
||||
314
backend/newAgent/node/execute.go
Normal file
314
backend/newAgent/node/execute.go
Normal file
@@ -0,0 +1,314 @@
|
||||
package newagentnode
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
newagentllm "github.com/LoveLosita/smartflow/backend/newAgent/llm"
|
||||
newagentmodel "github.com/LoveLosita/smartflow/backend/newAgent/model"
|
||||
newagentprompt "github.com/LoveLosita/smartflow/backend/newAgent/prompt"
|
||||
newagentstream "github.com/LoveLosita/smartflow/backend/newAgent/stream"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
executeStageName = "execute"
|
||||
executeStatusBlockID = "execute.status"
|
||||
executeSpeakBlockID = "execute.speak"
|
||||
executePinnedKey = "execution_context"
|
||||
)
|
||||
|
||||
// ExecuteNodeInput 描述执行节点单轮运行所需的最小依赖。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 只承载"本轮执行"需要的输入,不负责持久化;
|
||||
// 2. RuntimeState 提供 plan 步骤与轮次预算;
|
||||
// 3. ConversationContext 提供历史对话与置顶上下文;
|
||||
// 4. ToolExecutor 后续由业务层注入,当前先留空。
|
||||
type ExecuteNodeInput struct {
|
||||
RuntimeState *newagentmodel.AgentRuntimeState
|
||||
ConversationContext *newagentmodel.ConversationContext
|
||||
UserInput string
|
||||
Client *newagentllm.Client
|
||||
ChunkEmitter *newagentstream.ChunkEmitter
|
||||
ResumeNode string
|
||||
}
|
||||
|
||||
// ExecuteRoundObservation 记录执行阶段每轮的关键观察。
|
||||
//
|
||||
// 设计说明:
|
||||
// 1. 参考 coding agent 模式,后端只记录事实,不做语义校验;
|
||||
// 2. ToolResult 存储工具调用的原始返回,供 LLM 下一轮决策;
|
||||
// 3. 该结构后续可扩展用于调试、回放、审计。
|
||||
type ExecuteRoundObservation struct {
|
||||
Round int `json:"round"`
|
||||
StepIndex int `json:"step_index"`
|
||||
GoalCheck string `json:"goal_check,omitempty"`
|
||||
Decision string `json:"decision,omitempty"`
|
||||
ToolName string `json:"tool_name,omitempty"`
|
||||
ToolParams string `json:"tool_params,omitempty"`
|
||||
ToolSuccess bool `json:"tool_success"`
|
||||
ToolResult string `json:"tool_result,omitempty"`
|
||||
}
|
||||
|
||||
// RunExecuteNode 执行一轮执行节点逻辑。
|
||||
//
|
||||
// 核心设计原则:
|
||||
// 1. LLM 主导:LLM 自己判断 done_when 是否满足,自己决定何时推进/完成;
|
||||
// 2. 后端兜底:只做资源控制(轮次预算)、安全兜底(防无限循环)、证据记录;
|
||||
// 3. 不做硬校验:后端不质疑 LLM 的 advance/complete 决策,信任 LLM 判断。
|
||||
//
|
||||
// 步骤说明:
|
||||
// 1. 校验最小依赖,推送"正在执行"状态,避免用户空等;
|
||||
// 2. 检查当前是否有可执行的 plan 步骤,无计划则报错;
|
||||
// 3. 构造执行阶段 prompt,调用 LLM 获取决策;
|
||||
// 4. 若 LLM 先对用户说话,则伪流式推送并写回历史;
|
||||
// 5. 按 LLM 决策执行动作:
|
||||
// 5.1 call_tool:执行工具调用,记录证据,推进轮次;
|
||||
// 5.2 ask_user:打开追问交互,等待用户回复;
|
||||
// 5.3 advance:LLM 判定当前步骤完成,推进到下一步;
|
||||
// 5.4 complete:LLM 判定整个任务完成,进入交付阶段;
|
||||
// 6. 安全兜底:轮次耗尽时强制进入交付,避免无限循环。
|
||||
func RunExecuteNode(ctx context.Context, input ExecuteNodeInput) error {
|
||||
// 1. 校验依赖并准备运行态。
|
||||
runtimeState, conversationContext, emitter, err := prepareExecuteNodeInput(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
flowState := runtimeState.EnsureCommonState()
|
||||
|
||||
// 2. 检查是否有可执行的 plan 步骤。
|
||||
if !flowState.HasCurrentPlanStep() {
|
||||
return fmt.Errorf("execute node: 当前无有效 plan 步骤,无法执行")
|
||||
}
|
||||
|
||||
// 3. 推送执行阶段状态,让前端知道当前进度。
|
||||
current, total := flowState.PlanProgress()
|
||||
currentStep, _ := flowState.CurrentPlanStep()
|
||||
if err := emitter.EmitStatus(
|
||||
executeStatusBlockID,
|
||||
executeStageName,
|
||||
"executing",
|
||||
fmt.Sprintf("正在执行第 %d/%d 步:%s", current, total, truncateText(currentStep.Content, 60)),
|
||||
false,
|
||||
); err != nil {
|
||||
return fmt.Errorf("执行阶段状态推送失败: %w", err)
|
||||
}
|
||||
|
||||
// 4. 消耗一轮预算,并检查是否耗尽。
|
||||
if !flowState.NextRound() {
|
||||
// 轮次耗尽,强制进入交付阶段。
|
||||
flowState.Done()
|
||||
return nil
|
||||
}
|
||||
|
||||
// 5. 构造本轮执行输入,请求 LLM 输出 ExecuteDecision。
|
||||
messages := newagentprompt.BuildExecuteMessages(flowState, conversationContext)
|
||||
decision, rawResult, err := newagentllm.GenerateJSON[newagentmodel.ExecuteDecision](
|
||||
ctx,
|
||||
input.Client,
|
||||
messages,
|
||||
newagentllm.GenerateOptions{
|
||||
Temperature: 0.3,
|
||||
MaxTokens: 1200,
|
||||
Thinking: newagentllm.ThinkingModeEnabled,
|
||||
Metadata: map[string]any{
|
||||
"stage": executeStageName,
|
||||
"step_index": flowState.CurrentStep,
|
||||
"round_used": flowState.RoundUsed,
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
if rawResult != nil && strings.TrimSpace(rawResult.Text) != "" {
|
||||
return fmt.Errorf("执行决策解析失败,原始输出=%s,错误=%w", strings.TrimSpace(rawResult.Text), err)
|
||||
}
|
||||
return fmt.Errorf("执行阶段模型调用失败: %w", err)
|
||||
}
|
||||
if err := decision.Validate(); err != nil {
|
||||
return fmt.Errorf("执行决策不合法: %w", err)
|
||||
}
|
||||
|
||||
// 6. 若 LLM 先对用户说话,则伪流式推送并写回历史。
|
||||
if strings.TrimSpace(decision.Speak) != "" {
|
||||
if err := emitter.EmitPseudoAssistantText(
|
||||
ctx,
|
||||
executeSpeakBlockID,
|
||||
executeStageName,
|
||||
decision.Speak,
|
||||
newagentstream.DefaultPseudoStreamOptions(),
|
||||
); err != nil {
|
||||
return fmt.Errorf("执行文案推送失败: %w", err)
|
||||
}
|
||||
// 将 LLM 的话追加到对话历史,保证下一轮上下文连续。
|
||||
// TODO: 后续需要把工具调用结果也追加到历史,这里先留占位。
|
||||
}
|
||||
|
||||
// 7. 按 LLM 决策执行动作,后端信任 LLM 判断,不做语义校验。
|
||||
switch decision.Action {
|
||||
case newagentmodel.ExecuteActionContinue:
|
||||
// 继续当前步骤的 ReAct 循环。
|
||||
// 若有工具调用意图,则执行工具并记录证据。
|
||||
if decision.ToolCall != nil {
|
||||
return executeToolCall(ctx, flowState, conversationContext, decision.ToolCall, emitter)
|
||||
}
|
||||
// 无工具调用,仅对话,继续下一轮。
|
||||
return nil
|
||||
|
||||
case newagentmodel.ExecuteActionAskUser:
|
||||
// LLM 判定缺少关键信息,打开追问交互。
|
||||
question := resolveExecuteAskUserText(decision)
|
||||
runtimeState.OpenAskUserInteraction(uuid.NewString(), question, strings.TrimSpace(input.ResumeNode))
|
||||
return nil
|
||||
|
||||
case newagentmodel.ExecuteActionNextPlan:
|
||||
// LLM 判定当前步骤已完成,推进到下一步。
|
||||
// 后端信任 LLM 判断,不做硬校验。
|
||||
if !flowState.AdvanceStep() {
|
||||
// 所有步骤已完成,进入交付阶段。
|
||||
flowState.Done()
|
||||
}
|
||||
return nil
|
||||
|
||||
case newagentmodel.ExecuteActionDone:
|
||||
// LLM 判定整个任务已完成,直接进入交付阶段。
|
||||
// 后端信任 LLM 判断,不做硬校验。
|
||||
flowState.Done()
|
||||
return nil
|
||||
|
||||
default:
|
||||
// 1. LLM 输出了不支持的 action,不应直接报错终止,而应给它修正机会。
|
||||
// 2. 使用通用修正函数追加错误反馈,让 Graph 继续循环。
|
||||
// 3. LLM 下一轮会看到错误反馈并修正自己的输出。
|
||||
llmOutput := decision.Speak
|
||||
if strings.TrimSpace(llmOutput) == "" {
|
||||
llmOutput = decision.Reason
|
||||
}
|
||||
AppendLLMCorrectionWithHint(
|
||||
conversationContext,
|
||||
llmOutput,
|
||||
fmt.Sprintf("你输出的 action \"%s\" 不是合法的执行动作。", decision.Action),
|
||||
"合法的 action 包括:continue(继续当前步骤)、ask_user(追问用户)、next_plan(推进到下一步)、done(任务完成)。",
|
||||
)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// prepareExecuteNodeInput 校验并准备执行节点的运行态依赖。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 校验必要依赖是否注入;
|
||||
// 2. 为空依赖提供兜底值,避免空指针;
|
||||
// 3. 不负责持久化,不负责业务逻辑。
|
||||
func prepareExecuteNodeInput(input ExecuteNodeInput) (*newagentmodel.AgentRuntimeState, *newagentmodel.ConversationContext, *newagentstream.ChunkEmitter, error) {
|
||||
if input.RuntimeState == nil {
|
||||
return nil, nil, nil, fmt.Errorf("execute node: runtime state 不能为空")
|
||||
}
|
||||
if input.Client == nil {
|
||||
return nil, nil, nil, fmt.Errorf("execute node: execute client 未注入")
|
||||
}
|
||||
|
||||
input.RuntimeState.EnsureCommonState()
|
||||
if input.ConversationContext == nil {
|
||||
input.ConversationContext = newagentmodel.NewConversationContext("")
|
||||
}
|
||||
if input.ChunkEmitter == nil {
|
||||
input.ChunkEmitter = newagentstream.NewChunkEmitter(newagentstream.NoopPayloadEmitter(), "", "", time.Now().Unix())
|
||||
}
|
||||
return input.RuntimeState, input.ConversationContext, input.ChunkEmitter, nil
|
||||
}
|
||||
|
||||
// resolveExecuteAskUserText 解析追问用户的文案。
|
||||
//
|
||||
// 优先级:
|
||||
// 1. 优先使用 LLM 输出的 speak;
|
||||
// 2. 其次使用 reason;
|
||||
// 3. 最后使用默认文案。
|
||||
func resolveExecuteAskUserText(decision *newagentmodel.ExecuteDecision) string {
|
||||
if decision == nil {
|
||||
return "执行过程中遇到不确定的情况,需要向你确认。"
|
||||
}
|
||||
if strings.TrimSpace(decision.Speak) != "" {
|
||||
return strings.TrimSpace(decision.Speak)
|
||||
}
|
||||
if strings.TrimSpace(decision.Reason) != "" {
|
||||
return strings.TrimSpace(decision.Reason)
|
||||
}
|
||||
return "执行过程中遇到不确定的情况,需要向你确认。"
|
||||
}
|
||||
|
||||
// executeToolCall 执行工具调用并记录证据。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 只负责执行工具调用,记录结果;
|
||||
// 2. 不负责判断工具调用是否成功(由 LLM 下一轮判断);
|
||||
// 3. 不负责重试(由外层 Graph 循环控制)。
|
||||
//
|
||||
// TODO: 当前为骨架实现,后续需要:
|
||||
// 1. 接入真实的工具执行器;
|
||||
// 2. 把工具调用结果追加到对话历史;
|
||||
// 3. 记录 ExecuteEvidenceReceipt。
|
||||
func executeToolCall(
|
||||
ctx context.Context,
|
||||
flowState *newagentmodel.CommonState,
|
||||
conversationContext *newagentmodel.ConversationContext,
|
||||
toolCall *newagentmodel.ToolCallIntent,
|
||||
emitter *newagentstream.ChunkEmitter,
|
||||
) error {
|
||||
if toolCall == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 当前为骨架实现,仅记录工具调用意图。
|
||||
// 后续需要:
|
||||
// 1. 根据 toolCall.Name 路由到具体工具执行器;
|
||||
// 2. 执行工具调用,获取结果;
|
||||
// 3. 记录 ExecuteEvidenceReceipt;
|
||||
// 4. 把工具调用结果追加到 conversationContext.History。
|
||||
|
||||
toolName := strings.TrimSpace(toolCall.Name)
|
||||
if toolName == "" {
|
||||
return fmt.Errorf("工具调用缺少工具名称")
|
||||
}
|
||||
|
||||
// 推送工具调用状态,让前端知道当前在做什么。
|
||||
if err := emitter.EmitStatus(
|
||||
executeStatusBlockID,
|
||||
executeStageName,
|
||||
"tool_call",
|
||||
fmt.Sprintf("正在调用工具:%s", toolName),
|
||||
false,
|
||||
); err != nil {
|
||||
return fmt.Errorf("工具调用状态推送失败: %w", err)
|
||||
}
|
||||
|
||||
// TODO: 执行真实工具调用,并记录证据。
|
||||
// 伪代码:
|
||||
// result := toolRegistry.Execute(ctx, toolCall.Name, toolCall.Arguments)
|
||||
// evidence := ExecuteEvidenceReceipt{
|
||||
// StepIndex: flowState.CurrentStep,
|
||||
// Source: ExecuteEvidenceSourceToolObservation,
|
||||
// Name: toolCall.Name,
|
||||
// Success: result.Success,
|
||||
// Summary: result.Summary,
|
||||
// }
|
||||
// flowState.RecordEvidence(evidence)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// truncateText 截断文本到指定长度。
|
||||
//
|
||||
// 用于状态推送时避免超长文本影响前端展示。
|
||||
func truncateText(text string, maxLen int) string {
|
||||
text = strings.TrimSpace(text)
|
||||
if len(text) <= maxLen {
|
||||
return text
|
||||
}
|
||||
if maxLen <= 3 {
|
||||
return text[:maxLen]
|
||||
}
|
||||
return text[:maxLen-3] + "..."
|
||||
}
|
||||
@@ -119,7 +119,20 @@ func RunPlanNode(ctx context.Context, input PlanNodeInput) error {
|
||||
writePlanPinnedBlocks(conversationContext, decision.PlanSteps)
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("未支持的规划动作: %s", decision.Action)
|
||||
// 1. LLM 输出了不支持的 action,不应直接报错终止,而应给它修正机会。
|
||||
// 2. 使用通用修正函数追加错误反馈,让 Graph 继续循环。
|
||||
// 3. LLM 下一轮会看到错误反馈并修正自己的输出。
|
||||
llmOutput := decision.Speak
|
||||
if strings.TrimSpace(llmOutput) == "" {
|
||||
llmOutput = decision.Reason
|
||||
}
|
||||
AppendLLMCorrectionWithHint(
|
||||
conversationContext,
|
||||
llmOutput,
|
||||
fmt.Sprintf("你输出的 action \"%s\" 不是合法的执行动作。", decision.Action),
|
||||
"合法的 action 包括:continue(继续当前步骤)、ask_user(追问用户)、next_plan(推进到下一步)、done(任务完成)。",
|
||||
)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
56
backend/newAgent/prompt/chat.go
Normal file
56
backend/newAgent/prompt/chat.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package newagentprompt
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
newagentmodel "github.com/LoveLosita/smartflow/backend/newAgent/model"
|
||||
"github.com/cloudwego/eino/schema"
|
||||
)
|
||||
|
||||
const chatIntentSystemPrompt = `
|
||||
你是 SmartFlow 的意图分类器。
|
||||
你的唯一任务是判断用户本轮输入是"纯闲聊"还是"包含任务意图"。
|
||||
|
||||
判断规则:
|
||||
1. chat:打招呼、感谢、简单问答、情感表达、闲聊,不涉及任何具体任务或操作请求。
|
||||
2. task:包含任何需要规划/执行/操作的意图,包括但不限于查询信息、创建内容、修改数据、安排日程、继续已有任务等。
|
||||
|
||||
保守原则:当不确定时,倾向于判断为 task,宁可多走一次规划也不要丢失用户意图。
|
||||
|
||||
严格输出以下 JSON(不要输出 markdown,不要在 JSON 外补文字):
|
||||
{"intent":"chat或task","reply":"仅当intent=chat时填写你的闲聊回复,task时留空","reason":"简短判断依据"}
|
||||
`
|
||||
|
||||
// BuildChatIntentSystemPrompt 返回意图分类系统提示词。
|
||||
func BuildChatIntentSystemPrompt() string {
|
||||
return strings.TrimSpace(chatIntentSystemPrompt)
|
||||
}
|
||||
|
||||
// BuildChatIntentMessages 组装意图分类的 messages。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 只取最近 6 条历史,保证分类高效;
|
||||
// 2. 不注入 pinned blocks / tool schemas,分类不需要这些信息;
|
||||
// 3. 不负责解析模型输出。
|
||||
func BuildChatIntentMessages(conversationContext *newagentmodel.ConversationContext, userInput string) []*schema.Message {
|
||||
messages := make([]*schema.Message, 0, 8)
|
||||
|
||||
messages = append(messages, schema.SystemMessage(BuildChatIntentSystemPrompt()))
|
||||
|
||||
if conversationContext != nil {
|
||||
history := conversationContext.HistorySnapshot()
|
||||
if len(history) > 6 {
|
||||
history = history[len(history)-6:]
|
||||
}
|
||||
if len(history) > 0 {
|
||||
messages = append(messages, history...)
|
||||
}
|
||||
}
|
||||
|
||||
trimmedInput := strings.TrimSpace(userInput)
|
||||
if trimmedInput != "" {
|
||||
messages = append(messages, schema.UserMessage(trimmedInput))
|
||||
}
|
||||
|
||||
return messages
|
||||
}
|
||||
Reference in New Issue
Block a user