从 0 到 1 构建 GitHub 自动化测试 Case Reviewer Bot(二):小试牛刀-先跑一遍基础的功能

目录

  • [从 0 到 1 构建 GitHub 自动化测试 Case Reviewer Bot(二):小试牛刀-先跑一遍基础的功能](#从 0 到 1 构建 GitHub 自动化测试 Case Reviewer Bot(二):小试牛刀-先跑一遍基础的功能)
    • 源码地址
    • 先看全局流程
    • 几个先补上的概念
      • [FastAPI 服务](#FastAPI 服务)
      • webhook
      • [case memory](#case memory)
    • 目录里先看这几个文件
    • [创建 Python 环境](#创建 Python 环境)
    • [复制 basic 配置](#复制 basic 配置)
    • 启动服务
    • [先跑一次 smoke test](#先跑一次 smoke test)
    • [简单看一下本地 smoke test 跑了什么](#简单看一下本地 smoke test 跑了什么)
    • 运行遇到的问题
      • [1. 端口 8000 被占用](#1. 端口 8000 被占用)
      • [2. conda 环境没激活](#2. conda 环境没激活)
      • [3. PowerShell 禁止执行脚本](#3. PowerShell 禁止执行脚本)
      • [4. ngrok 命令不存在](#4. ngrok 命令不存在)
      • [5. ngrok 版本不一致或账号未认证](#5. ngrok 版本不一致或账号未认证)
      • [6. ngrok 启动了,但看不到公网地址](#6. ngrok 启动了,但看不到公网地址)
      • [7. `/health` 不通](#7. /health 不通)
      • [8. smoke test 失败](#8. smoke test 失败)
      • [9. `.env` 里填了模型配置,但启动很慢](#9. .env 里填了模型配置,但启动很慢)
    • 总结

从 0 到 1 构建 GitHub 自动化测试 Case Reviewer Bot(二):小试牛刀-先跑一遍基础的功能

这一篇先把系统在本地跑起来。

但它不只是"启动一个服务"。我更希望这一篇能帮你先建立一个全局印象:这个 bot 以后会怎么接 GitHub、怎么读 PR、怎么查历史 case、怎么生成评论。先知道大概流程,再看后面的 webhook 和 GitHub API,脑子里会有位置。

本篇先不真正连接 GitHub,不发 PR 评论,不打开 LLM。目标是:

text 复制代码
拿到源码
  -> 创建环境
  -> 启动服务
  -> 跑 smoke test
  -> 看懂 basic 版最小流程

源码地址

这一篇对应的 demo 源码已经放到 GitHub:

text 复制代码
https://github.com/yxh175/reviewer-bot

后面的命令默认你已经进入 demo 根目录。为了方便描述,先把这个目录记成:

text 复制代码
reviewer-bot/

先看全局流程

这个 basic demo 最终想跑的流程是:

text 复制代码
GitHub PR
  -> GitHub webhook
  -> FastAPI 服务 /github/webhook
  -> 拉取 PR changed files
  -> 提取测试 case
  -> 查询 SQLite case memory
  -> 计算相似度
  -> 生成 Markdown 评论
  -> 调 GitHub API 更新 PR 评论

用 Mermaid 表达会更清楚:
#mermaid-svg-N3QYiqECNyY49BGv{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-N3QYiqECNyY49BGv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-N3QYiqECNyY49BGv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-N3QYiqECNyY49BGv .error-icon{fill:#552222;}#mermaid-svg-N3QYiqECNyY49BGv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-N3QYiqECNyY49BGv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-N3QYiqECNyY49BGv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-N3QYiqECNyY49BGv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-N3QYiqECNyY49BGv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-N3QYiqECNyY49BGv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-N3QYiqECNyY49BGv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-N3QYiqECNyY49BGv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-N3QYiqECNyY49BGv .marker.cross{stroke:#333333;}#mermaid-svg-N3QYiqECNyY49BGv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-N3QYiqECNyY49BGv p{margin:0;}#mermaid-svg-N3QYiqECNyY49BGv .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-N3QYiqECNyY49BGv .cluster-label text{fill:#333;}#mermaid-svg-N3QYiqECNyY49BGv .cluster-label span{color:#333;}#mermaid-svg-N3QYiqECNyY49BGv .cluster-label span p{background-color:transparent;}#mermaid-svg-N3QYiqECNyY49BGv .label text,#mermaid-svg-N3QYiqECNyY49BGv span{fill:#333;color:#333;}#mermaid-svg-N3QYiqECNyY49BGv .node rect,#mermaid-svg-N3QYiqECNyY49BGv .node circle,#mermaid-svg-N3QYiqECNyY49BGv .node ellipse,#mermaid-svg-N3QYiqECNyY49BGv .node polygon,#mermaid-svg-N3QYiqECNyY49BGv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-N3QYiqECNyY49BGv .rough-node .label text,#mermaid-svg-N3QYiqECNyY49BGv .node .label text,#mermaid-svg-N3QYiqECNyY49BGv .image-shape .label,#mermaid-svg-N3QYiqECNyY49BGv .icon-shape .label{text-anchor:middle;}#mermaid-svg-N3QYiqECNyY49BGv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-N3QYiqECNyY49BGv .rough-node .label,#mermaid-svg-N3QYiqECNyY49BGv .node .label,#mermaid-svg-N3QYiqECNyY49BGv .image-shape .label,#mermaid-svg-N3QYiqECNyY49BGv .icon-shape .label{text-align:center;}#mermaid-svg-N3QYiqECNyY49BGv .node.clickable{cursor:pointer;}#mermaid-svg-N3QYiqECNyY49BGv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-N3QYiqECNyY49BGv .arrowheadPath{fill:#333333;}#mermaid-svg-N3QYiqECNyY49BGv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-N3QYiqECNyY49BGv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-N3QYiqECNyY49BGv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-N3QYiqECNyY49BGv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-N3QYiqECNyY49BGv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-N3QYiqECNyY49BGv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-N3QYiqECNyY49BGv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-N3QYiqECNyY49BGv .cluster text{fill:#333;}#mermaid-svg-N3QYiqECNyY49BGv .cluster span{color:#333;}#mermaid-svg-N3QYiqECNyY49BGv div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-N3QYiqECNyY49BGv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-N3QYiqECNyY49BGv rect.text{fill:none;stroke-width:0;}#mermaid-svg-N3QYiqECNyY49BGv .icon-shape,#mermaid-svg-N3QYiqECNyY49BGv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-N3QYiqECNyY49BGv .icon-shape p,#mermaid-svg-N3QYiqECNyY49BGv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-N3QYiqECNyY49BGv .icon-shape .label rect,#mermaid-svg-N3QYiqECNyY49BGv .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-N3QYiqECNyY49BGv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-N3QYiqECNyY49BGv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-N3QYiqECNyY49BGv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} GitHub PR
GitHub webhook
FastAPI /github/webhook
拉取 PR changed files
提取测试 case
查询 SQLite case memory
计算相似度
生成 Markdown 评论
调用 GitHub API 更新 PR 评论

这一篇只跑其中的本地部分:

text 复制代码
本地启动 FastAPI
  -> /health 验证服务
  -> basic-smoke.ps1 验证 case 提取、case memory、patch review

对应到本篇要完成的事情,其实只有这条小链路:
#mermaid-svg-bVg5X0IFfWvOJ7ak{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-bVg5X0IFfWvOJ7ak .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bVg5X0IFfWvOJ7ak .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bVg5X0IFfWvOJ7ak .error-icon{fill:#552222;}#mermaid-svg-bVg5X0IFfWvOJ7ak .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bVg5X0IFfWvOJ7ak .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bVg5X0IFfWvOJ7ak .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bVg5X0IFfWvOJ7ak .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bVg5X0IFfWvOJ7ak .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bVg5X0IFfWvOJ7ak .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bVg5X0IFfWvOJ7ak .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bVg5X0IFfWvOJ7ak .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bVg5X0IFfWvOJ7ak .marker.cross{stroke:#333333;}#mermaid-svg-bVg5X0IFfWvOJ7ak svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bVg5X0IFfWvOJ7ak p{margin:0;}#mermaid-svg-bVg5X0IFfWvOJ7ak .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-bVg5X0IFfWvOJ7ak .cluster-label text{fill:#333;}#mermaid-svg-bVg5X0IFfWvOJ7ak .cluster-label span{color:#333;}#mermaid-svg-bVg5X0IFfWvOJ7ak .cluster-label span p{background-color:transparent;}#mermaid-svg-bVg5X0IFfWvOJ7ak .label text,#mermaid-svg-bVg5X0IFfWvOJ7ak span{fill:#333;color:#333;}#mermaid-svg-bVg5X0IFfWvOJ7ak .node rect,#mermaid-svg-bVg5X0IFfWvOJ7ak .node circle,#mermaid-svg-bVg5X0IFfWvOJ7ak .node ellipse,#mermaid-svg-bVg5X0IFfWvOJ7ak .node polygon,#mermaid-svg-bVg5X0IFfWvOJ7ak .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-bVg5X0IFfWvOJ7ak .rough-node .label text,#mermaid-svg-bVg5X0IFfWvOJ7ak .node .label text,#mermaid-svg-bVg5X0IFfWvOJ7ak .image-shape .label,#mermaid-svg-bVg5X0IFfWvOJ7ak .icon-shape .label{text-anchor:middle;}#mermaid-svg-bVg5X0IFfWvOJ7ak .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-bVg5X0IFfWvOJ7ak .rough-node .label,#mermaid-svg-bVg5X0IFfWvOJ7ak .node .label,#mermaid-svg-bVg5X0IFfWvOJ7ak .image-shape .label,#mermaid-svg-bVg5X0IFfWvOJ7ak .icon-shape .label{text-align:center;}#mermaid-svg-bVg5X0IFfWvOJ7ak .node.clickable{cursor:pointer;}#mermaid-svg-bVg5X0IFfWvOJ7ak .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-bVg5X0IFfWvOJ7ak .arrowheadPath{fill:#333333;}#mermaid-svg-bVg5X0IFfWvOJ7ak .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-bVg5X0IFfWvOJ7ak .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-bVg5X0IFfWvOJ7ak .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bVg5X0IFfWvOJ7ak .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-bVg5X0IFfWvOJ7ak .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bVg5X0IFfWvOJ7ak .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-bVg5X0IFfWvOJ7ak .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-bVg5X0IFfWvOJ7ak .cluster text{fill:#333;}#mermaid-svg-bVg5X0IFfWvOJ7ak .cluster span{color:#333;}#mermaid-svg-bVg5X0IFfWvOJ7ak div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-bVg5X0IFfWvOJ7ak .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-bVg5X0IFfWvOJ7ak rect.text{fill:none;stroke-width:0;}#mermaid-svg-bVg5X0IFfWvOJ7ak .icon-shape,#mermaid-svg-bVg5X0IFfWvOJ7ak .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bVg5X0IFfWvOJ7ak .icon-shape p,#mermaid-svg-bVg5X0IFfWvOJ7ak .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-bVg5X0IFfWvOJ7ak .icon-shape .label rect,#mermaid-svg-bVg5X0IFfWvOJ7ak .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bVg5X0IFfWvOJ7ak .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-bVg5X0IFfWvOJ7ak .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-bVg5X0IFfWvOJ7ak :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} python -m app.cli run
启动 FastAPI 服务
GET /health
确认服务可访问
运行 basic-smoke.ps1
确认本地核心链路可用

几个先补上的概念

在真正启动前,先把几个词说清楚。后面会反复出现。

FastAPI 服务

这个 bot 本质上是一个 Python Web 服务。

启动后,它会监听:

text 复制代码
http://127.0.0.1:8000

目前最重要的接口有两个:

text 复制代码
GET  /health
POST /github/webhook

这一篇先验证 /health。下一篇再让 GitHub 调 /github/webhook

webhook

webhook 可以先理解成"别人主动来通知你"。

不是我们的服务一直问 GitHub:

text 复制代码
有没有新 PR?
有没有新 commit?

而是 GitHub 有事件时主动 POST 给我们:

text 复制代码
有 PR 更新了,我把事件内容发给你的 /github/webhook

这一篇还不配置 webhook,但先把服务跑起来,为下一篇做准备。

case memory

case memory 是历史测试用例库。

bot 要判断一个新测试 case 是否重复,必须先知道仓库里已经有哪些 case。

basic 版默认用 SQLite:

text 复制代码
data/case_memory.db

不是因为 SQLite 最强,而是因为 basic demo 需要简单、可见、少依赖。

目录里先看这几个文件

进入项目后,可以先看这些:

text 复制代码
app/
  服务和核心逻辑

examples/
  本地示例仓库和示例 PR patch

.env.basic.example
  basic 版快速启动配置

BASIC_QUICKSTART.md
  最短运行教程

scripts/basic-smoke.ps1
  本地冒烟测试脚本

创建 Python 环境

这个 demo 用 Python 写,推荐用 conda 隔离环境:

powershell 复制代码
conda env create -f environment.yml
conda activate test-case-reviewer-bot

安装 basic 依赖:

powershell 复制代码
python -m pip install -r requirements-basic.txt

博客版 demo 已经把升级版依赖拿掉了。这里先只安装 FastAPI、uvicorn 和 OpenAI SDK 这些 basic 链路会用到的包。

basic 默认用 SQLite,所以不需要额外安装数据库驱动。这样做的目的不是说 SQLite 最强,而是让新人先把 webhook、case memory 和 PR 评论这条链路跑明白。

复制 basic 配置

basic 版只保留一个配置模板,直接复制:

powershell 复制代码
Copy-Item .env.basic.example .env -Force

打开 .env,先关注这几项:

dotenv 复制代码
APP_NAME=reviewer-bot
CASEBOT_RUNTIME=webhook
WEBHOOK_PROCESS_MODE=inline
WEBHOOK_HOST=0.0.0.0
WEBHOOK_PORT=8000

DATABASE_PATH=data/case_memory.db

EMBEDDING_PROVIDER=local
LLM_REVIEW_ENABLED=false
LOG_PROGRESS=true
LOG_LEVEL=INFO

这里每个配置都有作用:

  • CASEBOT_RUNTIME=webhookpython -m app.cli run 会启动服务。
  • WEBHOOK_PROCESS_MODE=inline:webhook 请求里直接处理任务,适合 demo。
  • DATABASE_PATH=data/case_memory.db:basic 版用本地 SQLite 文件保存 case memory。
  • EMBEDDING_PROVIDER=local:先不调用外部模型。
  • LLM_REVIEW_ENABLED=false:先不让模型延迟影响调试。
  • LOG_LEVEL=INFO:打印阶段日志,方便知道服务在做什么。

如果忘了复制 .env,服务也能启动,但后面连接 GitHub 时会缺少 GITHUB_WEBHOOK_SECRETGITHUB_TOKEN 这些配置。所以建议一开始就从 .env.basic.example 复制一份。

启动服务

执行:

powershell 复制代码
python -m app.cli run

正常会看到:

text 复制代码
[casebot 23:29:08] INFO  Starting reviewer-bot webhook server on http://0.0.0.0:8000
INFO:     Started server process [29932]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)

另开一个 PowerShell 验证健康检查:

bash 复制代码
curl http://127.0.0.1:8000/health

预期返回类似:

text 复制代码
ok                  : True
app_name            : reviewer-bot
runtime             : webhook
webhook_process_mode: inline
database_path       : data/case_memory.db

先跑一次 smoke test

服务能启动不代表核心链路没问题。basic 目录里有一个本地冒烟脚本:

powershell 复制代码
.\scripts\basic-smoke.ps1

它不会连 GitHub,也不会调 OpenAI。它做几件事:

  • 编译 Python 文件。
  • 验证 pytest case 提取。
  • 验证 TS/JS case 提取。
  • bootstrap 示例 case memory。
  • review 本地 sample patch。
  • 生成一份 Markdown 评论。

看到下面输出就说明本地核心链路没坏:

text 复制代码
Smoke test passed

生成的临时文件在:

text 复制代码
.runtime/smoke/case_memory_smoke.db
.runtime/smoke/review-comment.md

这个脚本的价值是:以后你改代码,如果还没接 GitHub,也可以先跑它确认 basic 链路还活着。

最终的结果

简单看一下本地 smoke test 跑了什么

如果你打开 scripts/basic-smoke.ps1,会看到它没有做什么神秘操作。

核心就是几步:

text 复制代码
python -B -m compileall app
extract_cases(...)
python -m app.cli bootstrap-repo --root examples/repo
python -m app.cli review-patch examples/sample-pr-diff.patch

也就是说,在还没接 GitHub 之前,它已经验证了:

text 复制代码
示例测试文件
  -> case 提取
  -> 写入 SQLite
  -> 示例 patch review
  -> 生成评论 Markdown

换成流程图就是:

这条本地链路跑通后,再接 GitHub 会踏实很多。否则你一上来调 webhook,出问题时很难知道是 GitHub 配错了,还是本地提取器坏了。

运行遇到的问题

1. 端口 8000 被占用

现象:

text 复制代码
address already in use

解决方式一:关掉占用 8000 的进程。

解决方式二:改 .env

dotenv 复制代码
WEBHOOK_PORT=8001

然后启动:

powershell 复制代码
python -m app.cli run

后面 ngrok 也要对应:

powershell 复制代码
ngrok http 8001

2. conda 环境没激活

现象:

text 复制代码
ModuleNotFoundError: No module named 'fastapi'

先看当前环境:

powershell 复制代码
conda info --envs

再激活:

powershell 复制代码
conda activate test-case-reviewer-bot

如果环境里没装依赖:

powershell 复制代码
python -m pip install -r requirements-basic.txt

3. PowerShell 禁止执行脚本

运行 smoke test 时报:

text 复制代码
running scripts is disabled on this system

可以临时这样执行:

powershell 复制代码
powershell -ExecutionPolicy Bypass -File .\scripts\basic-smoke.ps1

不建议一上来全局改执行策略,先用临时绕过就够了。

4. ngrok 命令不存在

现象:

text 复制代码
ngrok : 无法将"ngrok"项识别为 cmdlet

说明 ngrok 没安装,或者不在 PATH 里。

解决:

  • 安装 ngrok。
  • 或者用完整路径运行:
powershell 复制代码
C:\path\to\ngrok.exe http 8000

5. ngrok 版本不一致或账号未认证

现象可能是:

text 复制代码
ERR_NGROK_4018
authentication failed

或者:

text 复制代码
unsupported agent version

这通常不是代码问题,而是 ngrok 本身的问题:

  • 新版 ngrok 需要登录账号和 authtoken。
  • 旧版 agent 可能被账号策略拒绝。
  • Windows Store / msix 版本和手动下载版本可能混用。

处理顺序:

  1. 确认版本:
powershell 复制代码
ngrok version
  1. 配置 authtoken:
powershell 复制代码
ngrok config add-authtoken 你的_token
  1. 如果提示 agent 版本不支持,就升级 ngrok。

  2. 如果系统里有多个 ngrok,确认当前用的是哪个:

powershell 复制代码
Get-Command ngrok

6. ngrok 启动了,但看不到公网地址

有时终端显示不完整,Forwarding 那一行不明显。

可以查本地 ngrok API:

powershell 复制代码
(Invoke-RestMethod http://127.0.0.1:4040/api/tunnels).tunnels.public_url

如果能看到:

text 复制代码
https://xxxx.ngrok-free.dev

说明 tunnel 是正常的。

7. /health 不通

先确认服务是否还在跑。

powershell 复制代码
Invoke-RestMethod http://127.0.0.1:8000/health

如果不通,看启动服务的终端有没有报错。

常见原因:

  • 端口不对。
  • 服务启动失败。
  • 当前目录不对。
  • conda 环境不对。

8. smoke test 失败

先看它卡在哪一步。

如果是 compileall 失败,说明代码语法有问题。

如果是 extractor 返回 0,说明示例文件路径或提取规则有问题。

如果是 review comment 没生成,说明 review-patch 链路出问题。

smoke test 的好处就是把问题缩小到本地,不要一开始就怀疑 GitHub。

9. .env 里填了模型配置,但启动很慢

basic 启动本身不应该因为模型慢。

但如果你后面触发 PR review,并开启:

dotenv 复制代码
EMBEDDING_PROVIDER=openai
LLM_REVIEW_ENABLED=true

模型调用可能让 webhook 等很久。

调试阶段建议先保持:

dotenv 复制代码
EMBEDDING_PROVIDER=local
LLM_REVIEW_ENABLED=false

等 GitHub 评论链路跑通后,再打开模型。

总结

这一篇只验证本地服务和本地链路。

暂时不做:

  • 不配置 GitHub webhook。
  • 不发布 PR 评论。
  • 不打开 LLM。

这几个都放后面。先把本地服务跑稳,下一篇再让 GitHub 真的来调用它。

到这里,你应该有三个确认:

text 复制代码
服务能启动
/health 能访问
basic-smoke 能通过

这三个确认完成后,再接 GitHub webhook 就更轻松简单了。

相关推荐
故七月1 小时前
当AI成为决策参谋:GEO的合规之道与成都实践
人工智能
网易云信1 小时前
4小时分享、5场业务实战,网易把AI如何进入真实业务讲明白了
人工智能·agent
IT古董1 小时前
《FDE前沿部署工程师实战教程》01 - FDE是什么:AI时代正在崛起的新型工程师
人工智能
weixin_469273811 小时前
银行流水 PDF 转 Excel 或者 CSV 完整指南
人工智能·pdf·excel
2601_962860151 小时前
AI生成3D模型后还能继续编辑、贴图和绑定吗?从网格、材质到自动绑骨的处理流程
人工智能·3d·贴图
晴天161 小时前
魔搭社区(ModelScope)介绍-Day29
人工智能
全栈技术圈1 小时前
直播复盘效率革命:基于AI的全自动直播回放复盘工具「小智回播」实测
人工智能
YMWM_1 小时前
机器人领域中的期刊和会议
人工智能·机器人
阿里云大数据AI技术1 小时前
技术揭秘:阿里云 Elasticsearch 云原生向量引擎如何登顶 VectorDBBench
人工智能·elasticsearch