mcpField
T
Typecast

Typecast

本地服务
github.com/nilayp-codes商业与营销13 天前更新

关于 Typecast

Typecast 面向评估 LLM 提取流程的开发者,可将职位描述转换为经过架构验证的 JSON,并通过错误反馈重试及实验结果衡量有效性和准确度。

主要功能

  1. 01根据模型架构验证 JSON
  2. 02错误反馈重试,最多三次
  3. 03确定性整理技能和地点
  4. 04统计模型有效性与准确度

适用场景

  1. 01从职位描述提取结构化数据
  2. 02比较架构约束与重试策略
  3. 03衡量多轮运行中的提取失败

MCP 相关

项目在 PyPI 提供 `typecast-mcp` MCP 服务;输入未说明其 MCP 接口。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"nilayp-codes-typecast": {
"command": "uvx",
"args": [
"typecast-mcp"
]
}
}
}
启动命令uvx typecast-mcp

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
pypitypecast-mcp0.1.0

工具

2

收录的工具列表 · 更新于 3 天前

此工具无需参数。

详细介绍

Typecast

能将杂乱的职位描述转换为通过架构验证的类型化 JSON,并衡量转换失败的频率的 LLM Agent。

主要发现(通过 OpenRouter 使用两种 Qwen 模型处理 30 条真实职位描述):

  • 架构模式能让输出有效,但不保证正确。 在设置 C 中,两种模型的输出有效率均为 98.9%,成本低于重试,但端到端准确率分别为 67.1%(7B)和 84.7%(235B)。
  • 错误反馈重试修复了小模型的问题,却没有修复大模型的问题。 设置 B 中,7B 模型的有效率为 98.9%,而 235B 模型为 88.9%;后者在四次设置 B 运行中都未能处理 jd_002。加入考虑数量的重试消息也没有改变这一结果。
  • 没有一次运行达到我 90% 的字段准确率目标(最佳:87.3%),而且不同运行的结果会有差异。 同一设置的端到端准确率在不同运行间相差约 8 个百分点,因此,单次运行之间的差距不能作为定论。

60 秒演示:

https://github.com/user-attachments/assets/c4a2572c-88a2-49a3-8e20-176010877154

CLI 和包名称:soa · PyPI 上的 MCP 服务:typecast-mcp

CI

结果

通过 OpenRouter 使用两种开放权重模型,处理 30 条真实职位描述,共进行四种设置。字段准确率根据 20 条人工核验的标准标签评分(标签的制作方式见局限性)。进行 3 次运行时,数值为平均值,括号内为各次运行的最小值至最大值;进行 1 次运行时,数值即为该次运行结果。A = 普通提示词,不重试;B = 普通提示词 + 错误反馈重试;C = 提供商架构模式,不重试;D = 架构模式 + 重试。

