本文档用于沉淀当前项目中 src/main/resources/prompts/ 与 src/main/resources/skills/ 的注册方式、调用链路、触发条件与维护注意事项。
目标不是单纯列出资源文件,而是回答下面几个维护问题:
- 某个 prompt / skill 是否已经被代码接入。
- 它是“确定会触发”、"条件触发",还是“仅注册但不保证实际 tool call”。
- 哪些业务链路走默认
ChatClient,哪些走plain/voiceclient。 - 哪些 README / CLAUDE 中描述的能力已经落地,哪些仍然只是扩展目标。
本文档统一使用以下判定层级:
- 确定触发:存在明确业务入口,且代码中能追到实际模型调用(例如
.call()、.stream()或structuredOutputInvoker.invoke(...))。 - 条件触发:代码已接线,但依赖异步消费、配置开关、provider 分支或特定输入条件。
- 仅注册:资源或 ToolCallback 已注册到 Spring / ChatClient,但不能据此断言运行时一定发生 tool call。
- 未接入:当前代码中没有发现可达调用链。
需要特别区分两件事:
skills/*被后端代码读取并注入 Prompt。SkillsTool被注册到 Spring AI,并由模型在运行时真正发起 tool call。
前者在本项目中大量存在,后者只属于“具备能力”,不能默认视为每次都会发生。
当前 src/main/resources/prompts/ 下有 14 个模板文件;按本次静态核对,已在当前主链路中发现明确引用:
resume-analysis-system.stresume-analysis-user.stknowledgebase-query-system.stknowledgebase-query-user.stknowledgebase-query-rewrite.stinterview-question-skill-system.stinterview-question-skill-user.stinterview-question-resume-system.stinterview-question-resume-user.stjd-parse-system.stinterview-evaluation-system.stinterview-evaluation-user.stinterview-evaluation-summary-system.stinterview-evaluation-summary-user.st
src/main/resources/skills/ 下当前包含:
_shared/ai-agent-dev/algorithm/ali-backend/bytedance-backend/frontend/java-backend-tencent/java-backend/novel-expert/python-backend/system-design/tcm-qa/test-development/
这些 skill 资源在当前项目中有两种使用方式:
- 作为 Spring AI
SkillsTool的技能根目录。 - 作为
simulation/voice评估阶段的后端静态知识源,由业务代码直接加载SKILL.md、skill.meta.yml与 references。
AgentUtilsConfiguration 会把 app.ai.agent-utils.skills-root 对应的目录注册为 interviewSkillsToolCallback。
- 配置类:
src/main/java/com/ruici/ai/common/ai/AgentUtilsConfiguration.java - 配置属性:
src/main/java/com/ruici/ai/common/ai/AgentUtilsProperties.java - 默认值:
classpath:skills - yml 来源:
src/main/resources/application.yml
行为特征:
- 若
skillsRoot不存在,会直接抛异常,说明它不是可选注释性配置,而是实际启用逻辑的一部分。 - 这一步只能证明 SkillsTool 已注册,不能证明后续每次业务调用都会实际触发 tool call。
- 即使走默认 client,也还要满足
interviewSkillsToolCallbackbean 实际存在,才会挂载defaultToolCallbacks(...)。
判定:仅注册。
LlmProviderRegistry 是整个项目理解 prompt / skill 使用情况的关键。
- 方法:
getChatClient(...)、getDefaultChatClient()、getChatClientOrDefault(...) - 行为:会尝试挂载
defaultToolCallbacks(interviewSkillsToolCallback),并在配置允许时挂载ToolCallAdvisor、memory、logger。
判定:具备 skill tool 能力。
- 方法:
getPlainChatClient(...) - 行为:显式构造不带 tools / advisors 的
ChatClient。
判定:不会触发 SkillsTool。
- 方法:
getChatClient(session.toLlmRuntimeSnapshot())— 通过会话级快照获取 voice client - 行为:显式构造 plain/no tools 的
ChatClient,目的是避免实时语音链路因为 tool calling 导致首包时延增加或流式链路不稳定。
判定:不会触发 SkillsTool。
补充说明:
voice模块当前已经接入会话级 LLM / ASR / TTS 快照。- 会话创建时通过
VoiceInterviewSessionEntity同时固化三类快照(applyLlmRuntimeSnapshot/applyAsrRuntimeSnapshot/applyTtsRuntimeSnapshot),WebSocket 实时链路通过toLlmRuntimeSnapshot()消费。 - 实时对话使用
voice client,因此不会触发 SkillsTool。 - 会后评估复用同一份会话快照,但调用的是
default client,因此具备 SkillsTool / advisors 能力。
以下配置会直接影响 prompt / skill 的实际行为:
app.ai.agent-utils.skills-rootapp.ai.default-providerapp.ai.providers.*app.ai.advisors.enabledapp.ai.advisors.tool-call-enabledapp.ai.advisors.tool-call-conversation-history-enabledapp.ai.advisors.stream-tool-call-responsesapp.ai.rag.rewrite.enabledapp.ai.structured-*
相关类:
src/main/java/com/ruici/ai/common/config/LlmProviderProperties.javasrc/main/java/com/ruici/ai/common/ai/StructuredOutputProperties.javasrc/main/java/com/ruici/ai/common/ai/AgentUtilsProperties.javasrc/main/java/com/ruici/ai/common/config/runtime/resolver/DefaultAiRuntimeConfigResolver.javasrc/main/java/com/ruici/ai/common/config/runtime/snapshot/AiRuntimeConfigSnapshot.javasrc/main/java/com/ruici/ai/common/config/runtime/service/AiRuntimeCacheInvalidationNotifier.java
resume-analysis-system.stresume-analysis-user.st
ResumeAnalysisProperties 负责绑定默认 prompt 路径:
- 配置类:
src/main/java/com/ruici/ai/modules/document/service/ResumeAnalysisProperties.java - 默认值:
classpath:prompts/resume-analysis-system.stclasspath:prompts/resume-analysis-user.st
ResumeGradingService 构造时将这两个模板读入为 PromptTemplate。
文档分析的核心链路为:
- 文档上传进入
ResumeUploadService。 - 任务进入 Redis Stream。
AnalyzeStreamConsumer消费分析任务。ResumeGradingService.analyzeResume(...)组装 prompt。structuredOutputInvoker.invoke(...)发起模型调用并解析结构化返回。
关键文件:
src/main/java/com/ruici/ai/modules/document/listener/AnalyzeStreamConsumer.javasrc/main/java/com/ruici/ai/modules/document/service/ResumeGradingService.java
ResumeGradingService 当前会先解析 DOCUMENT 场景的 AiRuntimeConfigSnapshot,再调用
llmProviderRegistry.getChatClient(snapshot)。
这意味着:
- Prompt 调用:确定发生。
- SkillsTool 能力:已挂载到
defaultclient。 - 实际 tool call:不能仅凭接线断言一定发生,取决于模型是否发起工具调用。
- Prompt:确定触发
- SkillsTool:仅注册 / 可能参与,但不能保证每次触发
simulation 是当前项目中 prompt / skill 关系最复杂的模块,需拆成“题目生成”、“JD 解析”、“评估”、“方向建模”四层理解。
interview-question-skill-system.stinterview-question-skill-user.stinterview-question-resume-system.stinterview-question-resume-user.stjd-parse-system.stinterview-evaluation-system.stinterview-evaluation-user.stinterview-evaluation-summary-system.stinterview-evaluation-summary-user.st
InterviewQuestionProperties 绑定四个出题模板路径:
- 配置类:
src/main/java/com/ruici/ai/modules/simulation/service/InterviewQuestionProperties.java - 前缀:
app.interview
主入口:
InterviewControllerInterviewSessionService.createSession(...)InterviewQuestionService.generateQuestionsBySkill(...)
在 generateQuestionsBySkill(...) 中会根据是否带文档上下文分成两支:
- 方向题:
generateDirectionOnly(...) - 基于文档题:
generateResumeQuestions(...)
特点:
- 使用
interview-question-skill-system.st与interview-question-skill-user.st - 通过
structuredOutputInvoker.invoke(...)调用模型 - 引入
skillService.buildQuestionReferenceSection(...)注入分类参考内容 - 使用由
InterviewSessionService传入的默认 client - 当前 prompt 语义中明确包含
Skill Tool/ skill 使用指令,因此该链路在业务语义上应视为 tool-capable prompt,不应随意切换到plain client
这条链路说明:
- prompt 会确定触发。
- skill 资源也确定被业务代码读取并注入 prompt。
- 是否发生 Spring AI 的 tool call,不可保证。
补充维护结论(2026-05):
- 若运行时模型不支持 function / tool calling(例如某些
qwen-math-*模型),会直接报Function call not supported.。 - 此时正确排障顺序应为:先检查模型能力与 runtime override,再决定是否调整 client 类型。
- 如果 prompt 仍要求
Skill Tool/ skill 调用,而代码却改成plain client,会造成“提示词语义要求可调用工具,但运行时显式禁用工具”的不一致。 - 因此,方向题链路默认应保持
InterviewSessionService -> getChatClient(runtimeSnapshot);是否真正发生 tool call 由模型能力、advisor 配置与模型决策共同决定。
判定:
- Prompt:确定触发
- 后端 skill 资源注入:确定触发
- SkillsTool:仅注册 / 可能参与
特点:
- 使用
interview-question-resume-system.st与interview-question-resume-user.st - 通过
structuredOutputInvoker.invoke(...)调用模型 - 显式使用
llmProviderRegistry.getPlainChatClient(null)
这意味着:
- prompt 会确定触发。
- 这一分支显式绕过 skills tool / advisors。
判定:
- Prompt:确定触发
- SkillsTool:不会触发
InterviewSkillService.parseJd(...) 会:
- 加载
jd-parse-system.st - 使用
structuredOutputInvoker.invoke(...) - 通过默认
ChatClient调用模型
同时,它会把当前已知 reference 文件列表拼入 prompt,指导模型把 JD 分类映射到现有技能参考。
判定:
- Prompt:确定触发
- SkillsTool:仅注册 / 可能参与
- skill metadata / references:确定参与
评估 prompt 不在 modules/simulation 目录下,而是在 shared 层的 UnifiedEvaluationService 中统一加载:
interview-evaluation-system.stinterview-evaluation-user.stinterview-evaluation-summary-system.stinterview-evaluation-summary-user.st
调用链分两种:
InterviewSessionService.generateReport(...)AnswerEvaluationService.evaluateInterview(...)UnifiedEvaluationService.evaluate(...)
submitAnswer(...)最后一题完成后入 Redis StreamEvaluateStreamConsumerAnswerEvaluationServiceUnifiedEvaluationService.evaluate(...)
评估阶段还会注入 skillService.buildEvaluationReferenceSectionSafe(skillId) 生成的参考基线。
判定:
- Prompt:确定触发(异步链路属于业务完成后确定发生)
- skill reference 注入:确定触发
- SkillsTool:仅注册 / 可能参与
README 中描述的三个方向并不是纯口号,代码里已经有显式建模:
- 求职面试
- 专业答疑
- 职业沟通表达
落地方式主要体现在:
SimulationDirectionSimulationScenarioTypeInterviewQuestionService.resolveDifficulty(...)scenarioType对objective、questionStyle、aiRoleLabel的注入
这说明三方向已经对出题风格和场景目标产生实际影响。
但当前实现仍需注意:
- 主要还是
prompts + skill metadata + reference 注入模式。 - 我没有发现
simulation主链路直接调用knowledgebase模块做 RAG 检索增强。
结论:
- 三个方向:已落地为真实业务分支
- “knowledgebase + prompts + skills 三者协同驱动 simulation 主流程”:当前未完全落地
knowledgebase-query-system.stknowledgebase-query-user.stknowledgebase-query-rewrite.st
KnowledgeBaseQueryProperties 负责绑定:
systemPromptPathuserPromptPathrewritePromptPathrewrite.enabled
默认前缀:app.ai.rag
KnowledgeBaseQueryService.answerQuestion(...)- 向量检索
retrieveRelevantDocs(...) - 命中后构造
systemPrompt + userPrompt - 走
chatClient.prompt().system(...).user(...).call()或 gateway 分支
KnowledgeBaseQueryService.answerQuestionStream(...)- query rewrite + 检索
- 走
.stream().content()或 gateway 分支
rewriteQuestion(...)- 使用
knowledgebase-query-rewrite.st - 仅在
rewriteEnabled=true且问题非空时触发
RAG 主要依赖的是:
- prompt 模板
- 向量检索结果
- provider / gateway 分支
它使用默认 ChatClient,因此 具备 SkillsTool 能力。但从当前实现看,它的主设计目标并不是“靠技能工具回答”,而是“靠检索上下文回答”。
另外需要注意 gateway 分支:
- 当
OpenAiCompatibleGatewayClient.supports(providerId)为真时,会直接走 gateway client。 - 这条分支绕开了
ChatClient.prompt(),因此不会经过ChatClient上挂载的 tool callback / advisors。
- 主问答 prompt:确定触发
- rewrite prompt:条件触发
- SkillsTool:仅注册 / 可能参与
- gateway 分支下的 SkillsTool:不会参与
voice 必须拆成“实时语音对话”和“会后评估”两条链理解,因为两条链使用的 ChatClient 类型不同。
主链路:
VoiceInterviewWebSocketHandlerDashscopeLlmService.chat(...)/chatStreamSentences(...)llmProviderRegistry.getChatClient(session.toLlmRuntimeSnapshot())→ voice client
这里的关键点是:
- 实时对话已接入会话级 LLM 快照:会话创建时固化
LLM / ASR / TTS三类快照到VoiceInterviewSessionEntity,WebSocket 链路通过session.toLlmRuntimeSnapshot()获取 client。 DashscopeLlmService优先使用会话快照创建 client,provider 配置作为兜底。VoiceInterviewPromptService.generateSystemPromptWithContext(...)使用SKILL_PERSONA_INSTRUCTION模板按 skillId 注入方向角色设定,并追加VOICE_RESPONSE_CONSTRAINTS(6 条语音输出约束)。- 提示词显式禁止提及工具调用、技能加载或系统内部实现,与
no-tools client设计对齐。
另外,开场白不是 prompt 模板生成,而是来自:
src/main/resources/voice-interview-opening.yml
因此实时语音主链路的结论是:
- 使用了系统 prompt 组装逻辑,但当前实时对话主链路不使用
resources/prompts/*.st。 - Skill 语义通过
SKILL_PERSONA_INSTRUCTION注入角色方向,但显式禁止模型提及工具/技能加载。
判定:
prompts/*.st:未接入- SkillsTool:不会触发(client 无 tools + prompt 显式禁止)
voice-interview-opening.yml:确定使用
主链路:
VoiceInterviewService.endSession(...)或手动触发评估VoiceEvaluateStreamProducer入队VoiceEvaluateStreamConsumerVoiceInterviewEvaluationService.generateEvaluation(...)UnifiedEvaluationService.evaluate(...)
这一分支会:
- 使用会话级快照获取
defaultclient(getChatClient(session.toLlmRuntimeSnapshot())),与实时对话共用同一份会话快照但 clientType 不同 - 注入
skillService.buildEvaluationReferenceSectionSafe(session.getSkillId()) - 复用 shared 的
interview-evaluation-*与interview-evaluation-summary-*prompt
判定:
- Prompt:确定触发
- skill reference 注入:确定触发
- SkillsTool:仅注册 / 可能参与
虽然用户当前重点不是 schedule,但从“尽量找全”的角度,仍建议记录它,因为它也包含 AI 调用。
schedule 当前没有使用 src/main/resources/prompts/*.st 中的模板文件。
InterviewParseService 使用的是类内内联常量 PARSE_PROMPT,不是 resources 下的模板文件。
InterviewScheduleControllerInterviewParseService.parse(...)- 先规则解析
- 规则失败后才进入
parseWithAI(...) - 使用
llmProviderRegistry.getChatClientOrDefault(provider)+.call()
由于使用的是默认 ChatClient,理论上具备 tool callback 能力;但当前业务意图是结构化解析文本,不是使用 skill tool 完成复杂工具编排。
判定:
prompts/*.st:未接入- 内联 Prompt:确定触发
- SkillsTool:仅注册 / 可能参与
-
ResumeAnalysisProperties- 前缀:
app.resume.analysis - 路径:
resume-analysis-system.st/resume-analysis-user.st
- 前缀:
-
InterviewQuestionProperties- 前缀:
app.interview - 路径:
interview-question-skill-*/interview-question-resume-*
- 前缀:
-
InterviewEvaluationProperties- 前缀:
app.interview.evaluation - 路径:
interview-evaluation-*/interview-evaluation-summary-*
- 前缀:
-
KnowledgeBaseQueryProperties- 前缀:
app.ai.rag - 路径:
knowledgebase-query-system.st/knowledgebase-query-user.st/knowledgebase-query-rewrite.st
- 前缀:
-
AgentUtilsProperties- 前缀:
app.ai.agent-utils - 默认
skillsRoot=classpath:skills
- 前缀:
-
LlmProviderProperties- 前缀:
app.ai - 管理 provider、advisors、tool-call 相关开关
- 前缀:
-
StructuredOutputProperties- 前缀:
app.ai - 管理结构化输出重试、错误注入、严格 JSON 指令与 metrics
- 前缀:
document的文档分析 prompt 已真实接入并使用。simulation的题目生成、JD 解析、评估 prompt 已真实接入并使用。simulation的 skill 资源并非摆设,后端会主动加载SKILL.md、skill.meta.yml与 references。knowledgebase的 RAG 主问答、流式问答与 query rewrite prompt 已真实接入。voice的会后评估复用了 simulation evaluation prompt;实时对话则走 voice 专用 plain client。
- 不能因为
SkillsTool已注册,就认定所有默认 client 链路都会实际发生 tool call。 - 不能把
simulation里的“skill”简单理解成“全靠 Spring AI 工具调用”;当前更真实的实现是“后端主动加载 skill 资源并把内容注入 prompt”。 - 不能把
voice实时链路当成 skill-enabled agent 流程;它明确绕开了 tools。 - 不能把
knowledgebase的 gateway 分支误判为也会经过ChatClient上的 tool callbacks。 - 不能把“默认 client 具备增强能力”外推成“所有 RAG 请求都会实际经过增强 client”;gateway 分支是明确例外。
simulation虽然已经有三个方向和多个 skill pack,但我没有发现主链路直接接入knowledgebase模块做 RAG 增强。- README 中“通过知识库、提示词模板和 Skill 组合出不同垂类能力”的目标,在
simulation主流程上目前更接近“部分落地”。
- 优先新增
@ConfigurationProperties绑定路径,不要把路径硬编码在多个 service 中。 - 明确记录它走的是默认 client、plain client 还是 voice client。
- 若是结构化输出,优先复用
StructuredOutputInvoker。
- 如果目的是给
simulation/voice评估提供参考内容,需要同时补齐:skills/<skillId>/SKILL.mdskills/<skillId>/skill.meta.yml- 对应 reference 文件
- 如果目的是让模型在默认 client 下可进行 tool calling,则还要确认:
- 该链路没有走
plain/voiceclient app.ai.advisors.tool-call-enabled=true- provider 与 gateway 分支没有绕过
ChatClient
- 该链路没有走
当出现“为什么这个 skill 没生效”时,请按下面顺序排查:
- 业务链路是否用了默认
ChatClient。 - 是否走了 gateway / plain / voice 分支。
skillsRoot是否正确,skill 目录是否存在。- 如果是 simulation / voice 评估参考内容问题,检查
skill.meta.yml与 reference 文件映射是否完整。 - 如果是 RAG rewrite 问题,检查
app.ai.rag.rewrite.enabled。
当出现“prompt 明明要求 tool / skill,为什么运行时报 Function call not supported.”时,请额外按下面顺序排查:
- 当前命中的模型是否支持 function / tool calling。
- 当前 runtime snapshot 是否被
REQUEST_OVERRIDE/ DB runtime config 切到了不支持 tool 的模型。 - 该链路是否本应保留 tool 语义;若是,不要用
plain client规避报错。 - 只有在业务语义明确不需要 tool 时,才同步删除 prompt 中的
Skill Tool指令并改用plain client。
src/main/java/com/ruici/ai/common/ai/AgentUtilsConfiguration.javasrc/main/java/com/ruici/ai/common/ai/AgentUtilsProperties.javasrc/main/java/com/ruici/ai/common/ai/LlmProviderRegistry.javasrc/main/java/com/ruici/ai/common/ai/StructuredOutputInvoker.javasrc/main/java/com/ruici/ai/common/ai/StructuredOutputProperties.javasrc/main/java/com/ruici/ai/common/config/LlmProviderProperties.javasrc/main/java/com/ruici/ai/common/config/runtime/resolver/DefaultAiRuntimeConfigResolver.javasrc/main/java/com/ruici/ai/common/config/runtime/snapshot/AiRuntimeConfigSnapshot.javasrc/main/java/com/ruici/ai/common/config/runtime/service/AiRuntimeCacheInvalidationNotifier.javasrc/main/resources/application.yml
src/main/java/com/ruici/ai/modules/document/service/ResumeAnalysisProperties.javasrc/main/java/com/ruici/ai/modules/document/service/ResumeGradingService.javasrc/main/java/com/ruici/ai/modules/document/listener/AnalyzeStreamConsumer.java
src/main/java/com/ruici/ai/modules/simulation/service/InterviewQuestionProperties.javasrc/main/java/com/ruici/ai/modules/simulation/service/InterviewQuestionService.javasrc/main/java/com/ruici/ai/modules/simulation/service/InterviewSessionService.javasrc/main/java/com/ruici/ai/modules/simulation/skill/InterviewSkillService.javasrc/main/java/com/ruici/ai/common/evaluation/InterviewEvaluationProperties.javasrc/main/java/com/ruici/ai/common/evaluation/UnifiedEvaluationService.java
src/main/java/com/ruici/ai/modules/knowledgebase/service/KnowledgeBaseQueryProperties.javasrc/main/java/com/ruici/ai/modules/knowledgebase/service/KnowledgeBaseQueryService.javasrc/main/java/com/ruici/ai/common/ai/OpenAiCompatibleGatewayClient.java
src/main/java/com/ruici/ai/modules/voice/handler/VoiceInterviewWebSocketHandler.javasrc/main/java/com/ruici/ai/modules/voice/service/DashscopeLlmService.javasrc/main/java/com/ruici/ai/modules/voice/service/VoiceInterviewPromptService.javasrc/main/java/com/ruici/ai/modules/voice/service/VoiceInterviewEvaluationService.javasrc/main/java/com/ruici/ai/modules/voice/service/VoiceInterviewService.javasrc/main/java/com/ruici/ai/modules/voice/model/VoiceInterviewSessionEntity.javasrc/main/java/com/ruici/ai/modules/voice/listener/VoiceEvaluateStreamConsumer.javasrc/main/resources/voice-interview-opening.yml
src/main/java/com/ruici/ai/modules/schedule/service/InterviewParseService.java