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,219 @@
|
||||
"""实验 5-11:对话式界面定制系统 —— FastAPI 后端。
|
||||
|
||||
一个最小的 chatbot 后端:前端把用户消息 POST 到 /api/chat,后端返回回复。
|
||||
开发模式下用 `uvicorn main:app --reload` 启动,改动后端代码会自动 reload
|
||||
(对应书中所说的"FastAPI 的热加载")。
|
||||
|
||||
两种回复模式(默认保持"回声式"占位,聚焦 UI 定制这一主题):
|
||||
- **echo(默认)**:后端把用户消息原样回显,无需任何模型 Key,开箱即用;
|
||||
- **llm(可选)**:设置模型后走真实 LLM 对话,让运行起来的 chatbot 真会说话。
|
||||
通过命令行 `--model` 或环境变量 `CHAT_MODEL` 打开,复用与 agent.py 相同的
|
||||
OPENAI_API_KEY / OPENAI_BASE_URL 配置。
|
||||
|
||||
启动方式(二选一,行为一致):
|
||||
uvicorn main:app --reload --port 8000 # 书中示例:模块级 app + uvicorn 热加载
|
||||
python main.py --reload --port 8000 # 本文件自带的命令行入口(见 --help)
|
||||
"""
|
||||
|
||||
import os
|
||||
import argparse
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from pydantic import BaseModel
|
||||
|
||||
try: # dotenv 可选:让 --model 模式也能读到 .env 里的 OPENAI_API_KEY
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
app = FastAPI(title="Conversational UI Backend")
|
||||
|
||||
# 允许前端(Vite dev server, 5173)直接跨域访问,方便本地开发。
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
|
||||
class ChatRequest(BaseModel):
|
||||
message: str
|
||||
|
||||
|
||||
def _chat_model() -> str:
|
||||
"""当前回复模式:返回模型名则走真实 LLM,返回空串则为默认 echo 模式。
|
||||
|
||||
从环境变量 CHAT_MODEL 读取(而非模块级常量),这样即使 `--reload` 触发的
|
||||
子进程重新 import 本模块,也能透过环境变量拿到命令行传入的模型设置。
|
||||
"""
|
||||
return (os.getenv("CHAT_MODEL") or "").strip()
|
||||
|
||||
|
||||
def _llm_reply(message: str, model: str) -> str:
|
||||
"""走真实 LLM 生成回复,复用 agent.py 同款 OPENAI_* 配置。
|
||||
|
||||
任何异常都降级为清晰的提示(绝不编造回复),保证前端不至于白屏。
|
||||
"""
|
||||
try:
|
||||
from openai import OpenAI
|
||||
except Exception:
|
||||
return "(未安装 openai 依赖,无法启用 LLM 模式;已回退占位回复)"
|
||||
|
||||
api_key = os.getenv("OPENAI_API_KEY")
|
||||
base_url = os.getenv("OPENAI_BASE_URL")
|
||||
orkey = os.getenv("OPENROUTER_API_KEY")
|
||||
# 通用 OpenRouter 兜底:无直连 key,或 gpt-5.x(直连需组织实名认证)时改走 OpenRouter。
|
||||
prefer_or = bool(orkey) and (model or "").lower().startswith("gpt-5")
|
||||
if prefer_or or (not api_key and orkey):
|
||||
api_key, base_url = orkey, "https://openrouter.ai/api/v1"
|
||||
if "/" not in model:
|
||||
model = ("openai/" + model) if model.lower().startswith(("gpt-", "o1", "o3", "o4")) else "openai/gpt-5.6-luna"
|
||||
if not api_key:
|
||||
return "(未配置 OPENAI_API_KEY 或 OPENROUTER_API_KEY,无法启用 LLM 模式;已回退占位回复)"
|
||||
|
||||
client_kwargs = {"api_key": api_key, "timeout": 60.0, "max_retries": 2}
|
||||
if base_url:
|
||||
client_kwargs["base_url"] = base_url
|
||||
|
||||
try:
|
||||
client = OpenAI(**client_kwargs)
|
||||
resp = client.chat.completions.create(
|
||||
model=model,
|
||||
messages=[
|
||||
{"role": "system", "content": "你是一个乐于助人的中文智能助手,回答简洁友好。"},
|
||||
{"role": "user", "content": message},
|
||||
],
|
||||
temperature=(1 if any(k in (model or "").lower()
|
||||
for k in ("gpt-5", "o1", "o3", "o4", "thinking", "reasoner", "kimi-k3"))
|
||||
else 0.7),
|
||||
)
|
||||
return resp.choices[0].message.content or "(模型返回了空回复)"
|
||||
except Exception as e: # 网络/鉴权/模型名等问题都在此兜底
|
||||
return f"(调用 LLM 失败:{e};已回退占位回复)"
|
||||
|
||||
|
||||
@app.get("/api/health")
|
||||
def health():
|
||||
model = _chat_model()
|
||||
return {"status": "ok", "mode": "llm" if model else "echo", "model": model or None}
|
||||
|
||||
|
||||
@app.post("/api/chat")
|
||||
def chat(req: ChatRequest):
|
||||
"""默认回声式回复;设置 CHAT_MODEL 后走真实 LLM 对话。
|
||||
|
||||
本实验聚焦"对话式 UI 定制",后端逻辑刻意保持最小;
|
||||
如需真实客服体验,用 `python main.py --model <模型名>` 打开 LLM 模式即可。
|
||||
"""
|
||||
model = _chat_model()
|
||||
if model:
|
||||
return {"reply": _llm_reply(req.message, model)}
|
||||
return {"reply": f"我收到了你的消息:{req.message}"}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 命令行入口:让后端既能 `uvicorn main:app --reload` 启动,也能 `python main.py`
|
||||
# 启动,并通过参数控制 host/port/热加载/回复模式/日志。
|
||||
# ---------------------------------------------------------------------------
|
||||
def parse_args(argv=None):
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="main.py",
|
||||
description="实验 5-11:对话式界面定制系统 —— FastAPI 后端(最小 chatbot 服务)。"
|
||||
"为可对话定制的前端提供 /api/chat 载体,开发模式下配合 --reload 演示后端热加载。",
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--host",
|
||||
default="127.0.0.1",
|
||||
help="监听地址;对外可用 0.0.0.0。",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--port",
|
||||
type=int,
|
||||
default=8000,
|
||||
help="监听端口(前端 vite.config.js 默认把 /api 代理到 8000)。",
|
||||
)
|
||||
reload_group = parser.add_mutually_exclusive_group()
|
||||
reload_group.add_argument(
|
||||
"--reload",
|
||||
dest="reload",
|
||||
action="store_true",
|
||||
default=True,
|
||||
help="开启热加载:改动后端 .py 自动重启(开发默认开启)。",
|
||||
)
|
||||
reload_group.add_argument(
|
||||
"--no-reload",
|
||||
dest="reload",
|
||||
action="store_false",
|
||||
help="关闭热加载(更接近生产运行)。",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--model",
|
||||
default=os.getenv("CHAT_MODEL") or None,
|
||||
metavar="NAME",
|
||||
help="打开真实 LLM 对话并指定模型名(如 gpt-5.6-luna);"
|
||||
"缺省则为默认的 echo 回声模式。也可用环境变量 CHAT_MODEL 设置。",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--log-level",
|
||||
default="info",
|
||||
choices=["critical", "error", "warning", "info", "debug", "trace"],
|
||||
help="uvicorn 日志/输出级别。",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--print-config",
|
||||
action="store_true",
|
||||
help="只打印生效配置(JSON)后退出,不真正监听端口(便于无网络/无端口环境下校验)。",
|
||||
)
|
||||
return parser.parse_args(argv)
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
import json
|
||||
|
||||
args = parse_args(argv)
|
||||
|
||||
# 把 --model 写回环境变量:这样 --reload 派生的子进程重新 import 本模块时,
|
||||
# 也能通过 CHAT_MODEL 感知到 LLM 模式(子进程不共享本函数的局部状态)。
|
||||
if args.model:
|
||||
os.environ["CHAT_MODEL"] = args.model
|
||||
else:
|
||||
os.environ.pop("CHAT_MODEL", None)
|
||||
|
||||
config = {
|
||||
"host": args.host,
|
||||
"port": args.port,
|
||||
"reload": args.reload,
|
||||
"mode": "llm" if args.model else "echo",
|
||||
"model": args.model,
|
||||
"log_level": args.log_level,
|
||||
}
|
||||
|
||||
if args.print_config:
|
||||
print(json.dumps(config, ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
|
||||
import uvicorn
|
||||
|
||||
print(
|
||||
f"启动 FastAPI 后端:http://{args.host}:{args.port}"
|
||||
f" 模式={config['mode']}"
|
||||
f" 热加载={'开' if args.reload else '关'}"
|
||||
)
|
||||
# 用 import string 才能在 --reload 下工作;从 backend/ 目录运行 `python main.py`。
|
||||
uvicorn.run(
|
||||
"main:app",
|
||||
host=args.host,
|
||||
port=args.port,
|
||||
reload=args.reload,
|
||||
log_level=args.log_level,
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,6 @@
|
||||
# 后端单独运行所需依赖(与根目录 requirements.txt 一致的子集)
|
||||
fastapi>=0.110
|
||||
uvicorn[standard]>=0.27
|
||||
# 以下仅在用 `--model` 打开真实 LLM 对话模式时才需要;默认 echo 模式无需安装:
|
||||
# openai>=1.30
|
||||
# python-dotenv>=1.0
|
||||
Reference in New Issue
Block a user