模型设置运行次数最终有效率端到端准确率每次处理 30 条文档的成本
qwen-2.5-7b-instructA · 普通183.3%50.4%$0.0081
qwen-2.5-7b-instructB · 普通 + 重试398.9% (96.7%-100.0%)67.1% (62.9%-70.4%)$0.0103 ($0.0101-$0.0105)
qwen-2.5-7b-instructC · 架构398.9% (96.7%-100.0%)67.1% (65.0%-69.8%)$0.0076 ($0.0076-$0.0076)
qwen-2.5-7b-instructD · 架构 + 重试1100.0%70.0%$0.0080
qwen3-235b-a22b-2507A · 普通170.0%60.4%$0.0120
qwen3-235b-a22b-2507B · 普通 + 重试388.9% (86.7%-93.3%)75.6% (72.8%-80.9%)$0.0211 ($0.0199-$0.0234)
qwen3-235b-a22b-2507C · 架构398.9% (96.7%-100.0%)84.7% (82.8%-85.8%)$0.0136 ($0.0134-$0.0140)
qwen3-235b-a22b-2507D · 架构 + 重试1100.0%85.4%$0.0143
完整表格:还包含首次尝试有效率、平均调用次数和字段准确率 | 模型 | 设置 | 运行次数 | 首次尝试有效率 | 最终有效率 | 平均调用次数 | 字段准确率(有效输出) | 端到端准确率 | 每次 30 份文档运行的成本 | |---|---|---|---|---|---|---|---|---| | qwen-2.5-7b-instruct | A:普通提示词,不重试 | 单次运行 | 83.3% | 83.3% | 1.00 | 62.9% | 50.4% | $0.0081 | | qwen-2.5-7b-instruct | B:普通提示词 + 错误反馈重试 | 3 次运行的均值(最小值–最大值) | 82.2%(80.0%-83.3%) | 98.9%(96.7%-100.0%) | 1.20(1.17-1.23) | 68.2%(66.0%-70.4%) | 67.1%(62.9%-70.4%) | $0.0103($0.0101-$0.0105) | | qwen-2.5-7b-instruct | C:模式,不重试 | 3 次运行的均值(最小值–最大值) | 98.9%(96.7%-100.0%) | 98.9%(96.7%-100.0%) | 1.00(1.00-1.00) | 68.2%(66.5%-69.8%) | 67.1%(65.0%-69.8%) | $0.0076($0.0076-$0.0076) | | qwen-2.5-7b-instruct | D:模式 + 重试 | 单次运行 | 96.7% | 100.0% | 1.03 | 70.0% | 70.0% | $0.0080 | | qwen3-235b-a22b-2507 | A:普通提示词,不重试 | 单次运行 | 70.0% | 70.0% | 1.00 | 86.6% | 60.4% | $0.0120 | | qwen3-235b-a22b-2507 | B:普通提示词 + 错误反馈重试 | 3 次运行的均值(最小值–最大值) | 76.7%(73.3%-80.0%) | 88.9%(86.7%-93.3%) | 1.34(1.33-1.37) | 85.6%(85.1%-86.0%) | 75.6%(72.8%-80.9%) | $0.0211($0.0199-$0.0234) | | qwen3-235b-a22b-2507 | C:模式,不重试 | 3 次运行的均值(最小值–最大值) | 98.9%(96.7%-100.0%) | 98.9%(96.7%-100.0%) | 1.00(1.00-1.00) | 86.1%(85.6%-87.0%) | 84.7%(82.8%-85.8%) | $0.0136($0.0134-$0.0140) | | qwen3-235b-a22b-2507 | D:模式 + 重试 | 单次运行 | 100.0% | 100.0% | 1.00 | 85.4% | 85.4% | $0.0143 |

如何解读:

  • “3 次运行的均值(最小值–最大值)”(B 和 C)表示在相同的 30 份文档上运行 3 次的均值,括号内为最低和最高运行结果。这反映的是多次运行间的差异,并非区间估计。B 的运行是下文所述重试反馈修复后进行的 3 次运行。C 的运行包括首次 C 运行以及两次重复运行。“单次运行”(A 和 D)各指一次抽样:重复运行显示,同一设置在不同运行间的端到端准确率会波动约 8 个百分点(235B 设置 B:72.8% 到 80.9%)。每次运行自身的 95% 区间(有效率列采用 Wilson 区间,准确率列采用 bootstrap)见本节末尾的逐次运行表;A 和 D 的单次运行没有区间,因为进行这些运行时,区间计算代码尚不存在。
  • 首次尝试有效率 / 最终有效率:30 份文档中,首次回复通过验证的比例,或在最多 3 次尝试内有任意回复通过验证的比例。平均调用次数:每份文档的模型调用次数。
  • **字段准确率(有效输出)**仅在产生有效 posting 的文档上测量,因此会让经常失败的设置看起来表现更好(235B 设置 A:字段准确率为 86.6%,但有有效输出可评分的标注文档仅占 70.0%)。端到端准确率会将失败的文档的每个字段都计为错误;比较有效率不同的设置时,应以此为准。
  • 成本是提供商报告的 30 份文档单次运行成本。未设置 temperature(使用提供商默认值,具体值未知)。
我的目标及达成情况

运行前,我为设置 B 设定了三个目标:最终有效率达到 30/30、平均每份文档最多调用 1.3 次,以及字段准确率至少达到 90%。各次运行的结果如下(重试修复后的 3 次运行):

目标(设置 B)qwen-2.5-7b-instructqwen3-235b-a22b-2507
最终有效率 30/303 次运行中有 2 次达成(100.0%、100.0%、96.7%)3 次全部未达成(86.7%、93.3%、86.7%)
平均调用次数 <= 1.3达成(1.20、1.17、1.23)未达成(1.37、1.33、1.33)
字段准确率 >= 90%未达成(70.4%、68.1%、66.0%)未达成(85.7%、85.1%、86.0%)

