ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
"""
|
||||
github_mcp.py —— GitHub Issue 创建(默认 mock,可选真实 MCP)
|
||||
|
||||
- mock(默认):把"创建 Issue"渲染成将要提交的 Issue 结构,打印并写入本地文件,
|
||||
不联网、不需要 token。
|
||||
- 真实(mock=False,需 GITHUB_TOKEN + GITHUB_REPO):通过 MCP 协议连接官方
|
||||
GitHub MCP Server(stdio),调用其 `create_issue` 工具在真实仓库创建 Issue。
|
||||
出于安全,真实创建须由 demo.py 的 --create-issue 显式开启。
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import shlex
|
||||
from datetime import datetime
|
||||
from typing import Dict, Any, List
|
||||
|
||||
_OUT = os.path.join(os.path.dirname(__file__), "output", "github_issues.json")
|
||||
|
||||
# 官方 GitHub MCP Server 的默认启动命令(可用 GITHUB_MCP_COMMAND 覆盖)。
|
||||
# 默认用 Docker 运行官方镜像;也可换成任何暴露 create_issue 工具的 MCP Server。
|
||||
_DEFAULT_MCP_COMMAND = (
|
||||
"docker run -i --rm -e GITHUB_PERSONAL_ACCESS_TOKEN ghcr.io/github/github-mcp-server")
|
||||
|
||||
# 优先级 -> GitHub label 的映射
|
||||
_PRIORITY_LABEL = {"P0": "priority:critical", "P1": "priority:high",
|
||||
"P2": "priority:medium", "P3": "priority:low"}
|
||||
|
||||
|
||||
def build_issue(problem: Dict[str, Any], test_cases: List[Dict[str, Any]]) -> Dict[str, Any]:
|
||||
"""把一条诊断问题 + 关联回归测试用例,渲染成 GitHub Issue 结构。"""
|
||||
prio = problem.get("priority", "P2")
|
||||
module = problem.get("module", "unknown")
|
||||
related = [tc for tc in test_cases
|
||||
if tc.get("trajectory_id") in problem.get("trajectory_ids", [])]
|
||||
|
||||
body_lines = [
|
||||
f"## 问题描述\n{problem.get('description', '')}",
|
||||
f"\n## 涉及模块\n`{module}`",
|
||||
f"\n## 优先级\n{prio}",
|
||||
f"\n## 改进建议\n{problem.get('suggestion', '')}",
|
||||
f"\n## 相关生产轨迹\n" + ", ".join(problem.get("trajectory_ids", []) or ["(无)"]),
|
||||
]
|
||||
if related:
|
||||
body_lines.append("\n## 关联回归测试用例")
|
||||
for tc in related:
|
||||
body_lines.append(
|
||||
f"- `{tc.get('test_id')}` (轨迹 {tc.get('trajectory_id')} "
|
||||
f"第 {tc.get('focus_turn')} 轮): {tc.get('description', '')}")
|
||||
|
||||
return {
|
||||
"title": f"[{prio}][{module}] {problem.get('title', problem.get('description', ''))[:60]}",
|
||||
"body": "\n".join(body_lines),
|
||||
"labels": [f"module:{module}", _PRIORITY_LABEL.get(prio, "priority:medium"),
|
||||
"auto-diagnosis"],
|
||||
"assignees": [problem.get("suggested_assignee", "")] if problem.get("suggested_assignee") else [],
|
||||
}
|
||||
|
||||
|
||||
def create_issues(problems: List[Dict[str, Any]], test_cases: List[Dict[str, Any]],
|
||||
mock: bool = True, out_path: str = _OUT,
|
||||
repo: str = None, token: str = None) -> List[Dict[str, Any]]:
|
||||
"""为每条问题创建 Issue。
|
||||
|
||||
mock=True(默认):打印 + 落盘到 out_path,不联网。
|
||||
mock=False:通过 GitHub MCP Server 在 repo(owner/repo)真实创建 Issue,需 token。
|
||||
"""
|
||||
issues = [build_issue(p, test_cases) for p in problems]
|
||||
|
||||
if mock:
|
||||
os.makedirs(os.path.dirname(out_path) or ".", exist_ok=True)
|
||||
payload = {"created_at": datetime.now().isoformat(),
|
||||
"mode": "mock", "issues": issues}
|
||||
with open(out_path, "w", encoding="utf-8") as f:
|
||||
json.dump(payload, f, ensure_ascii=False, indent=2)
|
||||
print(f"\n[github_mcp:mock] 已将 {len(issues)} 个 Issue 写入 {out_path}")
|
||||
for i, iss in enumerate(issues, 1):
|
||||
print(f"\n----- Mock GitHub Issue #{i} -----")
|
||||
print(f"title : {iss['title']}")
|
||||
print(f"labels : {iss['labels']}")
|
||||
print("body :")
|
||||
for ln in iss["body"].splitlines():
|
||||
print(" " + ln)
|
||||
else:
|
||||
if not token or not repo:
|
||||
raise RuntimeError(
|
||||
"真实创建需 GITHUB_TOKEN 与 GITHUB_REPO(owner/repo),见 README。")
|
||||
created = _create_issues_via_mcp(issues, repo=repo, token=token)
|
||||
print(f"\n[github_mcp] 通过 MCP 在 {repo} 创建了 {len(created)} 个 Issue:")
|
||||
for url in created:
|
||||
print(f" {url}")
|
||||
|
||||
return issues
|
||||
|
||||
|
||||
def _create_issues_via_mcp(issues: List[Dict[str, Any]], repo: str, token: str) -> List[str]:
|
||||
"""通过 stdio 连接官方 GitHub MCP Server,逐个调用 create_issue 工具。
|
||||
|
||||
返回创建成功的 Issue URL 列表。需要已安装 `mcp` Python SDK 与可用的
|
||||
GitHub MCP Server(默认 Docker 镜像,可用 GITHUB_MCP_COMMAND 覆盖启动命令)。
|
||||
"""
|
||||
import asyncio
|
||||
|
||||
try:
|
||||
from mcp import ClientSession, StdioServerParameters
|
||||
from mcp.client.stdio import stdio_client
|
||||
except ImportError as e: # pragma: no cover - 依赖缺失时给出清晰指引
|
||||
raise RuntimeError(
|
||||
"缺少 MCP 客户端:pip install mcp(并确保 GitHub MCP Server 可启动)") from e
|
||||
|
||||
owner, _, name = repo.partition("/")
|
||||
if not owner or not name:
|
||||
raise RuntimeError(f"GITHUB_REPO 需形如 owner/repo,收到:{repo!r}")
|
||||
|
||||
cmd = shlex.split(os.getenv("GITHUB_MCP_COMMAND", _DEFAULT_MCP_COMMAND))
|
||||
params = StdioServerParameters(
|
||||
command=cmd[0], args=cmd[1:],
|
||||
env={**os.environ, "GITHUB_PERSONAL_ACCESS_TOKEN": token})
|
||||
|
||||
async def _run() -> List[str]:
|
||||
urls: List[str] = []
|
||||
async with stdio_client(params) as (read, write):
|
||||
async with ClientSession(read, write) as session:
|
||||
await session.initialize()
|
||||
for iss in issues:
|
||||
result = await session.call_tool("create_issue", {
|
||||
"owner": owner, "repo": name,
|
||||
"title": iss["title"], "body": iss["body"],
|
||||
"labels": iss["labels"],
|
||||
"assignees": iss["assignees"],
|
||||
})
|
||||
# MCP 工具返回文本内容;尽力提取 Issue URL,否则回退为原始文本。
|
||||
text = "".join(getattr(c, "text", "") for c in result.content)
|
||||
url = text
|
||||
try:
|
||||
url = json.loads(text).get("html_url", text)
|
||||
except Exception:
|
||||
pass
|
||||
urls.append(url)
|
||||
return urls
|
||||
|
||||
return asyncio.run(_run())
|
||||
Reference in New Issue
Block a user