Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 44c5d0237e | |||
| 0aebf74366 | |||
| 1852c16fe1 | |||
| 87f0b858ed | |||
| 5e9f4e04ed | |||
| 313e317e99 |
@@ -1,6 +1,5 @@
|
|||||||
name: "AI Code Review"
|
name: "AI Code Review (Skill-Driven)"
|
||||||
|
|
||||||
# 当有人发起 Pull Request (PR) 或者向 PR 提交新代码时,触发此工作流
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, synchronize]
|
types: [opened, synchronize]
|
||||||
@@ -9,7 +8,7 @@ jobs:
|
|||||||
review_with_skill:
|
review_with_skill:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
# 1. 把你仓库里的代码(包括 .claude 里的规则文件)下载到运行服务器上
|
# 1. 拉取代码及 Skill 规则文件
|
||||||
- name: Checkout Code & Skills
|
- name: Checkout Code & Skills
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
@@ -18,75 +17,86 @@ jobs:
|
|||||||
# 2. 运行 AI 审查脚本
|
# 2. 运行 AI 审查脚本
|
||||||
- name: Run Review Engine
|
- name: Run Review Engine
|
||||||
env:
|
env:
|
||||||
# 引入我们在第二步配置的 API Key
|
# 🔑 中转站 API Key(在 Gitea Secrets 配置)
|
||||||
API_KEY: ${{ secrets.LLM_API_KEY }}
|
API_KEY: ${{ secrets.LLM_API_KEY }}
|
||||||
# Gitea 自动生成的 Token,用于给机器人发评论权限,无需手动配置
|
# 🌐 中转站 Base URL(在 Gitea Secrets 配置)
|
||||||
GITEA_TOKEN: ${{ gitea.token }}
|
BASE_URL: ${{ secrets.LLM_BASE_URL }}
|
||||||
# 传递 Gitea 的相关环境变量给 Python 脚本
|
# 🤖 选择的模型名称(按需修改)
|
||||||
|
MODEL_NAME: "gpt-4o"
|
||||||
|
# Gitea 自动注入的 Token,无需手动配置
|
||||||
|
GITEA_TOKEN: ${{ gitea.token }}
|
||||||
GITEA_SERVER_URL: ${{ gitea.server_url }}
|
GITEA_SERVER_URL: ${{ gitea.server_url }}
|
||||||
GITEA_REPOSITORY: ${{ gitea.repository }}
|
GITEA_REPOSITORY: ${{ gitea.repository }}
|
||||||
GITEA_PR_NUMBER: ${{ gitea.event.number }}
|
GITEA_PR_NUMBER: ${{ gitea.event.number }}
|
||||||
GITEA_BASE_REF: ${{ gitea.base_ref }}
|
GITEA_BASE_REF: ${{ gitea.base_ref }}
|
||||||
GITEA_HEAD_REF: ${{ gitea.head_ref }}
|
GITEA_HEAD_REF: ${{ gitea.head_ref }}
|
||||||
run: |
|
run: |
|
||||||
# 提取当前 PR 的代码变动并保存到 diff.txt
|
# 提取当前 PR 的代码差异
|
||||||
git diff origin/$GITEA_BASE_REF...origin/$GITEA_HEAD_REF > diff.txt
|
git diff origin/$GITEA_BASE_REF...origin/$GITEA_HEAD_REF > diff.txt
|
||||||
|
|
||||||
# 使用 Python 执行审查
|
# 使用 Python 执行审查(纯标准库,无需 pip install)
|
||||||
python3 -c "
|
python3 -c "
|
||||||
import os, json, urllib.request, urllib.error
|
import os, json, urllib.request, urllib.error
|
||||||
|
|
||||||
# 1. 读取你提交的 Skill 规则书
|
# ========== 1. 读取 Skill 规则书 ==========
|
||||||
skill_base_path = '.claude/skills/code-review-skill/'
|
skill_base_path = '.claude/skills/code-review-skill/'
|
||||||
try:
|
try:
|
||||||
skill_config = open(os.path.join(skill_base_path, 'SKILL.md')).read()
|
skill_config = open(os.path.join(skill_base_path, 'SKILL.md'), encoding='utf-8').read()
|
||||||
python_rules = open(os.path.join(skill_base_path, 'reference/python.md')).read()
|
python_rules = open(os.path.join(skill_base_path, 'reference/python.md'), encoding='utf-8').read()
|
||||||
except FileNotFoundError as e:
|
except FileNotFoundError as e:
|
||||||
print(f'未找到规则文件: {e}'); exit(1)
|
print(f'❌ 未找到 Skill 规则文件: {e}'); exit(1)
|
||||||
|
|
||||||
# 组合成完整的 AI 提示词(System Prompt)
|
|
||||||
full_skill_prompt = f'{skill_config}\n\n以下是针对当前语言的补充规范:\n{python_rules}'
|
full_skill_prompt = f'{skill_config}\n\n以下是针对当前语言的补充规范:\n{python_rules}'
|
||||||
|
|
||||||
# 读取代码变动
|
# ========== 2. 读取代码变动 ==========
|
||||||
diff_data = open('diff.txt').read()
|
diff_data = open('diff.txt', encoding='utf-8').read()
|
||||||
if not diff_data.strip():
|
if not diff_data.strip():
|
||||||
print('没有检测到代码变更,跳过审查。'); exit(0)
|
print('✅ 没有检测到代码变更,跳过审查。'); exit(0)
|
||||||
|
|
||||||
# 2. 呼叫大模型 (这里以 DeepSeek 为例,已修正为官方标准 API 接口)
|
# ========== 3. 调用中转站大模型 API ==========
|
||||||
api_url = 'https://api.deepseek.com/v1/chat/completions'
|
base_url = os.getenv('BASE_URL', 'https://api.maxmodel.com/v1').rstrip('/')
|
||||||
|
api_url = f'{base_url}/chat/completions'
|
||||||
|
model_name = os.getenv('MODEL_NAME', 'gpt-4o')
|
||||||
|
|
||||||
|
print(f'🚀 正在调用模型: {model_name} @ {base_url}')
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
'Authorization': f'Bearer {os.getenv(\"API_KEY\")}',
|
'Authorization': f'Bearer {os.getenv(\"API_KEY\")}',
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
}
|
}
|
||||||
payload = {
|
payload = {
|
||||||
'model': 'deepseek-chat', # 或者使用 deepseek-coder
|
'model': model_name,
|
||||||
'messages': [
|
'messages': [
|
||||||
{'role': 'system', 'content': full_skill_prompt},
|
{'role': 'system', 'content': full_skill_prompt},
|
||||||
{'role': 'user', 'content': f'请严格对照上述 Skill 规范,评审以下 FastAPI 代码改动,并用中文输出评审意见:\n\n{diff_data}'}
|
{'role': 'user', 'content': f'请严格对照上述 Skill 规范,评审以下 FastAPI 代码改动,并用中文输出评审意见:\n\n{diff_data}'}
|
||||||
],
|
],
|
||||||
'temperature': 0.2
|
'temperature': 0.2
|
||||||
}
|
}
|
||||||
|
|
||||||
req = urllib.request.Request(api_url, data=json.dumps(payload).encode('utf-8'), headers=headers, method='POST')
|
req = urllib.request.Request(api_url, data=json.dumps(payload).encode('utf-8'), headers=headers, method='POST')
|
||||||
try:
|
try:
|
||||||
with urllib.request.urlopen(req) as response:
|
with urllib.request.urlopen(req, timeout=120) as response:
|
||||||
res = json.loads(response.read().decode('utf-8'))
|
res = json.loads(response.read().decode('utf-8'))
|
||||||
comment_body = res['choices'][0]['message']['content']
|
comment_body = res['choices'][0]['message']['content']
|
||||||
|
print('✅ 大模型已返回审查意见')
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
error_body = e.read().decode('utf-8')
|
||||||
|
print(f'❌ 请求中转站失败 [{e.code}]: {error_body}'); exit(1)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'请求大模型失败: {e}'); exit(1)
|
print(f'❌ 请求中转站异常: {e}'); exit(1)
|
||||||
|
|
||||||
# 3. 将 AI 审查意见发表到 Gitea 的 Pull Request 评论区
|
# ========== 4. 发表到 Gitea PR 评论区 ==========
|
||||||
gitea_url = f'{os.getenv(\"GITEA_SERVER_URL\")}/api/v1/repos/{os.getenv(\"GITEA_REPOSITORY\")}/issues/{os.getenv(\"GITEA_PR_NUMBER\")}/comments'
|
gitea_url = f'{os.getenv(\"GITEA_SERVER_URL\")}/api/v1/repos/{os.getenv(\"GITEA_REPOSITORY\")}/issues/{os.getenv(\"GITEA_PR_NUMBER\")}/comments'
|
||||||
gitea_headers = {
|
gitea_headers = {
|
||||||
'Authorization': f'token {os.getenv(\"GITEA_TOKEN\")}',
|
'Authorization': f'token {os.getenv(\"GITEA_TOKEN\")}',
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
}
|
}
|
||||||
gitea_payload = {'body': f'### 🤖 AI 自动化审查报告 (基于 Code Review Skill)\n\n{comment_body}'}
|
gitea_payload = {'body': f'### 🤖 AI 自动化审查报告 (基于 Code Review Skill)\n\n> 模型: \`{model_name}\` | 中转站: maxmodel\n\n{comment_body}'}
|
||||||
|
|
||||||
gitea_req = urllib.request.Request(gitea_url, data=json.dumps(gitea_payload).encode('utf-8'), headers=gitea_headers, method='POST')
|
gitea_req = urllib.request.Request(gitea_url, data=json.dumps(gitea_payload).encode('utf-8'), headers=gitea_headers, method='POST')
|
||||||
try:
|
try:
|
||||||
urllib.request.urlopen(gitea_req)
|
urllib.request.urlopen(gitea_req, timeout=30)
|
||||||
print('Skill 审查报告已成功发表!')
|
print('🎉 Skill 审查报告已成功发表到 PR!')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'发表评论失败: {e}'); exit(1)
|
print(f'❌ 发表评论失败: {e}'); exit(1)
|
||||||
"
|
"
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
# app/main.py
|
||||||
|
import time
|
||||||
|
import requests # ❌ 引入了同步的请求库
|
||||||
|
from fastapi import FastAPI
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
# 内存数据库模拟
|
||||||
|
DATABASE = []
|
||||||
|
|
||||||
|
# ❌ 严重问题 1: 硬编码敏感信息 (有安全隐患)
|
||||||
|
SECRET_KEY = "super-secret-key-123456"
|
||||||
|
|
||||||
|
|
||||||
|
# ❌ 严重问题 2: 在 async 异步函数中,使用了同步阻塞操作 (time.sleep 和 requests.get)
|
||||||
|
# 这会导致 FastAPI 的单线程事件循环被完全卡死!高并发时性能会雪崩 [1][3]。
|
||||||
|
@app.get("/fetch-data")
|
||||||
|
async def fetch_external_data():
|
||||||
|
time.sleep(2) # 模拟耗时操作,卡死事件循环 [3]
|
||||||
|
response = requests.get("https://api.github.com/repos/tiangolo/fastapi") # 阻塞 I/O [1]
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
|
||||||
|
# ❌ 严重问题 3: 没有任何数据校验
|
||||||
|
# 客户端如果少传了 name 或者 price 字段,直接报 KeyError 500 内部错误。
|
||||||
|
@app.post("/items")
|
||||||
|
async def create_item(item: dict): # ❌ 应该使用 Pydantic BaseModel [1][3]
|
||||||
|
item_name = item["name"]
|
||||||
|
item_price = item["price"]
|
||||||
|
|
||||||
|
new_item = {"name": item_name, "price": item_price}
|
||||||
|
DATABASE.append(new_item)
|
||||||
|
return {"status": "success", "data": new_item}
|
||||||
|
|
||||||
|
# TODO: 这是一个测试 AI 审查的临时注释
|
||||||
|
# TODO: 这是一个测试 AI 审查的临时注释
|
||||||
|
return {"message": "Hello, World!"} # ❌ 这行代码永远不会被执行,应该删除
|
||||||
|
|||||||
Reference in New Issue
Block a user