两种模型在所有设置下的每次运行都未达到 90% 的字段准确率目标。所有结果中最高的是 87.3%(235B,设置 B,首轮运行)。

每次运行及其 95% 区间(results/summary.md,原文) 第 1–8 行:第一轮,每个模型和配置运行一次(7B 的 A、B、C、D,然后是 235B)。这些数据是在区间代码编写完成前记录的,因此没有区间。第 9–18 行:重复轮次,依次为 7B B ×3、7B C ×2、235B B ×3、235B C ×2。区间以方括号标出。列说明:`field acc` 为有效输出中的准确率,`coverage` 为 20 份已标注文档中有有效输出的占比,`e2e acc` 将失败计为错误。
模型配置n首次尝试最终平均调用次数字段准确率(规范化)覆盖率端到端准确率(规范化)成本
openrouter/qwen/qwen-2.5-7b-instructA3083.3%83.3%1.0062.9%80.0%50.4%$0.0081
openrouter/qwen/qwen-2.5-7b-instructB3086.7%96.7%1.1768.3%95.0%64.8%$0.0102
openrouter/qwen/qwen-2.5-7b-instructC30100.0%100.0%1.0069.8%100.0%69.8%$0.0076
openrouter/qwen/qwen-2.5-7b-instructD3096.7%100.0%1.0370.0%100.0%70.0%$0.0080
openrouter/qwen/qwen3-235b-a22b-2507A3070.0%70.0%1.0086.6%70.0%60.4%$0.0120
openrouter/qwen/qwen3-235b-a22b-2507B3083.3%86.7%1.3387.3%85.0%74.3%$0.0186
openrouter/qwen/qwen3-235b-a22b-2507C30100.0%100.0%1.0085.8%100.0%85.8%$0.0140
openrouter/qwen/qwen3-235b-a22b-2507D30100.0%100.0%1.0085.4%100.0%85.4%$0.0143
openrouter/qwen/qwen-2.5-7b-instructB3080.0% [62.7-90.5]100.0% [88.6-100.0]1.2070.4% [64.4-76.9]100.0%70.4% [64.4-76.9]$0.0104
openrouter/qwen/qwen-2.5-7b-instructB3083.3% [66.4-92.7]100.0% [88.6-100.0]1.1768.1% [61.4-74.7]100.0%68.1% [61.4-74.7]$0.0101
openrouter/qwen/qwen-2.5-7b-instructB3083.3% [66.4-92.7]96.7% [83.3-99.4]1.2366.0% [59.6-72.2]95.0%62.9% [54.5-70.5]$0.0105
openrouter/qwen/qwen-2.5-7b-instructC30100.0% [88.6-100.0]100.0% [88.6-100.0]1.0066.5% [59.6-73.8]100.0%66.5% [59.6-73.8]$0.0076
openrouter/qwen/qwen-2.5-7b-instructC3096.7% [83.3-99.4]96.7% [83.3-99.4]1.0068.4% [61.3-75.0]95.0%65.0% [55.2-73.3]$0.0076
openrouter/qwen/qwen3-235b-a22b-2507B3076.7% [59.1-88.2]86.7% [70.3-94.7]1.3785.7% [81.3-89.6]85.0%72.8% [58.4-84.8]$0.0234
openrouter/qwen/qwen3-235b-a22b-2507B3073.3% [55.6-85.8]93.3% [78.7-98.2]1.3385.1% [81.1-88.9]95.0%80.9% [71.1-87.9]$0.0199
openrouter/qwen/qwen3-235b-a22b-2507B3080.0% [62.7-90.5]86.7% [70.3-94.7]1.3386.0% [82.0-89.6]85.0%73.1% [58.7-84.9]$0.0202
openrouter/qwen/qwen3-235b-a22b-2507C3096.7% [83.3-99.4]96.7% [83.3-99.4]1.0087.0% [83.7-89.9]95.0%82.8% [72.3-89.0]$0.0136
openrouter/qwen/qwen3-235b-a22b-2507C30100.0% [88.6-100.0]100.0% [88.6-100.0]1.0085.6% [82.0-88.8]100.0%85.6% [82.0-88.8]$0.0134

