mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
feat(wiki): inject pageType profile into route/create/merge prompts + content templates
This commit is contained in:
parent
551321b542
commit
8e8c1e34c7
@ -90,6 +90,56 @@ public class WikiPageTypeProfileService {
|
||||
return resolveProfile(kbId).getPageTypes().keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* The per-stage LLM instruction declared for a pageType, or empty string.
|
||||
* {@code stage} is {@code route} / {@code create} / {@code merge}. Used to
|
||||
* inject type-specific guidance into the corresponding stage prompt.
|
||||
*/
|
||||
public String stageInstruction(Long kbId, String pageType, String stage) {
|
||||
WikiPageTypeDef def = resolveProfile(kbId).get(pageType);
|
||||
if (def == null || stage == null) {
|
||||
return "";
|
||||
}
|
||||
WikiPageTypeDef.StageInstructions si = switch (stage) {
|
||||
case "route" -> def.getRoute();
|
||||
case "create" -> def.getCreate();
|
||||
case "merge" -> def.getMerge();
|
||||
default -> null;
|
||||
};
|
||||
return (si != null && si.getInstructions() != null) ? si.getInstructions().trim() : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the per-type Markdown templates as a prompt block for the
|
||||
* multi-page batch-create stage (where one call generates pages of several
|
||||
* types). Only types that declare a template are listed; empty when none.
|
||||
*/
|
||||
public String describeTemplatesForPrompt(Long kbId) {
|
||||
WikiPageTypeProfile profile = resolveProfile(kbId);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
profile.getPageTypes().forEach((name, def) -> {
|
||||
if (def != null && def.getTemplate() != null
|
||||
&& def.getTemplate().getMarkdown() != null
|
||||
&& !def.getTemplate().getMarkdown().isBlank()) {
|
||||
sb.append("### ").append(name).append(" 骨架\n")
|
||||
.append(def.getTemplate().getMarkdown().trim()).append("\n\n");
|
||||
}
|
||||
});
|
||||
return sb.toString().trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* The Markdown content template for a pageType, or empty string. Injected
|
||||
* into the generation prompt so the page follows the declared skeleton.
|
||||
*/
|
||||
public String templateMarkdown(Long kbId, String pageType) {
|
||||
WikiPageTypeDef def = resolveProfile(kbId).get(pageType);
|
||||
if (def == null || def.getTemplate() == null || def.getTemplate().getMarkdown() == null) {
|
||||
return "";
|
||||
}
|
||||
return def.getTemplate().getMarkdown().trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the KB's allowed page types as a prompt fragment, one per line
|
||||
* with description and required-metadata hints, e.g.
|
||||
|
||||
@ -813,7 +813,8 @@ public class WikiProcessingService {
|
||||
// this picks up changes from sequential chunks without an extra DB hit when nothing changed.
|
||||
String freshIndex = buildExistingPagesIndex(kbId);
|
||||
|
||||
String routeSystem = PromptLoader.loadPrompt("wiki/route-system");
|
||||
String routeSystem = PromptLoader.loadPrompt("wiki/route-system")
|
||||
.replace("{allowed_page_types}", allowedTypesFragment(kbId));
|
||||
String routeUserTemplate = PromptLoader.loadPrompt("wiki/route-user");
|
||||
String documentMapSection = buildDocumentMapSection(documentMap);
|
||||
String routeUser = routeUserTemplate
|
||||
@ -1082,10 +1083,13 @@ public class WikiProcessingService {
|
||||
// the profile recognises. Default-profile KBs get the same list
|
||||
// as the previous hardcoded enum, so behaviour is unchanged.
|
||||
batchSystem = batchSystem.replace("{allowed_page_types}",
|
||||
pageTypeProfileService.describeForPrompt(kbId));
|
||||
pageTypeProfileService.describeForPrompt(kbId))
|
||||
.replace("{page_type_templates}",
|
||||
emptyOr(pageTypeProfileService.describeTemplatesForPrompt(kbId)));
|
||||
} else {
|
||||
batchSystem = batchSystem.replace("{allowed_page_types}",
|
||||
"concept / person / place / event / technology / organization / product / term / process / other");
|
||||
"concept / person / place / event / technology / organization / product / term / process / other")
|
||||
.replace("{page_type_templates}", "(无)");
|
||||
}
|
||||
String batchUserTemplate = PromptLoader.loadPrompt("wiki/batch-create-user");
|
||||
String docMapSection = buildDocumentMapSection(documentMap);
|
||||
@ -1264,7 +1268,9 @@ public class WikiProcessingService {
|
||||
String title = pageMeta.path("title").asText("");
|
||||
String summary = pageMeta.path("summary").asText("");
|
||||
String configContent = kb.getConfigContent() != null ? kb.getConfigContent() : "";
|
||||
String createSystem = PromptLoader.loadPrompt("wiki/create-page-system");
|
||||
String createSystem = PromptLoader.loadPrompt("wiki/create-page-system")
|
||||
.replace("{page_type_instructions}",
|
||||
typeGuidance(kb.getId(), pageMeta.path("page_type").asText(""), "create"));
|
||||
String createUserTemplate = PromptLoader.loadPrompt("wiki/create-page-user");
|
||||
String createUser = createUserTemplate
|
||||
.replace("{config}", configContent)
|
||||
@ -1492,6 +1498,38 @@ public class WikiProcessingService {
|
||||
}
|
||||
}
|
||||
|
||||
private String emptyOr(String s) {
|
||||
return (s == null || s.isBlank()) ? "(无)" : s;
|
||||
}
|
||||
|
||||
/** Allowed page types fragment for prompt injection (profile-driven; legacy fallback). */
|
||||
private String allowedTypesFragment(Long kbId) {
|
||||
return pageTypeProfileService != null
|
||||
? pageTypeProfileService.describeForPrompt(kbId)
|
||||
: "concept / person / place / event / technology / organization / product / term / process / other";
|
||||
}
|
||||
|
||||
/**
|
||||
* Per-type guidance for the create / merge prompts: the stage instruction
|
||||
* plus, for the create stage, the Markdown template skeleton. Empty-safe.
|
||||
*/
|
||||
private String typeGuidance(Long kbId, String pageType, String stage) {
|
||||
if (pageTypeProfileService == null || pageType == null || pageType.isBlank()) {
|
||||
return "(无特定指引)";
|
||||
}
|
||||
String instr = pageTypeProfileService.stageInstruction(kbId, pageType, stage);
|
||||
String tpl = "create".equals(stage) ? pageTypeProfileService.templateMarkdown(kbId, pageType) : "";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (instr != null && !instr.isBlank()) {
|
||||
sb.append(instr);
|
||||
}
|
||||
if (tpl != null && !tpl.isBlank()) {
|
||||
if (sb.length() > 0) sb.append("\n\n");
|
||||
sb.append("请按以下 Markdown 骨架组织正文:\n").append(tpl);
|
||||
}
|
||||
return sb.length() == 0 ? "(无特定指引)" : sb.toString();
|
||||
}
|
||||
|
||||
private void applyValidatedMetadata(Long pageId, Long kbId, String pageType,
|
||||
JsonNode metadataNode) {
|
||||
if (pageId == null || pageTypeProfileService == null || metadataValidator == null) {
|
||||
@ -1546,7 +1584,9 @@ public class WikiProcessingService {
|
||||
}
|
||||
|
||||
String configContent = kb.getConfigContent() != null ? kb.getConfigContent() : "";
|
||||
String mergeSystem = PromptLoader.loadPrompt("wiki/merge-page-system");
|
||||
String mergeSystem = PromptLoader.loadPrompt("wiki/merge-page-system")
|
||||
.replace("{page_type_merge_instruction}",
|
||||
typeGuidance(kbId, existing.getPageType(), "merge"));
|
||||
// Trim existing content to prevent context overflow on small models (qwen-turbo: 4096 tokens).
|
||||
// Merging a 3000-char page + 30K chunk blows past the limit → truncated JSON → parse failure.
|
||||
// 1800 chars ≈ ~600 tokens, leaving ample room for the chunk and response.
|
||||
@ -2348,7 +2388,9 @@ public class WikiProcessingService {
|
||||
// Use existing two-phase single-page create logic
|
||||
String existingPagesIndex = buildExistingPagesIndex(kb.getId());
|
||||
String configContent = kb.getConfigContent() != null ? kb.getConfigContent() : "";
|
||||
String createSystem = PromptLoader.loadPrompt("wiki/create-page-system");
|
||||
String createSystem = PromptLoader.loadPrompt("wiki/create-page-system")
|
||||
.replace("{page_type_instructions}",
|
||||
typeGuidance(kb.getId(), page.getPageType(), "create"));
|
||||
String createUserTemplate = PromptLoader.loadPrompt("wiki/create-page-user");
|
||||
String createUser = createUserTemplate
|
||||
.replace("{config}", configContent)
|
||||
|
||||
@ -55,3 +55,6 @@
|
||||
|
||||
允许的页面类型:
|
||||
{allowed_page_types}
|
||||
|
||||
各类型内容模板(若某页的 page_type 有对应骨架,请按骨架组织正文;无则自由组织):
|
||||
{page_type_templates}
|
||||
|
||||
@ -55,3 +55,6 @@
|
||||
- `title`:通常与 metadata 一致;如有更精确的描述可微调
|
||||
- `content`:完整 markdown,开头一段摘要,后面分章节
|
||||
- `summary`:一段话简短摘要(可与 metadata.summary 一致或精炼)
|
||||
|
||||
## 内容指引(本知识库 / 该页类型)
|
||||
{page_type_instructions}
|
||||
|
||||
@ -47,3 +47,6 @@
|
||||
- `title`:通常保持不变;只有当新材料明确改变了页面主题时才改
|
||||
- `content`:完整 markdown,包含原内容中仍然有效的部分 + 来自新材料的新增信息
|
||||
- `summary`:更新后的一段话摘要
|
||||
|
||||
## 该类型的合并策略(本知识库)
|
||||
{page_type_merge_instruction}
|
||||
|
||||
@ -68,6 +68,12 @@
|
||||
- `update`:已存在但需要根据本材料合并更新的页面,**只列 slug 字符串数组,最多 5 条**
|
||||
- 两个数组合计通常至少 1 条(材料完全无价值——如空白页、纯目录——才允许全空)
|
||||
|
||||
## 允许的页面类型(本知识库)
|
||||
|
||||
为每个 create 项判断最贴切的类型;不确定时按默认处理。可在 create 项加 `page_type` 字段。
|
||||
|
||||
{allowed_page_types}
|
||||
|
||||
## 关键纪律
|
||||
|
||||
- 输出体积应该是几百到几千字,**不要超过几 KB**。如果你发现自己在写正文,立刻停下 —— 那是下一阶段的工作。
|
||||
|
||||
@ -89,6 +89,27 @@ class WikiPageTypeProfileServiceTest {
|
||||
assertTrue(fragment.contains("- other"), fragment);
|
||||
}
|
||||
|
||||
@Test
|
||||
void stageInstructionAndTemplate_areResolvedPerType() {
|
||||
WikiPageTypeProfileEntity row = new WikiPageTypeProfileEntity();
|
||||
row.setKbId(1L);
|
||||
row.setEnabled(1);
|
||||
row.setConfigJson("{\"pageTypes\":{\"episode\":{"
|
||||
+ "\"route\":{\"instructions\":\"仅当有明确日期时路由为 episode\"},"
|
||||
+ "\"merge\":{\"instructions\":\"保留已审阅分析,追加新证据\"},"
|
||||
+ "\"create\":{\"instructions\":\"抽取 event_date\"},"
|
||||
+ "\"template\":{\"markdown\":\"## Summary\\n{{summary}}\\n## 事件\"}}}}");
|
||||
when(mapper.selectOne(any())).thenReturn(row);
|
||||
|
||||
assertTrue(service.stageInstruction(1L, "episode", "route").contains("明确日期"));
|
||||
assertTrue(service.stageInstruction(1L, "episode", "merge").contains("已审阅"));
|
||||
assertTrue(service.stageInstruction(1L, "episode", "create").contains("event_date"));
|
||||
assertTrue(service.templateMarkdown(1L, "episode").contains("## Summary"));
|
||||
// unknown type / stage → empty, never null
|
||||
assertEquals("", service.stageInstruction(1L, "nope", "route"));
|
||||
assertEquals("", service.templateMarkdown(1L, "nope"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void describeForPrompt_customProfile_showsRequiredMetadata() {
|
||||
WikiPageTypeProfileEntity row = new WikiPageTypeProfileEntity();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user