feat: expose knowledge type priority traces

This commit is contained in:
2026-07-31 16:09:27 +08:00
parent 429636f3e8
commit 4e872febd5
5 changed files with 44 additions and 3 deletions

View File

@@ -1556,7 +1556,7 @@ function formatRecordDateTime(value: string, boundary: "start" | "end") {
</dl> </dl>
<h3>本轮 KnowledgeList</h3><pre class="trace-json">{{ JSON.stringify(selectedRetrievalLog.catalogSnapshot, null, 2) }}</pre> <h3>本轮 KnowledgeList</h3><pre class="trace-json">{{ JSON.stringify(selectedRetrievalLog.catalogSnapshot, null, 2) }}</pre>
<h3>Agent 工具轨迹</h3><pre class="trace-json">{{ JSON.stringify(selectedRetrievalLog.toolTrace, null, 2) }}</pre> <h3>Agent 工具轨迹</h3><pre class="trace-json">{{ JSON.stringify(selectedRetrievalLog.toolTrace, null, 2) }}</pre>
<h3>候选切片与取舍</h3><el-table :data="selectedRetrievalLog.candidates"><el-table-column prop="knowledgeId" label="知识库" width="90" /><el-table-column prop="versionId" label="版本" width="80" /><el-table-column prop="sectionId" label="章节" width="80" /><el-table-column prop="lexicalScore" label="词法分" width="100" /><el-table-column prop="rerankScore" label="重排分" width="100" /><el-table-column label="采用" width="80"><template #default="{ row }">{{ row.selected ? '是' : '否' }}</template></el-table-column><el-table-column prop="discardReason" label="舍弃原因" min-width="180" /></el-table> <h3>候选切片与取舍</h3><el-table :data="selectedRetrievalLog.candidates"><el-table-column prop="knowledgeId" label="知识库" width="90" /><el-table-column prop="knowledgeName" label="名称" min-width="150" show-overflow-tooltip /><el-table-column prop="knowledgeTypeLabel" label="类型优先级" width="170" /><el-table-column prop="versionId" label="版本" width="80" /><el-table-column prop="sectionId" label="章节" width="80" /><el-table-column prop="lexicalScore" label="词法分" width="100" /><el-table-column prop="rerankScore" label="重排分" width="100" /><el-table-column label="采用" width="80"><template #default="{ row }">{{ row.selected ? '是' : '否' }}</template></el-table-column><el-table-column prop="discardReason" label="舍弃原因" min-width="180" /></el-table>
<h3>最终回答</h3><pre class="trace-json">{{ selectedRetrievalLog.finalAnswer || '尚未生成回答' }}</pre> <h3>最终回答</h3><pre class="trace-json">{{ selectedRetrievalLog.finalAnswer || '尚未生成回答' }}</pre>
</template> </template>
</el-drawer> </el-drawer>

View File

@@ -379,6 +379,15 @@ function typeLabel(value: string) {
return knowledgeTypeOptions.find((item) => item.value === value)?.label || value; return knowledgeTypeOptions.find((item) => item.value === value)?.label || value;
} }
function typeDescription(value: string) {
return ({
fixed: "固定信息类用于上课时间、带练安排、回放音频、课程作业、会议链接、服务权益、助理联系方式等当前安排;与其他库冲突时优先采用固定信息类。",
course: "课程知识库用于课程概念、功课步骤、老师观点和课程原文解释;优先级高于答疑和通用知识。",
qa: "答疑知识库用于参考过往问答和常见问题,但不能覆盖课程原文或固定信息类的当前安排。",
general: "通用知识库只做背景补充,不能覆盖课程知识、答疑知识或固定信息类。",
} as Record<string, string>)[value] || "用于补充背景信息。";
}
function contentItemTypeLabel(value: string) { function contentItemTypeLabel(value: string) {
return ({ section: "章节", card: "知识卡", chunk: "切片" } as Record<string, string>)[value] || value; return ({ section: "章节", card: "知识卡", chunk: "切片" } as Record<string, string>)[value] || value;
} }
@@ -552,6 +561,7 @@ async function confirmAction(
<el-tab-pane label="基本信息" name="overview"> <el-tab-pane label="基本信息" name="overview">
<div class="knowledge-section-head"><h4>基本信息</h4><el-button type="primary" plain @click="openEdit">编辑基本信息</el-button></div> <div class="knowledge-section-head"><h4>基本信息</h4><el-button type="primary" plain @click="openEdit">编辑基本信息</el-button></div>
<dl class="knowledge-meta"><dt>后台名称</dt><dd>{{ detail.knowledge.name }}</dd><dt>知识库类型</dt><dd>{{ typeLabel(detail.knowledge.knowledgeType) }}</dd><dt>版本更新</dt><dd>同步成功后自动替换</dd><dt>SpaceID</dt><dd>{{ detail.knowledge.feishuSpaceId }}</dd><dt>NodeID</dt><dd>{{ detail.knowledge.feishuNodeId }}</dd><dt>当前版本</dt><dd>{{ detail.currentVersion ? `V${detail.currentVersion.versionNo}` : '暂无' }}</dd></dl> <dl class="knowledge-meta"><dt>后台名称</dt><dd>{{ detail.knowledge.name }}</dd><dt>知识库类型</dt><dd>{{ typeLabel(detail.knowledge.knowledgeType) }}</dd><dt>版本更新</dt><dd>同步成功后自动替换</dd><dt>SpaceID</dt><dd>{{ detail.knowledge.feishuSpaceId }}</dd><dt>NodeID</dt><dd>{{ detail.knowledge.feishuNodeId }}</dd><dt>当前版本</dt><dd>{{ detail.currentVersion ? `V${detail.currentVersion.versionNo}` : '暂无' }}</dd></dl>
<el-alert :title="typeDescription(detail.knowledge.knowledgeType)" type="info" :closable="false" show-icon />
<section v-if="detail.manifest" class="manifest-panel"><h4>Knowledge Manifest</h4><p><strong>用途</strong>{{ detail.manifest.purpose }}</p><p><strong>适用</strong>{{ detail.manifest.applicableQuestions }}</p><p><strong>不适用</strong>{{ detail.manifest.inapplicableQuestions }}</p><p><strong>核心主题</strong>{{ detail.manifest.coreTopics }}</p><p><strong>边界</strong>{{ detail.manifest.boundaries }}</p></section> <section v-if="detail.manifest" class="manifest-panel"><h4>Knowledge Manifest</h4><p><strong>用途</strong>{{ detail.manifest.purpose }}</p><p><strong>适用</strong>{{ detail.manifest.applicableQuestions }}</p><p><strong>不适用</strong>{{ detail.manifest.inapplicableQuestions }}</p><p><strong>核心主题</strong>{{ detail.manifest.coreTopics }}</p><p><strong>边界</strong>{{ detail.manifest.boundaries }}</p></section>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="内容预览" name="content" lazy> <el-tab-pane label="内容预览" name="content" lazy>

View File

@@ -14,6 +14,7 @@ from app.models.admin import Admin
from app.models.knowledge import ( from app.models.knowledge import (
HumanAttentionHistory, HumanAttentionHistory,
HumanAttentionRecord, HumanAttentionRecord,
Knowledge,
KnowledgeRetrievalCandidate, KnowledgeRetrievalCandidate,
KnowledgeRetrievalLog, KnowledgeRetrievalLog,
) )
@@ -113,11 +114,19 @@ def retrieval_detail(
.where(KnowledgeRetrievalCandidate.retrieval_log_id == log.id) .where(KnowledgeRetrievalCandidate.retrieval_log_id == log.id)
.order_by(KnowledgeRetrievalCandidate.selected.desc(), KnowledgeRetrievalCandidate.id) .order_by(KnowledgeRetrievalCandidate.selected.desc(), KnowledgeRetrievalCandidate.id)
).all() ).all()
knowledge_ids = {item.knowledge_id for item in candidates}
knowledge_map = {
item.id: item
for item in db.scalars(select(Knowledge).where(Knowledge.id.in_(knowledge_ids))).all()
} if knowledge_ids else {}
data = _retrieval_dict(log, detail=True) data = _retrieval_dict(log, detail=True)
data["candidates"] = [ data["candidates"] = [
{ {
"id": item.id, "id": item.id,
"knowledgeId": item.knowledge_id, "knowledgeId": item.knowledge_id,
"knowledgeName": knowledge_map[item.knowledge_id].name if item.knowledge_id in knowledge_map else "",
"knowledgeType": knowledge_map[item.knowledge_id].knowledge_type if item.knowledge_id in knowledge_map else "general",
"knowledgeTypeLabel": _knowledge_type_label(knowledge_map[item.knowledge_id].knowledge_type if item.knowledge_id in knowledge_map else "general"),
"versionId": item.version_id, "versionId": item.version_id,
"chunkId": item.chunk_id, "chunkId": item.chunk_id,
"sectionId": item.section_id, "sectionId": item.section_id,
@@ -131,6 +140,15 @@ def retrieval_detail(
return api_success(data) return api_success(data)
def _knowledge_type_label(value: str) -> str:
return {
"fixed": "固定信息类·最高优先级",
"course": "课程知识库",
"qa": "答疑知识库",
"general": "通用知识库",
}.get(value, value or "通用知识库")
@router.get("/attention/list") @router.get("/attention/list")
def attention_list( def attention_list(
priority: str = Query(default=""), priority: str = Query(default=""),

View File

@@ -154,7 +154,7 @@ class KnowledgeAgentService:
candidate_limit=candidate_limit, candidate_limit=candidate_limit,
prefer_fixed_information=cls._is_fixed_information_query(retrieval_question), prefer_fixed_information=cls._is_fixed_information_query(retrieval_question),
) )
trace.append(cls._trace("search_knowledge", len(trace) + 1, {"queryTerms": terms, "knowledgeIds": selected_ids, "candidateLimit": candidate_limit}, {"candidateCount": len(candidates), "candidates": [cls._candidate_trace(x) for x in candidates]}, started)) trace.append(cls._trace("search_knowledge", len(trace) + 1, {"queryTerms": terms, "knowledgeIds": selected_ids, "candidateLimit": candidate_limit, "preferFixedInformation": cls._is_fixed_information_query(retrieval_question)}, {"candidateCount": len(candidates), "candidates": [cls._candidate_trace(x) for x in candidates]}, started))
await cls._rerank(db, retrieval_question, candidates, trace, started) await cls._rerank(db, retrieval_question, candidates, trace, started)
selected = cls._select_sections( selected = cls._select_sections(
candidates, candidates,
@@ -391,7 +391,7 @@ class KnowledgeAgentService:
@staticmethod @staticmethod
def _candidate_trace(item: Candidate) -> dict: def _candidate_trace(item: Candidate) -> dict:
return {"knowledgeId": item.knowledge.id, "knowledgeName": item.knowledge.name, "versionId": item.version.id, "chunkId": item.chunk.id, "sectionId": item.section.id, "title": item.chunk.title, "lexicalScore": item.lexical_score, "rerankScore": item.rerank_score, "selected": item.selected, "discardReason": item.discard_reason} return {"knowledgeId": item.knowledge.id, "knowledgeName": item.knowledge.name, "knowledgeType": item.knowledge.knowledge_type, "knowledgeTypePriority": _knowledge_type_priority_label(item.knowledge.knowledge_type), "versionId": item.version.id, "chunkId": item.chunk.id, "sectionId": item.section.id, "title": item.chunk.title, "lexicalScore": item.lexical_score, "rerankScore": item.rerank_score, "selected": item.selected, "discardReason": item.discard_reason}
@classmethod @classmethod
def search_knowledge( def search_knowledge(
@@ -732,6 +732,15 @@ def _json_trace_value(value):
return str(value) return str(value)
def _knowledge_type_priority_label(value: str) -> str:
return {
"fixed": "最高优先级:固定信息类冲突时优先",
"course": "课程内容优先于答疑和通用知识",
"qa": "过往答疑参考,不覆盖课程原文",
"general": "背景补充,不覆盖业务知识",
}.get(value, "背景补充")
def _heading_level(content: str) -> int | None: def _heading_level(content: str) -> int | None:
match = re.match(r"^(#{1,6})\s+", content.lstrip()) match = re.match(r"^(#{1,6})\s+", content.lstrip())
return len(match.group(1)) if match else None return len(match.group(1)) if match else None

View File

@@ -539,6 +539,10 @@ Agent 已经可以:
- 检索日志能看到知识库类型和优先级; - 检索日志能看到知识库类型和优先级;
- 知识冲突时能说明采用固定信息类。 - 知识冲突时能说明采用固定信息类。
#### 开发进度
- 2026-07-31已有固定信息类优先召回、Prompt 类型规则注入和冲突处理规则;本次补充检索日志候选切片的知识库类型/优先级展示,并在知识库详情页展示类型使用规则说明。
--- ---
### 08. 柔性额度提醒 ### 08. 柔性额度提醒