主表中的 A 和 D 数据对应上方第 1–8 行(A 和 D)。第一轮的 B 行(7B:86.7% / 96.7%,235B:83.3% / 86.7%,分别为首次尝试 / 最终)来自修复重试逻辑之前的运行;第一轮的 C 行是主表中三次 C 运行中的第一次。重复运行的汇总见 results/aggregate.md;每次运行的原始报告见 results/2026-11-04_*.json。

## 工作原理
text ──► build prompt (schema + instructions)
          │
          ▼
     LLM call ──► raw string
          │
          ▼
  JobPosting.model_validate_json(raw)
     │ ok                     │ ValidationError
     ▼                        ▼
  log success           log failure (error + raw)
  return model          attempt < MAX? ──► append error to messages, retry
                              │ no
                              ▼
                       raise ExtractionFailed (logged)

向模型展示的架构由 Pydantic 模型(JobPosting.model_json_schema())生成,从不手动编写,回复也使用同一个模型进行检查。若回复未通过验证,就会将验证器的错误反馈给模型,每个错误占一行,最多重试 3 次。得到有效回复后,两个确定性工具会整理结果(规范化技能名称,并根据文档标题检查地点)。它们是普通代码,并非模型调用,因此不会增加 LLM 调用次数,下面的各个设置也仍可进行比较。详情请参阅:docs/ARCHITECTURE.md。

四种设置:A 普通提示词,不重试。B 普通提示词,将错误反馈给模型(最多尝试 3 次)。C 提供商的严格 JSON Schema 模式,不重试。D Schema 模式加重试。

为什么不直接使用原生结构化输出?

Schema 模式帮助很大,而且成本更低,但它并不能完成全部工作。

它解决了什么问题。 对两种模型而言,设置 C 首次尝试即通过验证的比例为 98.9%(96.7%-100.0%);相比之下,单次普通提示词运行(A)中,7B 模型的比例为 83.3%,235B 模型为 70.0%。在普通模式下,模型实际违反的限制是 skills 最多 30 项;在 Schema 模式下,报告统计的是违反 Schema 所包含限制的回复,而每次 C 和 D 运行中此类回复均为 0(6 次 C 运行,2 次 D 运行)。C 的成本也低于 B:7B 模型为 $0.0076,而 B 为 $0.0103;235B 模型为 $0.0136,而 B 为 $0.0211(每次运行的平均值)。

它无法做到什么:

  1. 它只能保证结构,不能保证含义。 C 中剩余的失败属于 JSON Schema 无法表达的规则。6 次 C 运行中,有两次运行各有一份文档因技能名称重复而被拒绝(“LangChain”和“Langchain”;“Python”出现两次;两次拒绝均记录在 results/failure_examples.jsonl 中),因此 C 的最终有效率为 98.9%(96.7%-100.0%),而不是 100%。实习生意味着没有工作经验这条规则也有同样的问题(无法在 Schema 中声明);本项目记录的 637 行尝试记录中,这条规则从未触发(文件最后一行记录了该计数),因此我没有证据判断它是否有效。
  2. 有效不代表正确。 Schema 模式下,235B 模型的有效率为 98.9%(96.7%-100.0%),字段准确率为 86.1%(85.6%-87.0%),端到端准确率为 84.7%(82.8%-85.8%):低于我设定的 90% 目标。C 中的 7B 模型有效率同样高,但字段准确率仅为 68.2%(66.5%-69.8%);其资历级别准确率在 3 次 C 运行中分别为 55.0%、55.0% 和 52.6%。有效率并不能说明资历级别是否正确。
  3. 它可能以召回率换取有效性。 模型通过输出更短的技能列表来遵守数量上限,而技能恰恰是准确性最弱的字段。对于 235B 模型,3 次 C 运行中的技能 F1 分别为 66.9%、72.1% 和 65.7%;3 次 B 运行中则分别为 71.4%、71.6% 和 68.3%(平均值为 68.2% 和 70.4%,范围有重叠)。在第一轮单次运行中,A 和 B 分别为 72.1% 和 73.8%,C 和 D 则分别为 66.9% 和 63.4%。重复运行表明这是一种趋势,而非明确的效果;标注文档只有 n=20 份,我不会做出更多推断。
  4. 它取决于提供商。 两种模型在此都接受了严格的 json_schema,但这是因为请求使用了 OpenRouter 的 require_parameters,确保请求只能路由到支持该功能的提供商。7B 模型始终由同一家提供商提供服务;235B 模型则由三家提供商提供服务。

最常见的失败类型及其修复方法

