fix(agent): enforce complete homework answers
This commit is contained in:
@@ -122,6 +122,8 @@ class KnowledgeAgentService:
|
||||
limit=cls._selection_limit(retrieval_question),
|
||||
prefer_numbered_practice=practice_overview,
|
||||
)
|
||||
if practice_overview:
|
||||
selected.sort(key=lambda item: (item.knowledge.name, item.section.sort_order))
|
||||
selected_contents = {
|
||||
item.section.id: cls._protect_content(cls._read_complete_section(db, item.section))
|
||||
for item in selected
|
||||
|
||||
@@ -116,7 +116,7 @@ class ModelClientService:
|
||||
return {
|
||||
"ok": True,
|
||||
"message": "Agent 调试完成",
|
||||
"answer": answer[:4000],
|
||||
"answer": answer,
|
||||
"prompt": rag_result.prompt,
|
||||
"modelName": model.model_name,
|
||||
"retrieveCount": len(rag_result.chunks),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
|
||||
from sqlalchemy import select
|
||||
@@ -127,6 +128,8 @@ class PromptService:
|
||||
if not context:
|
||||
context = "本轮没有可靠的正式知识章节。对于一般常识可以谨慎回答;涉及课程、老师观点或公司业务时必须说明依据不足,不得编造。"
|
||||
|
||||
completeness_constraint = cls._list_completeness_constraint(question, chunks)
|
||||
|
||||
messages: list[dict[str, str]] = [
|
||||
{
|
||||
"role": "system",
|
||||
@@ -163,13 +166,38 @@ class PromptService:
|
||||
"不要求知识库中必须存在一个与‘某课程作业’完全同名的章节。"
|
||||
"回答作业清单类问题时,应直接给出作业名称,并概括每项作业的用途或核心方向;"
|
||||
"正文已经提供基本步骤时可以简要说明,不要把本轮已经取到的内容留到下一轮再问。"
|
||||
"章节标题可以作为作业或练习名称,标题下的子章节是对应的正式说明。\n\n"
|
||||
"章节标题可以作为作业或练习名称,标题下的子章节是对应的正式说明。"
|
||||
f"{completeness_constraint}\n\n"
|
||||
f"{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": "大本营答疑助手"}
|
||||
@@ -180,8 +208,6 @@ class PromptService:
|
||||
|
||||
@staticmethod
|
||||
def _clean_history_content(content: str) -> str:
|
||||
import re
|
||||
|
||||
return re.sub(r"<think[\s\S]*?</think\s*>", "", content, flags=re.IGNORECASE).strip()
|
||||
|
||||
@classmethod
|
||||
|
||||
Reference in New Issue
Block a user