Pyrefly Issue Ranking Pipeline:用"双阶段 + 5 轮 LLM"管线自动为 GitHub Issue 排优先级
【免费下载链接】pyreflyA fast type checker and language server for Python项目地址: https://gitcode.com/GitHub_Trending/py/pyrefly
在类型检查器这类"误报即劝退用户、漏报即损害可信度"的项目中,GitHub Issue 的优先级排序是一项高人力成本、且容易被主观判断带偏的工程活动。Pyrefly 仓库在 scripts/issue_ranker/AGENTS.md 中描述了一套自动化管线:先从 GitHub 拉取 Issue,用 pyrefly/pyright/mypy 三个检查器实测 Issue 中的代码片段、结合真实开源项目(primer)上的错误数据做"影响面接地",再经过 5 轮分工明确的 LLM 调用(分类 → primer 影响 → 依赖关系 → 打分 → 终排)产出可复现的优先级榜单。读完本文,你能完整理解该管线的两阶段架构、各 Pass 的模型选择与成本估算、primer 分片并行机制,并能直接在本地或 CI 中运行 collect / rank / v1-analysis 各模式。
整体架构:collect 与 rank 两大阶段
该管线由 CLI 入口 驱动,支持--mode collect、--mode rank、--mode full(前两者合并)以及--mode v1-analysis(对既有 ranking.json 做事后 V1 差距分析,见 入口文件文档字符串)。
- Collect 阶段(
--mode collect):从 GitHub API 抓取 Issue 并"富化"——提取代码、跑检查器、分类状态、解析 Issue 间关系,产出中间 JSON。 - Rank 阶段(
--mode rank):在富化数据之上运行由 pipeline.py 编排的 5 轮 LLM 管线,输出 Markdown 报告与 JSON 结果。
两阶段解耦的好处是:昂贵的 collect(要跑真实检查器、安装依赖)只需要定期执行一次,而排名策略(prompt、权重、模型)可以在 rank 阶段快速迭代。
Collect 阶段的 7 个步骤
main.py 的collect_issue_data按顺序执行 7 个阶段:
- 拉取 Issue(github_issues.py):通过 GitHub API 获取 open issues,支持
--labels标签过滤与--limit数量限制。 - 区分类型检查类 Issue(
_is_typechecking_issue):只有"值得提取代码并实际跑检查器"的 Issue 才进入后续环节。其判定逻辑是:- 标题含 "false positive" / "false negative" → 一定是类型检查问题;
- 命中类型检查标签集(
typechecking、narrowing、overloads、conformance、typeshed等,见_TYPECHECKING_LABELS)→ 是; - 命中跳过标签集(
performance、language-server、documentation、configuration、build-fails等,见_SKIP_CODE_LABELS)→ 否; - 无标签时退回标题关键词启发式(
type、typing、narrow、protocol、dataclass等),且刻意避免把 "error handling" 这类词误判为类型问题。
- 提取代码块(code_extractor.py):三级策略——先匹配显式
python /py 围栏(_PYTHON_FENCE_RE),再匹配"看起来像 Python"的裸围栏(用行首def/class/import/from x import强信号避免误匹配错误输出文本),最后回退到LLM 提取:让 Haiku 模型基于 Issue 正文生成一个最小可复现代码片段(NO_CODE表示无法用代码演示)。 - LLM 修复残缺片段(
repair_snippet):Issue 里的粘贴代码常常缺 import、有语法错误或不完整。修复 prompt 明确要求"只修坏的部分、不重写逻辑",并携带 Issue 标题与正文作为上下文。 - 运行 pyrefly / pyright / mypy(issue_checker.py + dep_resolver.py):这是 collect 阶段技术含量最高的一环,下文单独展开。
- 状态分类(status_classifier.py):基于三方检查器结果把每个 Issue 归入五类之一——
false_positive/false_negative/confirmed_bug/already_fixed/feature_request。 - 解析关系(relationship_resolver.py):识别重复 Issue、阻塞关系(blocker)、父子 Issue,为 rank 阶段的依赖图 Pass 提供基础。
collect 结束后写出的 JSON 结构为{"issues": [...], "relationships": {...}, "metadata": {...}},metadata 中记录了标签过滤条件、Issue 总数、含代码的 Issue 数与类型检查 Issue 数(collect_issue_data末尾)。
检查器执行细节:先跑、再装依赖、重试一次、最后过滤
issue_checker.py 的设计文档(issue_checker.py 模块 docstring)概括了核心策略:每个代码片段写入临时目录,依次执行——
- pyrefly:
pyrefly check --output-format json <snippet>(JSON 输出便于机器解析); - pyright:
pyright --outputjson <snippet>; - mypy:
mypy --no-error-summary <snippet>; - 另外还会用
python3直接执行片段,尽力捕获运行时输出——当第三方依赖让静态检查不可靠时,运行时行为是重要的旁证信号。
所有检查器调用均带 60 秒超时,超时返回"CHECKER_TIMEOUT"标记(这与 primer 管线中的超时标记一致)。
依赖处理是这个流程中最微妙的部分,dep_resolver.py 采用"让检查器先报缺失、再据错误信息提取模块名"的路线,而不是预先静态扫描 import——因为检查器本身已知道哪些模块是标准库、哪些是第三方,天然更准。其具体机制:
- 用一组正则从三类检查器的错误信息中提取缺失模块名(pyrefly 的
Could not resolve import of "foo"、pyright 的Import "foo" could not be resolved、mypy 的Cannot find implementation or library stub for module named "foo"等,见_MISSING_MODULE_PATTERNS); - 维护模块名 → pip 包名映射表(
PIL → pillow、cv2 → opencv-python、sklearn → scikit-learn等,见_MODULE_TO_PACKAGE); - 先尝试批量
pip install,失败则逐个安装(install_missing_modules,dep_resolver.py#L102-L146); - 安装成功后重跑全部检查器一次;重试后仍然残留的 import 类错误被过滤掉(片段本就不可能带全依赖),而安装失败的模块记入
unresolved_deps字段。
unresolved_deps这个字段非常关键:下游的classify_status和打分 Pass 都会检查它——当依赖装不上且检查器"0 错误"时,系统明确知道这个 0 是不可信的(检查器根本没解析到第三方类型),会在 prompt 中附加DEPENDENCY WARNING,要求 LLM 转而依据 Issue 描述推理严重性,而不是轻信"0 错误 = 已修复"。
状态分类:启发式快路径 + LLM 兜底
status_classifier.py 先用廉价规则处理显而易见的情形:没有任何检查器结果(即没代码)→feature_request;三方检查器都没有"真实错误"(过滤掉reveal_type这类信息性输出后)→already_fixed。其余情形交给 Haiku 模型,prompt 中特别强调:跨检查器比较错误要"语义比较而非字面比较"、reveal_type是信息性输出不计入错误数、import 错误应忽略。若 LLM 返回非法状态或调用失败,则回退到简单启发式(_heuristic_classify:只有 pyrefly 报错 → false positive;只有其他检查器报错 → false negative;都报错 → confirmed bug)。
Rank 阶段:5 轮 LLM 管线的模型分工与成本估算
run_pipeline 按顺序执行 5 个 Pass,每个 Pass 使用不同档位的小/大模型,并在日志中记录耗时与美元成本估算。成本估算表(_COST_ESTIMATES)大致为:Haiku 单次调用约 $0.002、Sonnet 约 $0.03、Opus 大批量调用约 $0.75。5 个 Pass 分别对应 passes/ 目录下的模块:
| Pass | 模块 | 模型与调用量 | 职责 |
|---|---|---|---|
| 1. Categorize | passes/categorize.py | Haiku,每 Issue 一次 | 分类 Issue 类型/领域(false positive、性能、IDE、规范符合性等) |
| 2. Primer Impact | passes/primer_impact.py | 以确定性字符串匹配为主,少量 Haiku 模糊兜底 | 把 Issue 匹配到 primer 真实错误上,量化影响面 |
| 3. Dependencies | passes/dependencies.py | Opus,仅 1 次 | 构建依赖图:阻塞链、重复簇、依赖组 |
| 4. Score | passes/score.py | Sonnet,每 Issue 一次 | 0–100 加权打分,附分项 breakdown 与理由 |
| 5. Rank | passes/rank.py | Opus,按 50 个 Issue 一批 | 结合全部前置结果做终排序与同分裁决 |
值得注意的健壮性设计(均可在 pipeline.py 中验证):
- Pass 3 失败降级:依赖图构建若抛异常,则降级为空图(
dependency_groups/blocking_chains/duplicate_clusters全空)继续后续 Pass; - Pass 5 完全失败降级:回退为"按 priority_score 排序 + 机械分层"的 fallback 排名,并在 summary 中标注原因;
- Pass 结果缓存:
--pass-results <path>会落盘 Pass 1–4 的中间结果(categorizations/primer_impacts/dep_graph/scores),再次运行时若缓存完整则跳过 Pass 1–4、只重跑 Pass 5(run_pipeline#L65-L92)。CI 中这正是用来反复迭代终排 prompt 而不用重新支付前四轮成本的手段; - 打分重试:
score_all对单个 Issue 打分失败会等待 2 秒重试一次,再失败则记为priority_score: -1.0并附SCORING FAILED理由,避免整体管线因单点失败中断。
Pass 2:primer 匹配——确定性模板化 + 具体度评估
Pass 2 是"把排名扎根于真实影响面"的关键。primer_impact.py 的匹配流程:
- 建索引:遍历 primer 数据中每个项目的 pyrefly 错误,把错误消息中反引号包裹的标识符替换为
`_`形成"消息模板"(_templatize,primer_impact.py#L27-L30),即Argument 'x' is not assignable to parameter 'y'归一为Argument '_is not assignable to parameter '_',按(error_kind, template)聚合出受影响项目集合与总错误数(_build_primer_index); - Kind 匹配:先精确匹配 Issue 错误 kind 与 primer kind,再尝试归一化(去连字符/下划线、小写)匹配,再做子串包含匹配,最后才用一次 Haiku 调用做语义级模糊匹配(
_fuzzy_match_kind); - 模式具体度评估:匹配成功后再用一次 Haiku 调用评估"该模式对本 Issue 的具体度"——
HIGH表示该 Issue 很可能就是这些 primer 错误的主要成因,LOW表示模式过于泛化、根因众多。这个pattern_specificity随后会被传入 Pass 4 的 prompt,让打分时按具体度加权 primer 计数,避免"匹配数很多但相关性很弱"的假信号。
Pass 4:打分权重——误报与性能最重,实现难度不计分
Pass 4 的系统 prompt(_SYSTEM_PROMPT)是整条管线价值观的浓缩,其权重表值得完整呈现:
| 权重 | 信号 | 说明 |
|---|---|---|
| 最高 (x3) | 误报影响 | pyrefly 报错而 pyright/mypy 不报错的 Issue——这类错误直接赶走用户;primer 频次高会放大该信号 |
| 最高 (x3) | 性能 | 内存、检查速度、LSP 响应。性能问题直接阻碍规模化采用 |
| 高 (x2) | 团队指派优先级 | GitHub Projects 中的 P0/P1/P2(P0 → 80+ 分,P1 → 65+,P2 → 50+);但 prompt 明确允许 LLM 覆盖过期/错误的优先级 |
| 中 (x1.5) | 漏报 + 规范符合度 | pyrefly 漏报而 pyright/mypy 能抓到的真实类型错误;TypeVar/ParamSpec/overloads/narrowing 等规范差距。影响正确性,但因用户感知不到"没报错",紧急度低于误报 |
| 中 (x1.5) | 可处理性 | 有清晰最小复现、根因已定位、范围明确 → 高分;描述含糊、无法复现 → 低分。特别强调:可处理性 ≠ 实现难度,修起来再难也不扣分 |
| 高 (x2) | IDE 与易用性 | hover/补全/跳转/诊断等 LSP 功能;IDE bug 建议 60–80 分,IDE 功能 45–65 分 |
| 中 (x1.5) | Primer 广度 | 多少 primer 项目命中该错误模式,需结合具体度评估加权 |
| 高 (x2.5) | 战略采用 | 打了 pytorch / google 标签的 Issue 是最高优先的采用目标,阻塞类应给 75+ |
| 中 (x1.5) | 生态采用 | pydantic、sqlalchemy 等框架标签,低于战略目标 |
| 低 (x0.5) | 边缘情形 | 影响项目少、交互少、陈旧、小众场景 |
prompt 中有两条硬约束尤其体现设计意图:
- "修复难度或复杂度永远不能降低分数"——难修的 bug 和易修的 bug 一样重要,只按用户影响与采用风险打分(score.py#L47);
- "盲评"设计:LLM 不知道哪些 Issue 已被团队划入 V1 milestone,纯靠信号打分,从而可以用事后重叠率来盲验证管线质量(score.py 模块 docstring)。
打分输入还会组装丰富的上下文(score_issue):前序 Pass 的分类与 primer 命中数、反应数/评论数/子 Issue 数、重复簇规模、该 Issue 阻塞的其他 Issue 数量、unresolved_deps警告、Python 运行时输出、前 5 条评论内容,以及从 spec_fetcher.py 拉取的 typing 规范摘录(按 primer kind 或 pyrefly 错误 kind 检索)作为接地材料。输出为priority_score(0–100)+ 9 个分项 breakdown(false_positive_impact、performance、team_priority、correctness、actionability、ide_usability、primer_breadth、adoption_impact、community_demand)+ 一两句理由。
Primer 管线:用 137 个真实开源项目给排名"接地"
Rank 所需的 primer 数据由独立脚本 scripts/compare_typecheckers.py 生产:它克隆约 137 个开源 Python 项目,对每个项目分别运行 pyrefly、pyright、mypy,输出primer_errors.json供 ranker 的 primer_impact Pass 消费。其运行前提与本地用法(来自脚本 docstring,compare_typecheckers.py#L1-L31):
- 需要一个安装了 pyright 与 mypy 的 Python 环境(
pip install pyright mypy); - 支持两阶段模式:先
--clone-only --cache-dir /tmp/primer_cache克隆项目,再在隔离环境内--reuse-cache跑检查器,方便在不能联网/克隆的受限环境(如容器内)分步执行; - 检查器输出有两种形态:默认的错误计数汇总表,以及
--output-json的完整错误消息 JSON(后者才是 ranker 需要的输入); - 超时处理与 issue_checker 一致:子进程超时被 kill 后返回
stderr="CHECKER_TIMEOUT"的占位结果(compare_typecheckers.py#L53-L69)——注意这里是刻意使用的专用标记"CHECKER_TIMEOUT"而非普通"timeout",下游解析时不会与代码里的超时字样混淆。
分片并行由--shard-index/--num-shards两个参数实现(_apply_sharding):参数必须成对出现、--num-shards为正、--shard-index必须在[0, num_shards)区间内;项目列表按步长切片projects[shard_index :: num_shards]分片,各分片可完全并行。各分片的 JSON 结果由 scripts/merge_primer_shards.py 合并回单文件,再交给 rank 阶段的--primer-data。
GitHub Actions 工作流:分片矩阵 + 三任务串联
.github/workflows/issue_ranking.yml 把上述脚本装配成可手动触发(workflow_dispatch)的 CI 工作流,输入参数包括:
labels:逗号分隔的标签过滤(留空 = 所有 open issues);run_primer:是否运行 primer 对比(约增加 2 小时时长);check_snippets:是否对 Issue 代码片段跑检查器(默认开);v1_analysis_run_id:填了则只做 V1 分析,从指定 Run 下载 ranking.json,跳过 primer 与排名。
工作流包含 4 个 job,依赖关系为primer(×4) → primer-merge → rank,另有条件触发的v1-analysis-standalone:
- primer:
matrix.shard: [0, 1, 2, 3]四分片并行跑compare_typecheckers.py,每分片timeout-minutes: 300;先cargo build --release构建 pyrefly 二进制,pip install pyright mypy,产物上传为 artifactprimer-shard-N(issue_ranking.yml#L29-L83)。 - primer-merge:用 sparse-checkout 只拉取 merge_primer_shards.py,下载 4 个分片 artifact 后合并为
primer_errors.json,上传为primer-results。 - rank:串联三个子步骤——
- 收集:
python -m scripts.issue_ranker --mode collect,若check_snippets开启则pip install pyrefly pyright mypy并用which pyrefly定位二进制传入--pyrefly(issue_ranking.yml#L192-L232)。注意此处GITHUB_TOKEN优先取secrets.GH_PROJECT_TOKEN——该 token 需要read:project权限才能拉取 GitHub Projects 里的 P0/P1/P2 优先级,缺失时会回退到普通 token,此时优先级字段为空(工作流内注释有说明); - 排名:
--mode rank,若 primer 数据存在则附加--primer-data,并始终传--pass-results /tmp/pass_results_cache.json启用缓存; - V1 差距分析:
--mode v1-analysis --apply-labels,读 ranking.json,把分析结论以v1-verified/consider-adding/removing标签回写 GitHub Issue(入口代码见main.py#L380-L396,仅管理这三类标签,不碰其他标签),并追加到 Run Summary; - 最后打印分层统计(critical/high/medium/low 各多少)与 Top 10 Issue,并上传
ranking.md、ranking.json、缓存与全部日志作为 artifactissue-ranking。
- 收集:
- v1-analysis-standalone:当只填了
v1_analysis_run_id时,用gh run download <run_id> -n issue-ranking从历史 Run 拉取 ranking 产物,单独重跑 V1 分析——支持"排名只做一次,标签策略反复迭代"的低成本玩法。
权限方面值得注意:工作流整体permissions: {}显式清空,仅 rank 与 v1 job 按需申请issues: write(用于回写标签),LLM 调用密钥使用secrets.PRIMER_CLASSIFIER_API_KEY注入ANTHROPIC_API_KEY。
本地运行与完整参数速查
AGENTS.md 给出的最小本地运行序列(需要:pyright/mypy 已安装、pyrefly 二进制、GITHUB_TOKEN、ANTHROPIC_API_KEY):
# 1. Primer(需要 pyrefly 二进制;没有 cargo 时用 --pyrefly 指向现成二进制) python3 scripts/compare_typecheckers.py --output /tmp/primer.json \ --pyrefly /path/to/pyrefly # 2. 收集并富化 Issue(需要 GITHUB_TOKEN) python3 -m scripts.issue_ranker --mode collect \ --pyrefly /path/to/pyrefly --output /tmp/issues.json # 3. 排名(需要 ANTHROPIC_API_KEY) python3 -m scripts.issue_ranker --mode rank \ --primer-data /tmp/primer.json --issue-data /tmp/issues.json \ --output /tmp/ranking.md --output-json /tmp/ranking.json完整 CLI 参数(来源:main.py 的main()):
| 参数 | 说明 |
|---|---|
--mode {collect,rank,full,v1-analysis} | 必需。collect 抓取+富化;rank 跑 LLM 管线;full 两者合一;v1-analysis 对既有 ranking.json 做 V1 差距报告 |
--labels | 逗号分隔的 GitHub 标签过滤(默认所有 open issues) |
--limit N | 最多抓取 N 个 Issue(调试用) |
--pyrefly PATH | 用于检查代码片段的 pyrefly 二进制路径;collect 阶段不提供则跳过检查器执行 |
--primer-data PATH | compare_typecheckers.py 产出的 primer_errors.json |
--issue-data PATH | collect 模式产出的 issue_data.json(rank 模式必需,除非用 full) |
--output/-o | Markdown 报告输出路径;不传则打印到 stdout |
--output-json | JSON 结果输出路径 |
--pass-results | Pass 1–4 中间结果缓存路径;文件有效时跳过 Pass 1–4 只重跑 Pass 5 |
--ranking-json | v1-analysis 模式的输入 ranking.json(缺省回退取--output-json值) |
--apply-labels | v1-analysis 模式下把标签回写到 GitHub(仅管理 v1-verified / consider-adding / removing 三类) |
--debug | 开启 DEBUG 级日志 |
测试方面,AGENTS.md 指出单测位于 scripts/issue_ranker/tests/(含对 ranker 与 compare_typecheckers 的测试),LLM 集成测试位于 scripts/issue_ranker/llm_tests/(覆盖代码提取、状态分类、各 Pass、GitHub 抓取与 LLM 传输层),并可用buck test pyrefly:issue_ranker_tests运行。
经验结论:primer 数据与团队优先级标签为何都重要
AGENTS.md 最后记录了 2026 年 3 月的一组管线调优经验(原文档中的关键结论,可视为该团队的实测发现):
- Primer 数据显著改变排名:在 137 个真实项目上运行检查器让排名扎根于实际影响面;没有 primer 数据时,单个 Issue 的得分可能摆动 30–44 分,分层(tier)分布也会大幅漂移;
- 团队优先级标签有独立价值:纳入 GitHub Projects 的 P0/P1/P2 后,管线排名与 V1 milestone 的重叠率提升约 16 个百分点;
- 两者叠加效果最佳——真实影响面(客观、来自代码实测)与团队判断(主观、但反映业务约束)互为补充。
这一结论也解释了管线的整体设计哲学:确定性计算(检查器实测、字符串模板匹配、分片统计)负责提供可复现、可核实的"事实信号",LLM 只负责其中真正需要语义理解的环节(代码提取/修复、状态归类、依赖图、加权打分、终排裁决),并且每一处 LLM 输出都有启发式 fallback(状态分类的_heuristic_classify、Pass 3/5 的降级路径、Pass 2 的确定性匹配优先),使得整条管线在 API 不可用时仍能产出可用结果。
核心文件索引
| 路径 | 角色 |
|---|---|
| scripts/issue_ranker/AGENTS.md | 管线架构总览(本文主体文档) |
| scripts/issue_ranker/main.py | CLI 入口,collect 七阶段与四种 mode |
| scripts/issue_ranker/pipeline.py | 5 轮 LLM 管线编排、成本估算、缓存与降级 |
| scripts/issue_ranker/code_extractor.py | 三级代码提取 + LLM 片段修复 |
| scripts/issue_ranker/issue_checker.py | 三检查器执行、依赖重试、错误过滤 |
| scripts/issue_ranker/dep_resolver.py | 从检查器错误提取缺失模块并安装 |
| scripts/issue_ranker/status_classifier.py | 五态分类:启发式 + LLM 兜底 |
| scripts/issue_ranker/relationship_resolver.py | 重复/阻塞/父子关系解析 |
| scripts/issue_ranker/passes/ | 5 个 Pass 实现(categorize / primer_impact / dependencies / score / rank) |
| scripts/issue_ranker/llm_transport.py | LLM API 封装(Anthropic + Llama),带重试 |
| scripts/issue_ranker/spec_fetcher.py | 拉取 typing 规范摘录供打分接地 |
| scripts/issue_ranker/report_formatter.py | Markdown / JSON 报告生成 |
| scripts/compare_typecheckers.py | primer 对比脚本(克隆、分片、双检查器) |
| scripts/merge_primer_shards.py | 合并 primer 分片输出 |
| .github/workflows/issue_ranking.yml | CI 工作流:分片矩阵、合并、排名、V1 分析 |
| scripts/issue_ranker/tests/ | 单元测试 |
| scripts/issue_ranker/llm_tests/ | LLM 集成测试 |
【免费下载链接】pyreflyA fast type checker and language server for Python项目地址: https://gitcode.com/GitHub_Trending/py/pyrefly
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考