根据普通提示词运行和重试运行(计数为首次错误出现在相应字段的失败尝试数, 见报告中的 failures.by_first_loc):

失败设置 A(运行 1 次)设置 B,运行 3 次修复方法
skills 超过 30 项上限7B:5 份失败文档中有 3 份;235B:9 份失败文档中有 8 份7B:失败尝试数分别为 5、5、3;235B:分别为 14、11、14重试反馈可修复大部分问题。Schema 模式可消除此问题。
技能名称超过 40 个字符(skills.*.name)7B 为 2,235B 为 17B:0、0、3;235B:1、1、0重试
用 null 代替 true/false(skills.*.required、salary_mentioned)无7B:一次运行中有 2 次尝试出错于 skills.*.required,另一次运行中有 1 次出错于 salary_mentioned重试
技能名称重复(也计入 skills,因为错误会报告在该字段)未单独计数未单独计数重试可修复:设置 B 中所有 5 次因名称重复而被拒的结果,在下一次尝试时均有效(每个被拒的尝试及其有效后续结果都记录在 results/failure_examples.jsonl 中)。Schema 模式无法修复:这是设置 C 中唯一剩下的失败(两份文档,同一文件)。
JSON 格式错误、Markdown 围栏、说明文字、额外字段、无效枚举值无:没有任何运行在根级字段、seniority、mode 或其他枚举值或额外字段上失败无无需修复

几乎所有问题都是数量上限,而非格式问题。在最初包含 11 份文档的基线测试中,两个模型的所有失败都是 skills 列表过长(results/2026-11-03_baseline_*.md)。我首先尝试让反馈更加具体。默认反馈行使用验证器自身的措辞(例如 skills: List should have at most 30 items after validation, not 42)。对于过长列表,我将其替换为包含数量和修复建议的反馈,例如 skills: has 39 items, maximum is 30 - remove 9, starting with required=false ones。结果好坏参半:

  • 这没有提高设置 B 中 235B 的最终有效率:修改前为 86.7%,之后三次运行分别为 86.7%、93.3% 和 86.7%。每次运行的区间都包含原来的数值,因此在 n=30 时看不出效果。
  • 在之后的 12 次文档运行中,首次 B 运行里失败的 4 份 235B 文档(jd_002、jd_003、jd_005、jd_023)有 6 次通过(jd_002 为 0/3,jd_003 为 1/3,jd_005 为 2/3,jd_023 为 3/3),但其他文档(jd_029、jd_030、jd_004)转而失败。我无法将修复效果与运行间波动区分开来。修复前后的首次尝试完全相同,但首次尝试有效率从 83.3% 变为 76.7%、73.3% 和 80.0%;这种差异属于运行间波动(抽样以及第一轮测试中未记录的提供商组合)。
  • **jd_002 在设置 B 的四次运行中对 235B 全部失败。**所有尝试都记录在 results/failure_examples.jsonl 中:首次运行(修复前)分别为 42、31、31 个技能;之后的三次运行分别为 70、31、31 个、47、31、31 个,以及 44、31、31 个。模型在首次重试时将技能数从 42–70 降至 31,然后就停在 31。对于包含 31 项的回复,新的反馈行是 skills: has 31 items, maximum is 30 - remove 1, starting with required=false ones(由代码生成;提示词文本本身未记录)。这是最明显的未解决失败:模型没有完成最后一步删减。

设计决策

  • 每个模型都设置 strict=True。 Pydantic 的默认宽松模式会悄悄将 "3" 转成 3,将 "yes" 转成 True,因此草率的回复也能通过校验,重试循环无事可做。严格模式则会拒绝这两种情况。
  • 字段必填但允许为 null(company、city、country、min_years_experience)。不设置默认值:模型必须 明确写出 null,因此“未提及”是模型作出的判断,而遗漏键则会报错,而不是静默地变成 null。
  • 重试提示词中每个错误占一行,格式为 loc: msg (got: <input, clipped to 60 chars>),根据 ValidationError.errors() 生成,而非使用 str(error)。原始字符串包含随版本变化的前缀、截断后的 input_value 和文档 URL,这些对模型而言都是噪声。唯一例外是列表过长的情况,会使用上文所示的、包含数量信息的行。我没有运行盲目重试对照组(不提供错误文本直接重试),因此没有 衡量反馈相比直接重新采样的效果;设置 A 与设置 B 的对比是有反馈的重试与不提供反馈的对比。
  • 工具用于确定性的后处理,不由模型调用。 验证通过后,再以普通代码运行规范技能名称处理和地点检查。这样每种设置都只需一个提示词,重试、尝试次数和成本统计也保持清晰;同时保留模型自己的输出(raw_posting), 因此有效性是根据模型实际说出的内容来衡量的。让模型调用工具是另一个我尚未进行的实验。
  • API 错误与验证错误分开处理。 429 或 5xx 错误会按退避间隔(2、4、8 s)重试,不会占用 3 次验证尝试中的任何一次。
  • 保留 30 个技能的上限。 首次使用普通提示词进行基准测试时,所有失败都由这一条限制造成,因此我保留它,用来测试重试和 schema 模式能做到什么,而不是放宽限制让数字看起来更好。

