feat(wiki): inject KB pageType profile into the batch-create prompt

This commit is contained in:
matevip 2026-05-31 07:54:34 +08:00
parent bea48bcf89
commit b50e384e0d
4 changed files with 78 additions and 1 deletions

View File

@ -10,6 +10,7 @@ import vip.mate.wiki.model.WikiPageTypeProfileEntity;
import vip.mate.wiki.repository.WikiPageTypeProfileMapper;
import java.io.InputStream;
import java.util.Map;
import java.util.Set;
/**
@ -89,6 +90,35 @@ public class WikiPageTypeProfileService {
return resolveProfile(kbId).getPageTypes().keySet();
}
/**
* Render the KB's allowed page types as a prompt fragment, one per line
* with description and required-metadata hints, e.g.
* {@code - episode: a dated event (required metadata: event_type, event_date)}.
* Injected into the route / batch-create prompts so the LLM only emits
* types the KB recognises.
*/
public String describeForPrompt(Long kbId) {
WikiPageTypeProfile profile = resolveProfile(kbId);
StringBuilder sb = new StringBuilder();
profile.getPageTypes().forEach((name, def) -> {
sb.append("- ").append(name);
if (def != null && def.getDescription() != null && !def.getDescription().isBlank()) {
sb.append(": ").append(def.getDescription().trim());
}
if (def != null && def.getSchema() != null) {
java.util.List<String> required = def.getSchema().entrySet().stream()
.filter(e -> e.getValue() != null && e.getValue().isRequired())
.map(Map.Entry::getKey)
.toList();
if (!required.isEmpty()) {
sb.append(" (required metadata: ").append(String.join(", ", required)).append(")");
}
}
sb.append('\n');
});
return sb.toString().trim();
}
/** The enabled profile row for a KB, or {@code null} when none configured. */
public WikiPageTypeProfileEntity findEnabledRow(Long kbId) {
if (kbId == null) {

View File

@ -62,6 +62,14 @@ public class WikiProcessingService {
private final WikiCitationService citationService;
private final org.springframework.context.ApplicationEventPublisher eventPublisher;
/**
* Optional KB pageType profile. Field-injected (not a constructor arg) so
* existing instantiations are unaffected; when absent the batch-create
* prompt falls back to the legacy hardcoded pageType enum.
*/
@org.springframework.beans.factory.annotation.Autowired(required = false)
private vip.mate.wiki.profile.WikiPageTypeProfileService pageTypeProfileService;
/**
* Read-the-failover-chain handle. Optional so the existing constructors and
* lazy-mode tests don't have to thread a new dependency. When null, the
@ -1065,6 +1073,16 @@ public class WikiProcessingService {
metasJson.append("]");
String batchSystem = PromptLoader.loadPrompt("wiki/batch-create-system");
if (pageTypeProfileService != null) {
// Inject the KB's allowed page types so the LLM only emits types
// 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));
} else {
batchSystem = batchSystem.replace("{allowed_page_types}",
"concept / person / place / event / technology / organization / product / term / process / other");
}
String batchUserTemplate = PromptLoader.loadPrompt("wiki/batch-create-user");
String docMapSection = buildDocumentMapSection(documentMap);
String batchUser = batchUserTemplate

View File

@ -50,4 +50,8 @@
- `title`:与 metadata 保持一致;如有更精确的描述可微调
- `content`:完整 markdown 正文
- `summary`:一段话简短摘要
- `page_type`页面类型从以下值中选一个concept / person / place / event / technology / organization / product / term / process / other
- `page_type`:页面类型,从下面"允许的页面类型"列表中选一个;都不合适时选 concept。
- `metadata`(可选):与所选 page_type 对应的结构化字段对象,只输出该类型声明的字段(带"required metadata"标注的字段应尽量补全)。
允许的页面类型:
{allowed_page_types}

View File

@ -79,4 +79,29 @@ class WikiPageTypeProfileServiceTest {
assertEquals("concept", service.normalizePageType(1L, "made-up-type"));
assertEquals("concept", service.normalizePageType(1L, null));
}
@Test
void describeForPrompt_defaultProfile_listsBuiltInTypes() {
when(mapper.selectOne(any())).thenReturn(null);
String fragment = service.describeForPrompt(1L);
assertTrue(fragment.contains("- concept"), fragment);
assertTrue(fragment.contains("- person"), fragment);
assertTrue(fragment.contains("- other"), fragment);
}
@Test
void describeForPrompt_customProfile_showsRequiredMetadata() {
WikiPageTypeProfileEntity row = new WikiPageTypeProfileEntity();
row.setKbId(1L);
row.setEnabled(1);
row.setConfigJson("{\"pageTypes\":{\"episode\":{\"description\":\"a dated event\","
+ "\"schema\":{\"event_date\":{\"type\":\"date\",\"required\":true},"
+ "\"note\":{\"type\":\"string\",\"required\":false}}}}}");
when(mapper.selectOne(any())).thenReturn(row);
String fragment = service.describeForPrompt(1L);
assertTrue(fragment.contains("- episode: a dated event"), fragment);
assertTrue(fragment.contains("required metadata: event_date"), fragment);
assertFalse(fragment.contains("note"), fragment); // optional field not listed as required
}
}