feat: 重构知识库系统,移除Hermes集成,增强RAG和同步功能
主要变更: - 移除Hermes智能体及相关回调服务 - 新增知识库RAG、同步、调度、规范化和索引任务服务 - 重构orchestrator服务,增强运行时聊天功能 - 更新前端聊天、政策制度、设置等页面样式和逻辑 - 更新expense_claims和document_intelligence服务 - 删除llm_wiki相关服务和测试文件 - 更新docker-compose配置和启动脚本
This commit is contained in:
@@ -83,7 +83,7 @@ def test_user_agent_sanitizes_model_thinking_blocks() -> None:
|
||||
)
|
||||
|
||||
|
||||
def test_user_agent_rejects_visible_reasoning_drafts() -> None:
|
||||
def test_user_agent_rejects_visible_reasoning_drafts() -> None:
|
||||
session_factory = build_session_factory()
|
||||
with session_factory() as db:
|
||||
service = UserAgentService(db)
|
||||
@@ -93,10 +93,43 @@ def test_user_agent_rejects_visible_reasoning_drafts() -> None:
|
||||
"用户问的是:住宿费怎么算?\n让我分析一下:\n1. 实体识别..."
|
||||
)
|
||||
is None
|
||||
)
|
||||
|
||||
|
||||
def test_user_agent_knowledge_prompt_enforces_knowledge_boundary() -> None:
|
||||
session_factory = build_session_factory()
|
||||
with session_factory() as db:
|
||||
ontology = SemanticOntologyService(db).parse(
|
||||
OntologyParseRequest(
|
||||
query="住宿费标准是多少?",
|
||||
user_id="pytest",
|
||||
context_json={"session_type": "knowledge"},
|
||||
)
|
||||
)
|
||||
service = UserAgentService(db)
|
||||
messages = service._build_model_messages(
|
||||
UserAgentRequest(
|
||||
run_id=ontology.run_id,
|
||||
user_id="pytest",
|
||||
message="住宿费标准是多少?",
|
||||
ontology=ontology,
|
||||
tool_payload={"result_type": "knowledge_search", "hits": []},
|
||||
),
|
||||
citations=[],
|
||||
suggested_actions=[],
|
||||
risk_flags=[],
|
||||
draft_payload=None,
|
||||
fallback_answer="",
|
||||
)
|
||||
assert "只能依据 facts.tool_payload.hits、facts.knowledge_answer_evidence" in messages[0]["content"]
|
||||
assert "不能用常识、外部知识或主观推断补齐缺失条件" in messages[0]["content"]
|
||||
assert "不能只依赖排在最前面的片段" in messages[0]["content"]
|
||||
assert "不能把第一列的数值直接套给后面的列名" in messages[0]["content"]
|
||||
assert "knowledge_evidence_blocks" in messages[0]["content"]
|
||||
assert '"knowledge_answer_evidence": []' in messages[1]["content"]
|
||||
|
||||
|
||||
def test_user_agent_knowledge_prompt_enforces_llm_wiki_boundary() -> None:
|
||||
def test_user_agent_knowledge_fallback_is_honest_and_personalized() -> None:
|
||||
session_factory = build_session_factory()
|
||||
with session_factory() as db:
|
||||
ontology = SemanticOntologyService(db).parse(
|
||||
@@ -107,7 +140,48 @@ def test_user_agent_knowledge_prompt_enforces_llm_wiki_boundary() -> None:
|
||||
)
|
||||
)
|
||||
service = UserAgentService(db)
|
||||
messages = service._build_model_messages(
|
||||
answer = service._build_knowledge_search_answer(
|
||||
UserAgentRequest(
|
||||
run_id=ontology.run_id,
|
||||
user_id="pytest",
|
||||
message="住宿费标准是多少?",
|
||||
ontology=ontology,
|
||||
context_json={"name": "张三"},
|
||||
tool_payload={
|
||||
"result_type": "knowledge_search",
|
||||
"hits": [{"title": "差旅费制度", "content": "住宿费标准正文"}],
|
||||
},
|
||||
),
|
||||
citations=[],
|
||||
)
|
||||
|
||||
assert answer.startswith("张三,您好。")
|
||||
assert "答案整理阶段本轮没有及时返回" in answer
|
||||
assert "先给你当前最直接的依据" in answer
|
||||
assert "《差旅费制度》" in answer
|
||||
|
||||
|
||||
def test_user_agent_knowledge_answer_generation_uses_fast_timeouts(monkeypatch) -> None:
|
||||
session_factory = build_session_factory()
|
||||
with session_factory() as db:
|
||||
ontology = SemanticOntologyService(db).parse(
|
||||
OntologyParseRequest(
|
||||
query="住宿费标准是多少?",
|
||||
user_id="pytest",
|
||||
context_json={"session_type": "knowledge"},
|
||||
)
|
||||
)
|
||||
service = UserAgentService(db)
|
||||
captured: dict[str, object] = {}
|
||||
|
||||
def fake_complete(messages, **kwargs):
|
||||
captured["messages"] = messages
|
||||
captured.update(kwargs)
|
||||
return "测试回答"
|
||||
|
||||
monkeypatch.setattr(service.runtime_chat_service, "complete", fake_complete)
|
||||
|
||||
answer = service._generate_answer_with_model(
|
||||
UserAgentRequest(
|
||||
run_id=ontology.run_id,
|
||||
user_id="pytest",
|
||||
@@ -122,14 +196,177 @@ def test_user_agent_knowledge_prompt_enforces_llm_wiki_boundary() -> None:
|
||||
fallback_answer="",
|
||||
)
|
||||
|
||||
assert "只能依据 tool_payload.hits 中的 LLM Wiki 内容作答" in messages[0]["content"]
|
||||
assert answer == "测试回答"
|
||||
assert captured["timeout_seconds"] == 5
|
||||
assert captured["slot_timeouts"] == {"main": 3, "backup": 5}
|
||||
assert captured["max_attempts"] == 1
|
||||
|
||||
|
||||
def test_user_agent_prefers_structured_knowledge_hit_for_answer_generation() -> None:
|
||||
selected = UserAgentService._select_knowledge_model_hits(
|
||||
{
|
||||
"hits": [
|
||||
{"content": "raw hit 1"},
|
||||
{"content": "raw hit 2"},
|
||||
{"content": "# 问答线索补充\n\n- 第二章 报销时限:费用发生后 30 日内提交申请。"},
|
||||
{"content": "# 结构化表格补充\n\n| 项目 | 金额 |"},
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
assert selected[0]["content"].startswith("# 问答线索补充")
|
||||
assert selected[1]["content"].startswith("# 结构化表格补充")
|
||||
assert selected[2]["content"] == "raw hit 1"
|
||||
|
||||
|
||||
def test_user_agent_uses_fast_knowledge_answer_without_model(monkeypatch) -> None:
|
||||
session_factory = build_session_factory()
|
||||
with session_factory() as db:
|
||||
ontology = SemanticOntologyService(db).parse(
|
||||
OntologyParseRequest(
|
||||
query="报销时限是多少?",
|
||||
user_id="pytest",
|
||||
context_json={"session_type": "knowledge"},
|
||||
)
|
||||
)
|
||||
service = UserAgentService(db)
|
||||
monkeypatch.setattr(
|
||||
service,
|
||||
"_generate_answer_with_model",
|
||||
lambda *args, **kwargs: (_ for _ in ()).throw(AssertionError("model should not be called")),
|
||||
)
|
||||
|
||||
response = service.respond(
|
||||
UserAgentRequest(
|
||||
run_id=ontology.run_id,
|
||||
user_id="pytest",
|
||||
message="报销时限是多少?",
|
||||
ontology=ontology,
|
||||
context_json={
|
||||
"name": "张三",
|
||||
"session_type": "knowledge",
|
||||
"user_input_text": "报销时限是多少?",
|
||||
},
|
||||
tool_payload={
|
||||
"result_type": "knowledge_search",
|
||||
"hits": [
|
||||
{
|
||||
"title": "费用报销制度",
|
||||
"content": (
|
||||
"# 问答线索补充\n\n"
|
||||
"- 第二章 报销时限:员工应在费用发生后 30 日内提交报销申请。\n"
|
||||
"- 第二章 报销时限:超过 30 日需补充审批说明。"
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
assert response.answer.startswith("张三,您好。")
|
||||
assert "当前能直接确认的是" in response.answer
|
||||
assert "30 日内提交报销申请" in response.answer
|
||||
assert "答案整理阶段本轮没有及时返回" not in response.answer
|
||||
|
||||
|
||||
def test_user_agent_fast_knowledge_answer_renders_relevant_table_preview() -> None:
|
||||
session_factory = build_session_factory()
|
||||
with session_factory() as db:
|
||||
ontology = SemanticOntologyService(db).parse(
|
||||
OntologyParseRequest(
|
||||
query="餐补标准是多少?",
|
||||
user_id="pytest",
|
||||
context_json={"session_type": "knowledge"},
|
||||
)
|
||||
)
|
||||
service = UserAgentService(db)
|
||||
|
||||
answer = service._build_fast_knowledge_answer(
|
||||
UserAgentRequest(
|
||||
run_id=ontology.run_id,
|
||||
user_id="pytest",
|
||||
message="餐补标准是多少?",
|
||||
ontology=ontology,
|
||||
context_json={
|
||||
"session_type": "knowledge",
|
||||
"user_input_text": "餐补标准是多少?",
|
||||
},
|
||||
tool_payload={
|
||||
"result_type": "knowledge_search",
|
||||
"hits": [
|
||||
{
|
||||
"title": "费用报销制度",
|
||||
"content": (
|
||||
"# 结构化表格补充\n\n"
|
||||
"## 表3 出差补贴标准\n\n"
|
||||
"| 项目 | 港澳台 | 其他地区 | 国外 |\n"
|
||||
"| --- | --- | --- | --- |\n"
|
||||
"| 餐补 | 75 | 55 | 140 |\n"
|
||||
"| 住宿补贴 | 35 | 35 | 35 |\n"
|
||||
),
|
||||
}
|
||||
],
|
||||
},
|
||||
),
|
||||
citations=[],
|
||||
)
|
||||
|
||||
assert answer is not None
|
||||
assert "| 项目 | 港澳台 | 其他地区 | 国外 |" in answer
|
||||
assert "| 餐补 | 75 | 55 | 140 |" in answer
|
||||
|
||||
|
||||
def test_user_agent_fast_knowledge_answer_notes_missing_location_grounding() -> None:
|
||||
session_factory = build_session_factory()
|
||||
with session_factory() as db:
|
||||
ontology = SemanticOntologyService(db).parse(
|
||||
OntologyParseRequest(
|
||||
query="前往北京出差的报销标准是什么?",
|
||||
user_id="pytest",
|
||||
context_json={"session_type": "knowledge"},
|
||||
)
|
||||
)
|
||||
service = UserAgentService(db)
|
||||
|
||||
answer = service._build_fast_knowledge_answer(
|
||||
UserAgentRequest(
|
||||
run_id=ontology.run_id,
|
||||
user_id="pytest",
|
||||
message="前往北京出差的报销标准是什么?",
|
||||
ontology=ontology,
|
||||
context_json={
|
||||
"session_type": "knowledge",
|
||||
"user_input_text": "前往北京出差的报销标准是什么?",
|
||||
},
|
||||
tool_payload={
|
||||
"result_type": "knowledge_search",
|
||||
"hits": [
|
||||
{
|
||||
"title": "费用报销制度",
|
||||
"content": (
|
||||
"# 结构化表格补充\n\n"
|
||||
"## 表3 出差补贴标准\n\n"
|
||||
"| 项目 | 港澳台 | 直辖市/特区/西藏 | 其他地区 |\n"
|
||||
"| --- | --- | --- | --- |\n"
|
||||
"| 餐补 | 75 | 65 | 55 |\n"
|
||||
"| 基本补贴 | 35 | 35 | 35 |\n"
|
||||
),
|
||||
}
|
||||
],
|
||||
},
|
||||
),
|
||||
citations=[],
|
||||
)
|
||||
|
||||
assert answer is not None
|
||||
assert "没有直接写出“北京”对应的地区档位或映射关系" in answer
|
||||
|
||||
|
||||
def test_user_agent_model_prompt_supports_contextual_personalization() -> None:
|
||||
session_factory = build_session_factory()
|
||||
with session_factory() as db:
|
||||
ontology = SemanticOntologyService(db).parse(
|
||||
OntologyParseRequest(
|
||||
session_factory = build_session_factory()
|
||||
with session_factory() as db:
|
||||
ontology = SemanticOntologyService(db).parse(
|
||||
OntologyParseRequest(
|
||||
query="我能坐什么舱位?",
|
||||
user_id="pytest",
|
||||
)
|
||||
@@ -159,8 +396,8 @@ def test_user_agent_model_prompt_supports_contextual_personalization() -> None:
|
||||
|
||||
system_prompt = messages[0]["content"]
|
||||
user_prompt = messages[1]["content"]
|
||||
assert "user_grade" in system_prompt
|
||||
assert "conversation_history" in system_prompt
|
||||
assert "context.user_grade" in system_prompt
|
||||
assert "conversation_history" in user_prompt
|
||||
assert '"user_name": "张三"' in user_prompt
|
||||
assert '"user_position": "财务分析师"' in user_prompt
|
||||
assert '"user_grade": "P5"' in user_prompt
|
||||
|
||||
Reference in New Issue
Block a user