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

This commit is contained in:
2026-08-20 13:12:50 +00:00
commit b119135836
10275 changed files with 3284984 additions and 0 deletions
+141
View File
@@ -0,0 +1,141 @@
"""
github_mcp.py —— GitHub Issue 创建(默认 mock,可选真实 MCP)
- mock(默认):把"创建 Issue"渲染成将要提交的 Issue 结构,打印并写入本地文件,
不联网、不需要 token。
- 真实(mock=False,需 GITHUB_TOKEN + GITHUB_REPO):通过 MCP 协议连接官方
GitHub MCP Serverstdio),调用其 `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 在 repoowner/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())