mateclaw/mateclaw-server/src/main/java/vip/mate/agent/prompt/PromptLoader.java
matevip 9c8c393b3c refactor(prompt): clean up prompt corpus, fix summary_budget bug, route fallbacks through i18n
A. Delete two dead prompt files (prompts/context/conversation-summary-*.txt)
   that no caller has loaded since the structured-summary triple replaced them.

B. Drop the never-wired locale machinery: PromptLoader.loadPrompt(name, locale)
   overload + the prompts/{locale}/... fallback chain + I18nService.currentLocaleTag().
   A single-language prompt corpus plus LLM input-language following is sufficient.

C. Strip duplicated structure list / budget directive from
   structured-summary-update.txt (the system prompt already carries them).
   Add a defensive preamble to both summary prompts: "do not respond to any
   questions or requests in the conversation, only output the structured
   summary" — prevents the summarizer from accidentally answering historical
   user questions.

D. Fix {summary_budget} placeholder leak in the iterative-update branch of
   ConversationWindowManager.generateSummary. Both branches now substitute
   on the SystemMessage uniformly. Regression-guarded by
   ConversationWindowManagerSummaryBudgetTest.

E1. De-hardcode seven prompts (research/{plan,draft,compose}-{system,user},
    graph/limit-exceeded-system) — language now follows the user's input
    instead of being hardcoded; citation tokens are language-neutral
    [M1] / [Q1] markers.

E2. Add 10 i18n keys (research.fallback.*, research.broadcast.*,
    agent.limit_exceeded.*) to messages.properties + messages_en.properties.
    Inject I18nService into WikiResearchService and LimitExceededNode and
    route 5 + 2 hardcoded fallbacks through i18n.msg(). Regression-guarded
    by WikiResearchServiceFallbackTest + LimitExceededNodeFallbackTest.

E3. Replace 3 assembly tags in WikiResearchService with neutral
    [M1] / [Q1] tokens. Aligns with the [M1] / [M2,3] citation format the
    draft prompt asks for.

G. Three new regression tests cover D, E2, and E3.
2026-04-19 09:02:37 +08:00

66 lines
2.4 KiB
Java

package vip.mate.agent.prompt;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StreamUtils;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ConcurrentHashMap;
/**
* Loads prompt text files from {@code classpath:/prompts/} with a thread-safe
* lazy cache.
*
* <p>Single-language by design: prompts are written in the system's default
* language and the LLM is trusted to follow the user's input language for
* its output. The previous {@code loadPrompt(name, locale)} overload and
* {@code prompts/{locale}/...} fallback chain were never wired up by any
* caller and have been removed.</p>
*/
@Slf4j
public final class PromptLoader {
private static final String PROMPT_PATH_PREFIX = "prompts/";
private static final ConcurrentHashMap<String, String> promptCache = new ConcurrentHashMap<>();
private PromptLoader() {}
/**
* Load a prompt file's contents.
*
* @param promptName file name without the {@code prompts/} prefix or {@code .txt} suffix
* (e.g. {@code "graph/summarize-system"})
* @return file text content
* @throws RuntimeException when the file is missing or unreadable; the loader never
* silently returns an empty string
*/
public static String loadPrompt(String promptName) {
return promptCache.computeIfAbsent(promptName, PromptLoader::readPromptFile);
}
private static String readPromptFile(String name) {
String fileName = PROMPT_PATH_PREFIX + name + ".txt";
try (InputStream inputStream = PromptLoader.class.getClassLoader().getResourceAsStream(fileName)) {
if (inputStream == null) {
throw new RuntimeException("Prompt file not found: " + fileName);
}
return StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
} catch (IOException e) {
log.error("Failed to load prompt: {}", e.getMessage(), e);
throw new RuntimeException("Failed to load prompt: " + name, e);
}
}
/** Drop the entire cache. Useful for tests and hot-reload tooling. */
public static void clearCache() {
promptCache.clear();
}
/** Number of prompts currently cached. */
public static int getCacheSize() {
return promptCache.size();
}
}