From 921b56cfb94d9fe132cd8376a938825c038603f0 Mon Sep 17 00:00:00 2001 From: Nelson <1475262689@qq.com> Date: Thu, 16 Jul 2026 17:16:51 +0800 Subject: [PATCH] fix(agent): keep response policy in managed prompt --- .../apps/backend/app/services/rag_service.py | 43 ++----------------- .../backend/tests/test_knowledge_agent.py | 24 ----------- 2 files changed, 3 insertions(+), 64 deletions(-) diff --git a/ai_knowledge_base_v2/apps/backend/app/services/rag_service.py b/ai_knowledge_base_v2/apps/backend/app/services/rag_service.py index 02e18c1..f516f84 100644 --- a/ai_knowledge_base_v2/apps/backend/app/services/rag_service.py +++ b/ai_knowledge_base_v2/apps/backend/app/services/rag_service.py @@ -1,6 +1,5 @@ from __future__ import annotations -import re from dataclasses import dataclass from sqlalchemy import select @@ -128,8 +127,6 @@ class PromptService: if not context: context = "本轮没有可靠的正式知识章节。对于一般常识可以谨慎回答;涉及课程、老师观点或公司业务时必须说明依据不足,不得编造。" - completeness_constraint = cls._list_completeness_constraint(question, chunks) - messages: list[dict[str, str]] = [ { "role": "system", @@ -157,47 +154,11 @@ class PromptService: messages.append({ "role": "system", - "content": ( - "[本轮可靠知识上下文]\n" - "以下章节均来自当前正式开放、已发布的知识库,可以作为本轮回答的可靠依据。" - "如果其中已经明确包含用户询问的课程、作业、功课或练习,不得再以‘没有资料’、" - "‘无法确认’或要求用户补充课程名称来回避回答;应直接根据章节标题和正文归纳。" - "用户问‘某课程的作业是什么’时,含义是询问该课程包含哪些作业或练习," - "不要求知识库中必须存在一个与‘某课程作业’完全同名的章节。" - "回答作业清单类问题时,应直接给出作业名称,并概括每项作业的用途或核心方向;" - "正文已经提供基本步骤时可以简要说明,不要把本轮已经取到的内容留到下一轮再问。" - "章节标题可以作为作业或练习名称,标题下的子章节是对应的正式说明。" - f"{completeness_constraint}\n\n" - f"{context}" - ), + "content": f"[本轮可靠知识上下文]\n{context}", }) messages.append({"role": "user", "content": question.strip()}) return messages - @staticmethod - def _list_completeness_constraint(question: str, chunks: list[RetrievedChunk]) -> str: - is_practice_list = ( - any(marker in question for marker in ("作业", "功课", "练习")) - and any(marker in question for marker in ("有哪些", "是什么", "包括什么", "都有什么", "列出", "汇总", "总结")) - ) - numbered = [ - chunk for chunk in chunks - if re.search(r"(?:练习|作业|功课)\s*[一二三四五六七八九十百\d]+", chunk.title) - ] - if not is_practice_list or not numbered: - return "" - titles = "\n".join( - f"{index}. {chunk.knowledge_name} / {chunk.title}" - for index, chunk in enumerate(numbered, start=1) - ) - return ( - "\n\n[作业清单完整性约束]\n" - f"本轮共召回 {len(numbered)} 个正式编号练习。回答必须覆盖下面全部 {len(numbered)} 项," - "不得只挑选部分项目,不得合并成少数概念,不得新增清单外的作业。" - "应按知识库分组,逐项保留正式练习名称,并根据对应正文简要概括;即使回答较长也必须列完。\n" - f"必须覆盖的标题清单:\n{titles}" - ) - @staticmethod def render_messages(messages: list[dict[str, str]]) -> str: role_labels = {"system": "系统", "user": "用户", "assistant": "大本营答疑助手"} @@ -208,6 +169,8 @@ class PromptService: @staticmethod def _clean_history_content(content: str) -> str: + import re + return re.sub(r"", "", content, flags=re.IGNORECASE).strip() @classmethod diff --git a/ai_knowledge_base_v2/apps/backend/tests/test_knowledge_agent.py b/ai_knowledge_base_v2/apps/backend/tests/test_knowledge_agent.py index d0ea94f..9791df6 100644 --- a/ai_knowledge_base_v2/apps/backend/tests/test_knowledge_agent.py +++ b/ai_knowledge_base_v2/apps/backend/tests/test_knowledge_agent.py @@ -21,7 +21,6 @@ from app.models.knowledge import ( ) from app.models.chat import ChatMessage from app.services.knowledge_agent_service import Candidate, KnowledgeAgentService -from app.services.rag_service import PromptService, RetrievedChunk def _database() -> Session: @@ -146,9 +145,6 @@ def test_course_question_searches_chunk_and_reads_parent_section(): knowledge_context = next( message["content"] for message in result.messages if message["content"].startswith("[本轮可靠知识上下文]") ) - assert "不得再以‘没有资料’" in knowledge_context - assert "不要求知识库中必须存在一个" in knowledge_context - assert "不要把本轮已经取到的内容留到下一轮再问" in knowledge_context assert "来源知识库:亲子课程" in knowledge_context @@ -228,26 +224,6 @@ def test_homework_overview_keeps_all_numbered_practices_before_selection(): assert all(item.chunk.title.startswith("练习") for item in selected) -def test_homework_overview_prompt_requires_every_recalled_title(): - chunks = [ - RetrievedChunk( - knowledge_id=1 if index <= 6 else 2, - knowledge_name="《原生·上》" if index <= 6 else "《原生·下》", - title=f"练习{index}:作业{index}", - content=f"作业{index}的正式说明", - ) - for index in range(1, 15) - ] - - constraint = PromptService._list_completeness_constraint("原生里的作业都有哪些", chunks) - - assert "本轮共召回 14 个正式编号练习" in constraint - assert "必须覆盖下面全部 14 项" in constraint - assert "不得新增清单外的作业" in constraint - assert "练习1:作业1" in constraint - assert "练习14:作业14" in constraint - - def test_complete_section_includes_child_headings_but_stops_at_next_peer(): with _database() as db: knowledge = _add_published_knowledge(db, knowledge_id=1, name="合一课程")