局限性

  • 30 份职位描述,20 个黄金标签。 一份文档会让有效率变动 3.3 个百分点,让字段准确率变动 5 个百分点;因此上面的区间较宽(例如,235B 配置 B 在一次运行中的最终有效率为 93.3% [78.7-98.2])。其中 5 个是非技术或半技术岗位,特意保留作为域外样本;其中两个(jd_012、jd_019)包含在 20 份已标注文档中。
  • 黄金标签由模型(Claude)起草,然后由我检查。 我核实了每个字段,并做了 17 处修正(docs/label_corrections.log)。只有一个人检查了一遍,因此我犯下的任何系统性错误都会留在标签中。首次基线测试后,我将标注规则写入了 schema 的字段描述,因此模型和标签遵循相同规则;使用早期提示词进行的运行无法比较。
  • 准确率的噪声下限,来自盲测。 对另外 5 份文档(jd_004、005、008、014、015,均不在黄金数据集中),我进行了盲标,并与 Claude 预先编写并封存的标签进行了比较,使用与评估相同的逐字段指标(以我的标签为参考):职位名称 80.0%,公司 100.0%,资历级别 80.0%,工作模式 80.0%,是否提及薪资 100.0%,最低工作年限 100.0%,城市 80.0%,国家 100.0%,技能 F1 61.0%(精确率 52.9%,召回率 72.0%),总体 86.8%(results/label_agreement.md)。5 份文档只能用于合理性检查,不能作为比率:一次意见不一致就会让某个字段变动 20 个百分点。这确实表明,在技能标注上,两位认真标注者之间的分歧,与模型和黄金标签之间的分歧程度相当;而 235B 大约 86% 的字段准确率,也接近两位标注者彼此之间的水平,因此距离我设定的 90% 目标仍有差距,其中一部分可能源于标签噪声,而非模型错误。但我尚未证明这一点。
  • 所有运行都在同一天进行,使用同一系列的两个模型和同一供应商层。 未设置温度。7B 始终路由到一个供应商;235B 路由到三个供应商。第一轮未记录供应商,因此供应商组合和采样噪声都混入了各次运行之间的差异。
  • 后处理工具没有针对最终标注规则进行调优,因此位置检查结果可能与某些文档的标签不一致。
  • D 配置对每个模型只运行了一次;只有 B 和 C 重复运行。
  • 数据。 data/jds/ 中的 30 份职位描述复制自公开招聘信息(LinkedIn 13 份、Work at a Startup 11 份、Wellfound 6 份,数量根据 data/jds/manifest.csv 统计),仅用于评估;如有要求,我会删除其中任何文档。manifest.csv 中有 5 行未记录来源 URL:jd_001、jd_002、jd_003、jd_004 和 jd_010(均来自 LinkedIn;备注写着“未记录 URL”)。

自行运行

需要 Python 3.11+ 和 uv。

git clone https://github.com/nilayp-codes/typecast.git
cd typecast
uv sync
cp .env.example .env        # then set OPENROUTER_API_KEY in .env (never commit it)

uv run soa extract data/jds/jd_001.txt                 # the JSON on stdout, a status line on stderr
uv run soa extract data/jds/jd_001.txt --json-mode     # provider schema mode
uv run soa extract data/jds/jd_001.txt > out.json

uv run soa eval --model openrouter/qwen/qwen-2.5-7b-instruct --setup B --limit 5
uv run soa eval --model openrouter/qwen/qwen3-235b-a22b-2507 --setup all

