Files
ai-agent-book/chapter5/conversational-ui/demo.py
T
liqiang b119135836
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
ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
2026-08-20 13:12:50 +00:00

241 lines
8.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""实验 5-11:对话式界面定制系统 —— 端到端演示与自动验证。
本 demo 在"无浏览器"的环境里,把可验证性落在【自然语言 → 代码修改被正确应用】的闭环上:
对每一条自然语言定制需求:
1) 调用真实 OpenAI,让 Agent 定位并改写前端源码(agent.customize);
2) 打印改动前后的 diff 片段(difflib),并把改动写回 frontend/src
3) 读回源码做断言,确认改动"确实符合需求"(颜色值/字体/文案按要求变化);
4) 运行 `npm run build`vite build),确认改动没有破坏应用(可编译通过)。
连续跑多轮(本例 3 轮),验证多轮迭代定制均生效且不破坏构建。
注意:真正"浏览器内 HMR 的视觉即时刷新"需手动 `npm run dev` + 打开浏览器查看;
本 demo 自动验证的是"代码修改被正确应用,且构建始终通过"。
运行:
python demo.py # 跑全部 3 轮定制并做完整验证
python demo.py --quick # 只跑第 1 轮(省时,用于快速冒烟)
python demo.py --rounds 2 # 只跑前 2 轮
python demo.py --no-build # 跳过 vite build(仅验证"改动被正确应用")
python demo.py -h # 查看全部参数
"""
import sys
import shutil
import difflib
import argparse
import subprocess
from pathlib import Path
import agent
HERE = Path(__file__).resolve().parent
FRONTEND = HERE / "frontend"
BASELINE = HERE / "baseline" # 前端源码的初始快照,保证 demo 可重复运行
# ---------------------------------------------------------------------------
# 每一轮的定制需求 + 对应的验证函数。
# verify(sources) 接收 {相对路径: 改写后内容},返回 (是否通过, 说明)。
# ---------------------------------------------------------------------------
def _all_text(sources: dict) -> str:
return "\n".join(sources.values())
ROUNDS = [
{
"requirement": "把发送按钮和用户消息气泡的主题色从绿色改成蓝色,用 #2563eb 这个蓝。",
"verify": lambda s: (
"#2563eb" in _all_text(s).lower().replace("#2563EB".lower(), "#2563eb"),
"源码中出现蓝色值 #2563eb",
),
},
{
"requirement": "把整个界面的字体换成等宽字体(monospace)。",
"verify": lambda s: (
"monospace" in _all_text(s).lower(),
"源码中出现 monospace 等宽字体",
),
},
{
"requirement": "把顶部的标题文案改成“我的专属客服”。",
"verify": lambda s: (
"我的专属客服" in _all_text(s),
"源码中出现新标题文案“我的专属客服”",
),
},
]
def restore_baseline():
"""把 frontend/src 下的可编辑文件恢复为初始快照,保证可重复运行。"""
for rel in agent.EDITABLE_FILES:
src = BASELINE / rel
dst = FRONTEND / rel
dst.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(src, dst)
def ensure_node_modules():
"""确保依赖已安装;首次 npm install 较慢属正常现象。"""
if (FRONTEND / "node_modules").exists():
return
print(">> 未发现 node_modules,正在执行 npm install(首次较慢,请耐心等待)…")
r = subprocess.run(["npm", "install"], cwd=FRONTEND)
if r.returncode != 0:
raise SystemExit("npm install 失败,请检查 Node/npm 环境。")
def run_build() -> bool:
"""运行 vite build,返回是否编译通过。"""
r = subprocess.run(
["npm", "run", "build"],
cwd=FRONTEND,
capture_output=True,
text=True,
)
tail = (r.stdout + r.stderr).strip().splitlines()
for line in tail[-6:]:
print(" | " + line)
return r.returncode == 0
def print_diff(rel: str, old: str, new: str):
"""打印一个文件改动前后的 unified diff 片段(最多若干行)。"""
diff = list(
difflib.unified_diff(
old.splitlines(),
new.splitlines(),
fromfile=f"a/{rel}",
tofile=f"b/{rel}",
lineterm="",
)
)
if not diff:
print(f" {rel} 无变化)")
return
shown = 0
for line in diff:
if line.startswith("+++") or line.startswith("---") or line.startswith("@@"):
print(" " + line)
elif line.startswith("+"):
print(" \033[32m" + line + "\033[0m") # 绿:新增
shown += 1
elif line.startswith("-"):
print(" \033[31m" + line + "\033[0m") # 红:删除
shown += 1
else:
continue # 省略上下文行,只看真正改动
if shown >= 20:
print(" …(diff 片段已截断)")
break
def parse_args(argv=None):
"""解析命令行参数:控制跑几轮、是否跳过构建。"""
parser = argparse.ArgumentParser(
description="实验 5-11:对话式界面定制系统 —— NL → 代码修改 闭环验证。"
"对每条自然语言 UI 定制需求,让 Agent 改写前端源码,"
"并断言改动生效、vite build 不被破坏。",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument(
"--quick",
action="store_true",
help="快速模式:只跑第 1 轮定制(等价于 --rounds 1)。",
)
parser.add_argument(
"--rounds",
type=int,
default=None,
metavar="N",
help=f"只跑前 N 轮定制(1..{len(ROUNDS)});默认跑全部。",
)
parser.add_argument(
"--no-build",
action="store_true",
help="跳过 vite build,仅验证'改动被正确应用'(更快,但不校验构建)。",
)
return parser.parse_args(argv)
def main(argv=None):
args = parse_args(argv)
# 决定本次运行的轮次数:--quick 优先,其次 --rounds,默认全部。
limit = len(ROUNDS)
if args.quick:
limit = 1
elif args.rounds is not None:
limit = max(1, min(args.rounds, len(ROUNDS)))
rounds = ROUNDS[:limit]
do_build = not args.no_build
print("=" * 72)
print("实验 5-11:对话式界面定制系统 —— NL → 代码修改 闭环验证")
print("=" * 72)
client, model = agent.build_client_and_model()
print(f"模型: {model}")
print(f"本次运行轮次: {limit}/{len(ROUNDS)} 构建验证: {'开启' if do_build else '关闭'}")
restore_baseline()
if do_build:
ensure_node_modules()
print("\n>> 基线构建校验(未定制前,确保应用本身可编译)…")
if not run_build():
raise SystemExit("基线构建失败,请先修复前端工程。")
print(" 基线构建:通过 ✅")
all_pass = True
for i, round_def in enumerate(rounds, 1):
req = round_def["requirement"]
print("\n" + "-" * 72)
print(f"第 {i} 轮 NL 定制需求:{req}")
print("-" * 72)
old_sources = agent.read_editable_sources(FRONTEND)
# 1) 调用 Agent(真实 OpenAI)得到改写方案
result = agent.customize(client, model, FRONTEND, req)
print(f"Agent 说明:{result.get('summary', '(无)')}")
# 2) 写回 + 打印 diff
changed = {}
for f in result["files"]:
rel = f["path"]
new_content = f["content"]
print(f"\n[改动文件] {rel}")
print_diff(rel, old_sources[rel], new_content)
(FRONTEND / rel).write_text(new_content, encoding="utf-8")
changed[rel] = new_content
# 3) 读回源码断言"改动符合需求"
current = agent.read_editable_sources(FRONTEND)
ok, desc = round_def["verify"](current)
print(f"\n断言:{desc} -> {'通过 ✅' if ok else '失败 ❌'}")
if not ok:
all_pass = False
# 4) 构建验证"没破坏应用"--no-build 时跳过)
if do_build:
print("构建验证(vite build):")
build_ok = run_build()
print(f" 构建结果:{'通过 ✅' if build_ok else '失败 ❌'}")
if not build_ok:
all_pass = False
else:
print("构建验证(vite build):已跳过(--no-build")
print("\n" + "=" * 72)
print(f"多轮定制总结:{'全部通过 ✅' if all_pass else '存在失败项 ❌'}")
print("提示:手动 `npm run dev` + 打开 http://localhost:5173 可看到 HMR 视觉即时生效。")
print("=" * 72)
return 0 if all_pass else 1
if __name__ == "__main__":
sys.exit(main())