ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Resume-Matcher 自定义求职信与外联提示词:从 fork 源码到 Settings 可视化配置的设计与实践

2026/9/11 1:57:54 拓冰建站 浏览量
Resume-Matcher 自定义求职信与外联提示词:从 fork 源码到 Settings 可视化配置的设计与实践 Resume-Matcher 自定义求职信与外联提示词从 fork 源码到 Settings 可视化配置的设计与实践【免费下载链接】Resume-MatcherThe #1 AI Harness for Building Resumes, PDFs, Cover Letters more, locally with 100 LLMs support.项目地址: https://gitcode.com/GitHub_Trending/re/Resume-Matcher本文基于 Resume-Matcher 仓库中的设计文档 2026-04-17-custom-feature-prompts-design.md 展开系统讲解该功能Issue #749从问题定位、方案设计、后端实现到前端落地的完整链路。读完本文你将掌握 Resume-Matcher 中求职信cover letter与冷外联cold outreach提示词的自定义机制如何新增两个可选配置字段、如何在保存时校验三个必需占位符、如何在运行时做防御性回退以及如何在 Settings 页面提供可视化编辑与一键恢复默认。文中的每一处实现结论都可以在对应源码与集成测试中直接验证。一、问题背景为什么改一行常量不是解决方案在设计文档中问题的起点非常具体COVER_LETTER_PROMPT和OUTREACH_MESSAGE_PROMPT是 templates.py 中的模块级常量分别定义了求职信与外联消息的默认生成指令。用户希望按自己的需求定制长度、语气和内容——例如150 words、include company research、bold specific keywords。但在引入自定义功能之前唯一的途径是fork 整个仓库修改常量这既无法跟随上游更新也无法在单实例部署中为不同用户提供差异配置。有趣的是简历优化resume-generation提示词已经支持定制用户可以在 Settings 的 Prompt Profile 区块从三个内置变体nudge/keywords/full中选择对应 IMPROVE_RESUME_PROMPTS。但这个固定菜单模式并不适合求职信与外联场景——用户需要的是自由文本覆盖free-form text override而不是在预设变体里选一个。这正是本设计文档与既有Prompt Profile模式的关键差异点。二、设计目标与非目标设计文档明确划定了功能边界目标用户可以对求职信和冷外联分别覆盖默认提示词文本per-feature空值 / 缺失值 使用默认提示词行为与之前完全一致向后兼容自定义提示词必须注入与默认值相同的占位符{job_description}、{resume_data}、{output_language}缺失占位符在保存时直接返回 422UI 将默认提示词作为 textarea 的 placeholder 展示并提供 Reset to default 一键还原按钮简历优化提示词已有变体机制不在此次范围内被明确延迟。非目标不支持按简历粒度的覆盖自定义提示词是全局的不覆盖 enrichment、refinement、JD 匹配、简历标题生成的提示词不做提示词版本管理或历史记录不引入占位符替换之外的任何模板机制。这个边界划分保证了改动是纯增量pure additive下文第 11 节会看到这正是回滚策略的前提。三、配置存储设计config.json 中的两个可选字段设计文档规定在config.json顶层新增两个可选字符串字段{ cover_letter_prompt: , outreach_message_prompt: }语义约定空字符串 使用默认值键不存在 与空字符串等价。一个容易被忽略但非常关键的设计决策是Settings类pydantic-settings 环境配置刻意不被扩展。设计文档的理由是——这些覆盖项属于每部署的用户数据而不是环境配置因此 config.py 保持原样运行期通过已有的load_config_file()在调用点加载。这一取舍避免了把用户运行时数据混入环境变量配置体系也让回滚删除两个键即可变得零成本。从当前源码看config.py 中load_config_file()/save_config_file()两个函数已经存在前者读取 config.json 并注入解密后的 API keys后者在写盘前剥离 secrets。设计文档中把llm.py的私有_load_stored_config提升为公共函数的计划最终落地为直接复用 config.py 中已有的load_config_file服务层与路由层统一从app.config导入避免了重复实现。四、占位符校验把运行时 KeyError 提前为保存时的 422自定义提示词最终会被服务层用str.format()渲染而format()对缺失占位符会抛KeyError。设计文档的思路是在保存时就校验让用户立刻看到清晰的 422 错误而不是等到生成时 500。校验逻辑实现在 prompts/init.pyREQUIRED_FEATURE_PROMPT_PLACEHOLDERS: tuple[str, ...] ( {job_description}, {resume_data}, {output_language}, ) def validate_prompt_placeholders(prompt: str) - list[str]: Return required placeholders missing from prompt. Empty or whitespace-only prompts are treated as use default and return an empty list (valid — the router treats them as clearing the override). Non-empty prompts must include every entry from REQUIRED_FEATURE_PROMPT_PLACEHOLDERS. if not prompt or not prompt.strip(): return [] return [p for p in REQUIRED_FEATURE_PROMPT_PLACEHOLDERS if p not in prompt]要点空字符串返回[]合法——因为空字符串是使用默认的哨兵值路由层只对非空字符串执行校验返回值是缺失占位符列表——空列表即合法非空列表会被路由层转成 422并附带missing字段列出具体缺失项三个占位符与 cover_letter.py 中format()的调用参数一一对应job_descriptionjob_description、resume_datajson.dumps(resume_data)、output_languageoutput_language。从注释可见{resume_data}会被注入为简历 JSON 的字符串形式{output_language}则是经get_language_name()转换后的完整语言名如 English、Chinese (Simplified)映射定义在 templates.py。五、后端服务集成运行时解析与防御性回退服务层是自定义提示词的消费者。设计文档给出的核心思路是一个共享解析 helper当前实现为 cover_letter.py 中的_resolve_feature_promptdef _resolve_feature_prompt( custom_key: str, default_template: str, ) - tuple[str, bool]: Resolve a feature-prompt template at runtime. Returns (template, is_custom). If the stored custom prompt is empty or absent, returns the default template. The is_custom flag lets callers decide whether to fall back to the default on a format failure (defensive — save-time validation should have caught a malformed custom prompt). stored load_config_file() custom (stored.get(custom_key) or ).strip() if not custom: return default_template, False return custom, Truegenerate_cover_letter与generate_outreach_message两个函数都采用同样的模式template, is_custom _resolve_feature_prompt( cover_letter_prompt, COVER_LETTER_PROMPT ) try: prompt template.format( job_descriptionjob_description, resume_datajson.dumps(resume_data), output_languageoutput_language, ) except (KeyError, IndexError, ValueError) as e: if not is_custom: raise logging.warning( Custom cover letter prompt failed to format (%s); falling back to default, e, ) prompt COVER_LETTER_PROMPT.format(...)这里有两层防护值得注意is_custom标志位只有当当前模板确实是用户自定义时才回退如果失败的是内置默认模板说明上游逻辑有 bug直接 re-raise 让调用方暴露问题异常集合覆盖了format()的所有失败模式KeyError未知占位符、IndexError越界的位置参数、ValueError未闭合的花括号如{foo——这一扩展比设计文档最初版本更完备。generate_outreach_message读取outreach_message_prompt键、默认模板为OUTREACH_MESSAGE_PROMPT结构完全一致。两个函数的 LLM 调用参数也值得对比求职信使用system_promptYou are a professional career coach and resume writer...且max_tokens2048外联消息则使用 professional networking coach 且max_tokens1024cover_letter.py。六、API 端点设计GET/PUT /api/v1/config/feature-prompts路由层在 routers/config.py 中新增了两个端点与已有的/config/prompts简历优化变体选择平级但按功能域隔离GET /api/v1/config/feature-prompts → { cover_letter_prompt, outreach_message_prompt, cover_letter_default, outreach_message_default } PUT /api/v1/config/feature-prompts body: { cover_letter_prompt?, outreach_message_prompt? } → same schema as GETGET 端的_default字段设计是文档反复强调的一个巧妙点cover_letter_default/outreach_message_default直接返回内置默认提示词全文来自 templates.py 的COVER_LETTER_PROMPT/OUTREACH_MESSAGE_PROMPT前端拿到后作为 textarea 的 placeholder 展示无需在多语言环境中重复维护这份长文本。PUT 端的校验逻辑以 cover letter 为例if request.cover_letter_prompt is not None: prompt request.cover_letter_prompt.strip() if prompt: missing validate_prompt_placeholders(prompt) if missing: raise HTTPException( status_code422, detail{ code: missing_placeholders, field: cover_letter_prompt, missing: missing, }, ) stored[cover_letter_prompt] promptoutreach 分支结构相同。所有变更通过_save_config(stored)落盘该 helper 内部调用save_config_file()并触发invalidate_config_cache()使共享配置缓存失效config.py 路由确保后续请求读到新值。七、Schema 模型与请求语义None 与 的严格区分两个 Pydantic 模型定义在 schemas/models.pyclass FeaturePromptsRequest(BaseModel): Request to update custom feature prompts. None means dont change this field. An empty string clears the override — the server persists so runtime resolution falls back to the built-in default without the key disappearing from config.json. cover_letter_prompt: str | None None outreach_message_prompt: str | None None class FeaturePromptsResponse(BaseModel): Response for custom feature prompts. The *_default fields expose the built-in prompt strings so the UI can render them as placeholder text without duplicating the content across locales. cover_letter_prompt: str outreach_message_prompt: str cover_letter_default: str outreach_message_default: str请求模型的str | None None语义是整条链路正确性的基石字段缺省 /null→ 本次不改动该字段对应if request.cover_letter_prompt is not None的守卫空字符串→ 清除覆盖服务端持久化后运行期自动回退默认响应模型不含可空字段保证前端拿到的是规范化的字符串。八、前端API 客户端、Settings UI 与 i18n 陷阱8.1 API 客户端lib/api/config.ts 中定义了完整类型与请求函数export interface FeaturePrompts { cover_letter_prompt: string; outreach_message_prompt: string; cover_letter_default: string; outreach_message_default: string; } export interface FeaturePromptsUpdate { cover_letter_prompt?: string; outreach_message_prompt?: string; } export interface FeaturePromptsValidationError { code: missing_placeholders; field: cover_letter_prompt | outreach_message_prompt; missing: string[]; } export class FeaturePromptsError extends Error { detail: FeaturePromptsValidationError; ... }updateFeaturePrompts对 422 做了专门处理解析响应体中的结构化detail当code missing_placeholders时抛出携带detail的FeaturePromptsError让 Settings 页面能把缺失的占位符逐项展示给用户。同时它对非 422 错误做了健壮处理——FastAPI 的detail可能是字符串也可能是对象代码显式区分序列化避免出现[object Object]。8.2 Settings 界面在 Content Generation 区块下每个功能求职信 / 外联的 prompt textarea只在对应功能 toggle 开启时渲染与既有 UX 一致。每个功能包含labelCustom prompt (optional)rows{8}的等宽字体 textareaplaceholder 显示默认提示词全文帮助文案必须包含三个占位符留空使用默认Reset to default 按钮——同时清空 textarea 并以空字符串调用 PUT保存失败时的行内 422 错误提示列出缺失的占位符。8.3 i18n 多语言与 next-intl 的 ICU 陷阱新增文案需要落到全部 5 个语言文件en / es / ja / zh / pt-BR。设计文档在此处记录了一个非常实战的坑next-intl 使用{name}作为变量占位符语法而帮助文案中的{job_description}、{resume_data}、{output_language}恰好与 ICU 语法重叠直接写入会被 next-intl 当作变量引用导致渲染失败。给出的三种解法ICU 转义{...}/values参数模板化 / 改写文案去掉花括号中文档明确选择了方案 (c)——把帮助文案改写为不带字面花括号的表述例如Must include three placeholders: job_description, resume_data, output_language (each in curly braces). Leave blank to use default.这个细节对所有基于 next-intl 的多语言项目都有直接借鉴价值。九、数据流全景设计文档给出了端到端数据流与当前实现完全吻合User opens Settings → enables Cover Letter toggle ↓ Settings renders textarea with default prompt as placeholder ↓ User pastes custom prompt, clicks Save ↓ PUT /api/v1/config/feature-prompts { cover_letter_prompt: ... } ↓ Router validates placeholders → 422 on missing OR 200 persist ↓ stored.cover_letter_prompt user text OR (on clear) ↓ Later: user runs Tailor → generates cover letter ↓ generate_cover_letter() reads stored config → uses custom or default ↓ .format() substitutes placeholders → LLM call → returns text十、错误处理矩阵设计文档用表格完整枚举了失败场景与预期行为这是理解系统边界的最佳入口失败场景行为保存空提示词视为清除为默认返回 200 OK提示词缺失必需占位符422codemissing_placeholders列出缺失项无状态变更提示词含额外未知占位符通过保存校验。运行时format()对单花括号按字面量处理无害但对{foo}风格会抛错服务层防御性 try/except 回退默认并记日志存储的提示词被磁盘编辑损坏同样的防御性回退落到默认提示词最后两行对应的是_resolve_feature_prompt try/except 组合对绕过 API 直接改 config.json这类带外变更的兜底能力。十一、验证与测试从设计到集成测试设计文档列出了 6 步手工验证清单空提示词走默认、三段式自定义提示词如 Write in Shakespearean English, 200 words生效、缺失{resume_data}时 422 且 UI 提示、Reset 还原默认、外联消息同流程复验。这些场景已被自动化集成测试覆盖。在 test_config_api.py 的TestFeaturePrompts类中test_get_feature_prompts验证 GET 返回存储值与默认值且三个占位符都出现在两个*_default字段中test_put_feature_prompts_rejects_missing_placeholders提交Use {job_description} only断言 422 且detail精确为{code: missing_placeholders, field: cover_letter_prompt, missing: [{resume_data}, {output_language}]}test_put_feature_prompts_strips_and_clears_values验证首尾空白被 strip含多行提示词保留内部换行纯空白输入被规范化为清除。十二、回滚策略因为整个功能是纯增量的回滚同样干净移除 UI textareas用户只看到功能 toggle恢复原状移除端点后下一次前端调用会 404——因此需要前后端同步回滚config.json 中已存储的cover_letter_prompt/outreach_message_prompt字段成为惰性数据——被回滚后的服务代码忽略不会报错数据不删除重新启用功能即可恢复已保存的自定义提示词。十三、风险与边界设计文档坦诚列出了三个风险_load_stored_config移动破坏既有导入实现中已确认llm.py的私有 helper 被移除并统一改用app.config.load_config_file服务层与路由层从同一来源导入规避了重复实现与漂移超长自定义提示词推高 token 消耗本次不强制 token 上限由 LLM 侧的max_tokens求职信 2048 / 外联 1024与提供商限制兜底format()将{视为特殊字符用户若想在提示词中写字面花括号例如在提示词里演示 JSON schema必须使用{{/}}转义helper 的文档注释已写明。结语Issue #749 的设计文档展示了一个教科书式的小功能大设计用两个可选 JSON 字段承载用户数据、用保存时校验换取干净的运行期、用is_custom标志位实现防御性回退、用_default字段避免多语言环境重复维护默认文案最后用严格的 None/ 语义区分不改动与清除。对于想要深入理解该实现或在此基础上扩展更多自定义提示词功能的开发者建议按以下路径阅读源码设计文档 → 校验 helperprompts/init.py→ 服务解析services/cover_letter.py→ 路由端点routers/config.py→ 前端客户端lib/api/config.ts→ 集成测试test_config_api.py。【免费下载链接】Resume-MatcherThe #1 AI Harness for Building Resumes, PDFs, Cover Letters more, locally with 100 LLMs support.项目地址: https://gitcode.com/GitHub_Trending/re/Resume-Matcher创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考