Files
WechatExplorer/tests/unit/agent-hub-routing.test.ts
T
Wxw-Gu b97e1f4bc6 fix: 问问微信恢复引用编号,日报修复成员头像与 hero 溢出
- 问问微信:Host 分配稳定 citationId 并注入模型上下文,正文 [E#] 与证据按钮同号
- 问问微信:回答返回前校验引用,幻觉编号移除并提示,不再出现不可信的引用按钮
- Agent Hub:收紧「最近会话」快捷路由,内容查询交回 Query Agent
2026-09-17 18:56:32 +08:00

156 lines
6.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, it } from 'vitest'
import {
matchGroupReportIntent,
matchRecentChatIntent,
queryAgentReplyText,
QUERY_AGENT_UNAVAILABLE_TEXT,
resolveInboundRoute
} from '../../src/main/services/agent-hub-routing'
import type { AskWechatQueryResult } from '../../src/shared/query-agent'
describe('Agent Hub 入站路由 — Query / Action 分流', () => {
it('A. 「TraceMemo 交流群最近聊了啥」→ Query Agent(不是 report)', () => {
expect(resolveInboundRoute('TraceMemo 交流群最近聊了啥')).toEqual({ kind: 'knowledge_query' })
})
it('B. 「总结一下 TraceMemo 交流群最近聊了什么」→ Query Agent("总结"不等于 report)', () => {
expect(resolveInboundRoute('总结一下 TraceMemo 交流群最近聊了什么')).toEqual({
kind: 'knowledge_query'
})
})
it('B2. 「总结」缺少产物词时,report 快捷匹配不成立', () => {
expect(matchGroupReportIntent('总结一下这个群最近聊了什么')).toBeNull()
expect(matchGroupReportIntent('帮我总结一下 TraceMemo 交流群的近期内容')).toBeNull()
})
it('C. 「生成 TraceMemo 交流群今天的群聊总结图片」→ Report Action(图片)', () => {
const route = resolveInboundRoute('生成 TraceMemo 交流群今天的群聊总结图片')
expect(route.kind).toBe('report_action')
if (route.kind !== 'report_action') throw new Error('unreachable')
expect(route.intent.group).toContain('TraceMemo')
expect(route.intent.range).toBe('today')
})
it('D. 「帮我做一份 TraceMemo 交流群今日日报」→ Report Action(日报)', () => {
const route = resolveInboundRoute('帮我做一份 TraceMemo 交流群今日日报')
expect(route.kind).toBe('report_action')
if (route.kind !== 'report_action') throw new Error('unreachable')
expect(route.intent.group).toContain('TraceMemo')
})
it('D2. 群 + 时间但不要产物 → Query Agent', () => {
expect(resolveInboundRoute('TraceMemo 交流群昨天聊了啥')).toEqual({ kind: 'knowledge_query' })
})
it('E. 「BOBO 最近说过什么」→ Query Agent', () => {
expect(resolveInboundRoute('BOBO 最近说过什么')).toEqual({ kind: 'knowledge_query' })
})
it('F. 「我和 BOBO 第一次聊了什么」→ Query Agent', () => {
expect(resolveInboundRoute('我和 BOBO 第一次聊了什么')).toEqual({ kind: 'knowledge_query' })
})
it('G. 群成员分析仍是专用 Action', () => {
const route = resolveInboundRoute('看看 TraceMemo交流群里 BOBO 最近说了什么')
expect(route.kind).toBe('group_member_action')
if (route.kind !== 'group_member_action') throw new Error('unreachable')
expect(route.intent.member).toBe('BOBO')
expect(route.intent.group).toContain('TraceMemo')
})
it('H. 会话列表是确定性快捷路径(不经过模型)', () => {
expect(resolveInboundRoute('最近有哪些会话')).toEqual({ kind: 'recent_list', limit: 5 })
expect(resolveInboundRoute('最近3条消息')).toEqual({ kind: 'recent_list', limit: 3 })
})
})
/**
* recent_list 的判定必须**正向**:只有用户确实在要"会话 / 联系人名单"时才算。
* 早先只要求「最近」+「消息|会话|聊天」同时出现,于是内容查询被截走 —— 用户问
* "最近…里有没有提到 X",得到的却是一串会话名。
*/
describe('Agent Hub 入站路由 — recent_list 边界', () => {
const cases: Array<[string, string, string]> = [
['最近有哪些聊天', 'recent_list', '名单形态 + 聊天载体'],
['最近有哪些会话', 'recent_list', '名单形态 + 会话载体'],
['最近和谁聊过', 'recent_list', '「和谁」问法本身就在问会话对象'],
['最近聊过哪些人', 'recent_list', '名单形态 + 人'],
['最近 5 个会话', 'recent_list', '数量 + 载体'],
['最近群里聊的消息里有没有提到报价', 'knowledge_query', '内容探针:有没有提到'],
['最近聊天里谁提过健身', 'knowledge_query', '内容探针:谁提过'],
['最近消息里有没有说过报价', 'knowledge_query', '内容探针:有没有说过'],
['技术交流群今天主要聊了什么', 'knowledge_query', '没有「最近」,不是名单请求'],
['生成技术交流群日报', 'report_action', '明确产物优先'],
['微信里最近发生了什么', 'knowledge_query', '不是名单形态'],
['总结一下 TraceMemo 交流群最近聊了什么', 'knowledge_query', '总结 ≠ 名单,且是内容探针']
]
for (const [text, expected, why] of cases) {
it(`「${text}」→ ${expected}(${why})`, () => {
expect(resolveInboundRoute(text).kind).toBe(expected)
})
}
it('recent_list 只保留实际存在的会话数量上限语义', () => {
// 无数字时默认 5;有数字时取该数字,并夹在 1..20。
expect(matchRecentChatIntent('最近和谁聊过')).toBe(5)
expect(matchRecentChatIntent('最近12个会话')).toBe(12)
expect(matchRecentChatIntent('最近99个会话')).toBe(20)
// 不是名单请求 → 不命中
expect(matchRecentChatIntent('最近聊天里谁提过健身')).toBeNull()
})
})
describe('Agent Hub 回复文案映射', () => {
const diagnostics = {
entry: 'agent-hub' as const,
provider: 'P',
model: 'm',
modelCallCount: 2,
toolCallCount: 1,
tools: ['query_messages'],
totalMs: 10,
outcome: 'answered' as const
}
it('answered → 直接用 Query Agent 的文本回答', () => {
expect(
queryAgentReplyText({
engine: 'query-agent',
status: 'answered',
answer: '最近聊了两件事。',
diagnostics
})
).toBe('最近聊了两件事。')
})
it('provider_unavailable / error → 使用安全文案', () => {
const unavailable: AskWechatQueryResult = {
engine: 'query-agent',
status: 'provider_unavailable',
message: '当前 AI 查询服务暂时不可用,请稍后再试。',
diagnostics: { ...diagnostics, outcome: 'provider_failure' }
}
expect(queryAgentReplyText(unavailable)).toBe('当前 AI 查询服务暂时不可用,请稍后再试。')
const error: AskWechatQueryResult = {
engine: 'query-agent',
status: 'error',
message: '本次查询没有完成,请稍后再试或换一种问法。',
diagnostics: { ...diagnostics, outcome: 'runtime_error' }
}
expect(queryAgentReplyText(error)).toBe('本次查询没有完成,请稍后再试或换一种问法。')
})
it('legacy 结果不会出现在 Agent Hub 路径上,兜底为服务不可用文案', () => {
const legacy: AskWechatQueryResult = {
engine: 'legacy',
status: 'legacy',
reason: 'runtime_error',
result: {} as never
}
expect(queryAgentReplyText(legacy)).toBe(QUERY_AGENT_UNAVAILABLE_TEXT)
})
})