已知问题:在 Windows 上,请克隆到较短的路径下,因为 LiteLLM 会在 .venv 下写入缓存文件,而过深的路径会超出 Windows 的路径长度限制(此时 soa extract 会报错 APIConnectionError ... [Errno 2])。

soa eval 会调用真实模型:它会显示计划和预计调用次数,并在发送任何内容前询问(--yes 可跳过询问)。在我的运行中,对 30 份文档使用一个配置的费用在 $0.0076 到 $0.0234 之间。它会保存 results/<date>_<model>_<setup>.json,并向 results/summary.md 追加一行;--resume <eval id> 可继续被中断的运行。要合并重复运行的结果:uv run python scripts/aggregate_runs.py results/<files>.json(会写入 results/aggregate.md)。30 份职位描述位于 data/jds/,黄金标签位于 data/labels/。

测试和类型检查(测试中不会调用真实模型):

uv run pytest
uv run mypy --strict src/ packages/typecast-mcp/src/

在 AI Agent 中使用(MCP 服务)

typecast-mcp 是一个轻量的 MCP 服务,可根据 JobPosting 架构检查职位发布 JSON,并返回 Agent 可以处理的单行错误。它不包含大模型,不需要 API 密钥,也不会发起网络调用。它是一个独立的软件包(packages/typecast-mcp/),依赖项只有 pydantic 和 mcp,因此安装时不会引入 LiteLLM 或其余评测框架。

提供两个工具:

  • get_job_posting_schema:职位发布的 JSON 架构。它不包含所有规则(重复技能名称和实习生/经验规则仅由验证器检查)。
  • validate_job_posting:接受一个参数 posting,即作为一个字符串传入的完整职位发布 JSON。返回 valid、错误行列表(field: problem (got: value)),以及验证通过时解析后的职位发布内容。

安装(需要 uv)。适用于 Claude Desktop、Cursor 和其他客户端:

{
  "mcpServers": {
    "typecast": {
      "command": "uvx",
      "args": ["typecast-mcp"]
    }
  }
}

Claude Code:claude mcp add typecast -- uvx typecast-mcp。在 Windows 上,如果客户端找不到 uvx,请在 command 中填写 uvx.exe 的完整路径(使用 where.exe uvx 查找)。Microsoft Store 版 Claude Desktop 不会读取 %APPDATA%\Claude\claude_desktop_config.json;请打开 Settings → Developer → Edit Config,以打开它实际读取的文件。不使用 uv 时:运行 pip install typecast-mcp,并将 typecast-mcp 用作命令。

从克隆的仓库运行。 仓库自带 .mcp.json,可从工作树运行服务(uv run typecast-mcp)。在 Claude Code 中打开文件夹,出现提示时批准 typecast,然后用 /mcp 检查。批准后会在此文件夹中运行 uv run,安装锁定的依赖项并启动服务。请仅在你信任的克隆仓库中批准。

实际示例。 Qwen3-235B 对 jd_002 的回复包含 70 项技能。Agent 使用 validate_job_posting 检查后,收到一行错误:

skills: has 70 items, maximum is 30 - remove 40, starting with required=false ones

Agent 一轮就修正了职位发布内容。这是一次使用 Claude Sonnet 5.5 的运行,并非基准测试。

Claude Desktop 演示(演示中预先植入了错误)。Claude 调用 validate_job_posting,收到两行错误,修正后重新验证。请注意,为了让职位发布通过验证,它必须猜测年限:

https://github.com/user-attachments/assets/4190ef12-fc7c-46f8-ad48-dd128d80aa54

它不会检查的内容: 值是否正确。职位发布可以通过验证,但资历级别或技能仍然可能有误;请参阅为什么不直接使用原生结构化输出?。

详细信息、限制和设计决策:docs/MCP.md。

接下来我会做什么

  1. 仅用于验证的 MCP 服务:已完成,参见在 AI Agent 中使用。
  2. 发布到 PyPI:MCP 服务已完成,typecast-mcp。
  3. 修复数据指出的剩余失败:例如,解决无法通过 jd_002 最后一轮筛选的模型问题,告诉它应删掉哪些项目。固定温度并指定提供方后重新测量,以减少重复运行之间的差异。
  4. 为黄金集增加第二位标注者,扩大文档集,并重复运行设置 D,以免字段准确率数字受 n=20 限制。

许可证

MIT。参见 LICENSE。

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。