Appearance
Agent 系统 — 情感计算引擎(基于 Google ADK)
定位
Shice AI 的核心是一个人际关系情感计算引擎:它对关系中各方的情绪状态、依恋风格、权力动态进行建模,并基于这些模型给出可执行的策略和话术。
Agent 系统使用 Google Agent Development Kit (ADK) for Go 构建,深度集成 ADK 的 Runner、Session、Memory 三大核心能力,通过自定义 model.LLM 适配器对接 OpenAI 兼容 API。
与其它文档的分工:module_design_notes.md 的「核心数据流」概括 Handler → System;本文聚焦 internal/agent 包内的路由、Runner、Pipeline、Prompt 与异步情绪提取。SSE 事件形状与 profileProposal 见 sse.md 与 handler/chat.go。
文件结构
backend/internal/agent/
├── types.go # StreamChunk, AgentContext, PersonData, Intent …
├── adk_model.go # OpenAIModel / MockModel / ContextAwareModel — model.LLM
├── llm.go # Mock 固定回复等
├── prompts.go # productPersona、各 Agent system prompt、chatRouterSystemPrompt
├── agents.go # llmagent.New 四子 Agent + newOrchestratorAgent + runDirectAgent
├── orchestrator.go # detectIntent()关键词表(独立工具/兜底,非 Chat 主路由)
├── chat_pipeline.go # decideChatRoute、两固定 Pipeline、Router 的 Langfuse step
├── system.go # System门面:Chat / Analyze / Advise / Script;Runner + Memory
├── emotion.go # EmotionExtractor → emotion_snapshots
├── trace_llm.go # 子调用步骤收集、Langfuse 分组上报
├── langfuse_context.go # trace id / steps 在 context 中的传递
├── profile_update_tool.go # Orchestrator 可挂载的档案更新工具(ADK tool)
├── profile_extract.go # 对话后画像/档案提案抽取(与 Chat Handler 门控配合)
├── profile_*.go # 提案注册、用户意图 LLM、观测记录等
backend/internal/observability/
└── langfuse.go # CompletionRecorderChat 端到端工作链路(概念)
HTTP POST /api/v1/chat/stream(Gin ChatHandler)
├── 额度(quota)与上下文加载(人物、历史、session summary、情绪快照、self profile …)
├── 组装 *agent.AgentContext(locale、max_tokens、Model、History …)
└── agent.System.Chat(ctx, userText, agentCtx)
│
├──① 【Router LLM】decideChatRoute — 单次非流式调用,输出 JSON {"mode":"..."}
│ • runner │ • pipe_analyze_strategist
│ • pipe_strategist_writer
│ (规则见 prompts.go → chatRouterSystemPrompt;解析失败则视为 runner)
│
├──②a mode =固定 Pipeline(不经过 ADK Runner)
│ • pipe_analyze_strategist:Analyzer →(pipelinePreamble)→ Strategist,两段均 runDirectAgent 流式
│ • pipe_strategist_writer:Strategist →(writerPipelinePreamble)→ Writer
│
└──②b mode = runner
└── adkRunner.Run(…, StreamingModeSSE)
└── Orchestrator(+ 可选 profile 工具)→ transfer_to_agent → 单个子 Agent 流式输出Strategy 专用接口(/strategy/analyze|advise|script)不走路由器、不走 Runner:System.Analyze / Advise / Script 直接 runDirectAgent 到对应 Agent。
架构(System 内部)
System (门面)
├── ChatRouter(chat_pipeline.go)
│ └── decideChatRoute → runner | pipe_analyze_strategist | pipe_strategist_writer
├── ContextAwareModel(按请求/Agent 切换底层模型)
├── ADK Runner(仅 runner 模式)
│ ├── runner.Run(…, StreamingModeSSE)
│ ├── OrchestratorAgent(SubAgents + Tools,InstructionProvider)
│ │ └── transfer_to_agent → profile / analyzer / strategist / writer
│ └── 四子 Agent:InstructionProvider 动态拼 prompt(注入 AgentContext)
├── 固定 Pipeline(两模式)
│ └── 两段 runDirectAgent,中间用 PipelinePreamble 注入下游 prompt
├── Direct Agent(Strategy API)
│ └── Analyze / Advise / Script → runDirectAgent
├── ADK Session + Memory(InMemory)
├── Session Summary(滚动摘要 → chat_sessions.summary,由 Handler/服务层触发,见业务代码)
└── EmotionExtractor(异步,非 Mock 时)
→ emotion_snapshots,下轮注入 Emotional trajectoryADK 集成方式
model.LLM 适配器
项目实现了 ADK 的 model.LLM 接口来对接 OpenAI 兼容 API:
go
// ADK 要求的接口
type LLM interface {
Name() string
GenerateContent(ctx context.Context, req *LLMRequest, stream bool) iter.Seq2[*LLMResponse, error]
}| 实现 | 说明 |
|---|---|
OpenAIModel | 通过 net/http 调用 OpenAI Chat Completions API,支持流式 SSE |
MockModel | 无需 API Key 的本地 Mock,返回固定中文回复 |
OpenAIModel.WithModel(name) 可创建使用不同模型的副本,用于用户指定模型切换。
Agent 定义
每个子 Agent 使用 ADK 的 llmagent.New() 定义:
go
agent, _ := llmagent.New(llmagent.Config{
Name: "analyzer",
Description: "Behavioral decoding: read subtext, model emotions, analyze power dynamics",
Model: NewContextAwareModel(model),
InstructionProvider: func(ctx adkagent.ReadonlyContext) (string, error) {
agentCtx, _ := ctx.Value(agentContextKey).(*AgentContext)
return analyzerSystemPrompt(agentCtx), nil
},
})每个 Agent 使用 InstructionProvider(而非静态 Instruction)动态生成 system prompt,从 Go context.Value 读取 AgentContext(人物、语言、自我画像等)。模型包装为 ContextAwareModel 以支持前端按请求切换模型。
Strategy 端点(Analyze/Advise/Script)仍通过 runDirectAgent() 直接调用 model.LLM.GenerateContent(),绕过 Runner。
ADK Runner — Chat 的 runner 模式
当且仅当 Router 返回 mode: "runner" 时,System.Chat 调用 adkRunner.Run():
- Orchestrator 接收用户消息(可调用挂载的 profile 更新类 tool,再
transfer_to_agent) - 通过 ADK
transfer_to_agent将执行交给单个子 Agent - 子 Agent 流式输出经
StreamChunk传给 Gin SSE
Runner 配置(system.go):
AppName: "shice-ai"Agent: orchestratorAgent(四 SubAgents + 可选 Tools)SessionService/MemoryService:InMemoryService()AutoCreateSession: trueRunConfig.StreamingMode: StreamingModeSSE
ADK Session
使用 ADK 的 session.InMemoryService() 管理 Agent 层面的会话状态。每次对话的 user/assistant 消息通过 AppendEvent() 追加到 ADK Session,为 Memory 检索提供数据源。
ADK Memory(长期记忆)
使用 ADK 的 memory.InMemoryService() 实现跨会话的长期记忆:
用户发消息 → SearchMemory(query=用户消息) → 检索相关历史记忆
↓
注入 prompt "Long-term memory"
↓
AI 回复完成 → AddToMemory(session) → 当前对话存入记忆库- 存储:每轮对话结束后,user/assistant 消息被追加到 ADK Session,然后整个 Session 通过
AddSessionToMemory()添加到记忆服务 - 检索:新对话时,用用户消息作为 query 调用
SearchMemory(),返回的相关记忆注入到 prompt 的 "Long-term memory" 部分 - 范围:按 userID 隔离,跨所有会话和人物
注意:当前使用 InMemory 实现,服务重启后记忆清空。生产环境可替换为持久化实现(如基于 Qdrant 向量数据库)。
会话摘要(Session Summary)
每轮对话完成后异步生成/更新:
- 用
sessionSummaryPrompt+ 已有摘要 + 最新消息调用 LLM - 生成 2-5 句话的滚动摘要,覆盖:讨论对象、核心问题、关键建议、行动项、情绪走向
- 存入
chat_sessions.summary字段
跨会话上下文注入优先级
1. Session Summary(优先)→ LLM 生成的 2-5 句摘要
2. Raw Excerpts(兜底) → 无摘要时截取前 4 条消息 × 120 字
3. Emotional Trajectory → 最近 5 条情绪快照
4. Long-term Memory → ADK Memory 关键词检索的相关记忆Chat Router LLM(decideChatRoute)
在 每一次 System.Chat 开头,先用 独立 system prompt(chatRouterSystemPrompt)做一次非流式小调用,模型须只输出 JSON:{"mode":"runner"|"pipe_analyze_strategist"|"pipe_strategist_writer"}。
语义规则(摘录,完整见 prompts.go):
| mode | 含义 |
|---|---|
runner | 默认;交给 Orchestrator + transfer_to_agent 选一个子 Agent |
pipe_analyze_strategist | 用户同一条消息里既要深度解读又要可执行方案 |
pipe_strategist_writer | 用户同一条消息里既要策略又要可直接粘贴的话术 |
若用户明确要更新/保存档案或画像(应用内结构化数据),Router 强制 runner,以便 Orchestrator 走工具链路,不走固定 Pipeline。
解析失败或异常时回退为 runner。Langfuse 中将该步记为 router(见 chat_pipeline.go)。
Orchestrator(Runner 模式内部)
在 runner 模式下,由 Orchestrator 的 InstructionProvider(orchestratorRoutingPrompt)+ ADK 的 transfer_to_agent 完成第二轮路由:
- 描述四个子 Agent 的分工
- LLM 选择
transfer_to_agent目标:profile/analyzer/strategist/writer - 选中 Agent 流式回复用户
orchestrator.go 中的 **detectIntent(关键词表)** 不驱动上述 Chat 主路径,保留作辅助/分析用途。
Pipeline 串联(已实现)
当 Router 选择固定 Pipeline 时,不调用 adkRunner.Run,而是顺序执行两段 runDirectAgent(两段均向同一 StreamChunk channel 写流式片段):
| Pipeline | 第一段 | 第二段 | 衔接 |
|---|---|---|---|
pipe_analyze_strategist | AnalyzerAgent | StrategistAgent | 第一段全文写入 agentCtx.PipelinePreamble(pipelinePreamble) |
pipe_strategist_writer | StrategistAgent | WriterAgent | 第一段写入 writerPipelinePreamble |
与「用户分两轮提问」无关;一轮用户消息内即可得到两段合成体验。
未来若要用 ADK
sequentialagent或把 Agent 封装为 tool 在 Orchestrator 内多步调用,可作为实现替换,产品语义上与当前固定 Pipeline 等价。
Prompt 设计
productPersona(共享基座)
- 情感计算引擎定位:对方情绪建模 + 依恋风格推断 + 预测反应
- 情绪感知规则:高焦虑/愤怒时先 1-2 句接住情绪,再分析
- 信息密度自适应:输入一句话 → 回 3-8 句;长输入 → 完整分析
- 伦理约束:不鼓励操控/欺骗
各 Agent 独立框架
| Agent | 分析维度 |
|---|---|
| ProfileAgent | 性格与沟通 / 依恋与冲突风格 / 需求价值 / 关系动态(阶段 + 权力 + 信任)/ 互动指南 |
| AnalyzerAgent | 表面 vs 真实意图 / 情绪建模(含依恋行为)/ 权力动态 / 潜台词 / 文化语境 / 推荐回应 |
| StrategistAgent | 局势 / 目标 / 分阶段计划(含 IF/THEN 条件分支)/ 风险地图 / 成功信号 |
| WriterAgent | 语言风格匹配(用户 SelfProfile 或推断)/ 3 变体 / 发送建议 |
动态注入
buildPersonContext— 关联人物档案(姓名/关系/标签/状态/备注)buildSelfProfileSection— 用户自我画像followUpInstruction— 追问时切换简洁格式replyLanguageInstruction— 回复语言(zh / en)outputBudgetInstruction—max_tokens预算pipelinePreamble— Pipeline 上游分析结果RecentInteractions— 近期互动摘要 / Session Summary / Long-term MemoryEmotionSnapshot— 情绪轨迹
情绪追踪 (EmotionExtractor)
每次对话后异步运行(仅非 MockModel):
- 用
emotionExtractPrompt+ 用户消息 + 助手回复通过model.LLM.GenerateContent()发一次轻量调用 - 提取 JSON:
{ user_mood, other_mood, trend, tags } - 存入
emotion_snapshots表,关联 session + person - 下次对话加载最近 5 条快照,注入 prompt 的
RecentInteractions
配置示例
yaml
ai:
default_provider: "openai"
default_model: "gpt-4o-mini"
api_key: "sk-..."
agents:
orchestrator: { provider: "openai", model: "gpt-4o-mini" }
profile: { provider: "openai", model: "gpt-4o-mini" }
analyzer: { provider: "openai", model: "gpt-4o-mini" }
strategist: { provider: "openai", model: "gpt-4o-mini" }
writer: { provider: "openai", model: "gpt-4o-mini" }每个 Agent 可独立配置 provider / model / base_url / api_key,支持混合部署(如 Orchestrator 用轻量模型,Strategist 用重量级模型)。
Langfuse 观测见 internal/observability/langfuse.go;由 cmd/server 构造 CompletionRecorder 注入 NewSystem。