前四课已经完成了核心能力。现在把它打包成一个完整的 Web 应用。
1. 最终架构
```text ┌──────────────────────────┐ │ Web 界面 │ │ HTML + CSS + JS │ └──────────────┬───────────┘ │ HTTP 请求 ▼ ┌──────────────────────────┐ │ Flask 服务器 │ │ Python 后端 │ └──────────────┬───────────┘ │ 调用 ▼ ┌──────────────────────────┐ │ LangChain Agent │ │ 工具 + LLM + 记忆 │ └──────────────────────────┘ ```
2. 项目结构
```text lesson05/ ├── app.py ├── agent.py ├── templates/ │ └── index.html ├── static/ │ ├── style.css │ ├── app_demo.png │ └── chat_demo.png ├── run.sh └── requirements.txt ```
3. Agent 模块
```python import os import base64 import hashlib import hmac import time from datetime import datetime from urllib import parse
import requests from langchain_openai import ChatOpenAI from langchain_core.tools import tool from langchain.agents import create_agent from langgraph.checkpoint.memory import InMemorySaver
SENIVERSE_UID = os.getenv("SENIVERSE_UID", "") SENIVERSE_KEY = os.getenv("SENIVERSE_KEY", "") SENIVERSE_API_URL = "https://api.seniverse.com/v3/weather/now.json"
@tool def get_weather(city: str) -> str: """查询城市实时天气""" try: ts = int(time.time()) params_str = f"ts={ts}&uid={SENIVERSE_UID}" key = bytes(SENIVERSE_KEY, "UTF-8") raw = bytes(params_str, "UTF-8") digester = hmac.new(key, raw, hashlib.sha1).digest() sig = parse.quote(base64.b64encode(digester).decode("utf8")) url = ( f"{SENIVERSE_API_URL}?location={parse.quote(city)}" f"&{params_str}&sig={sig}" )
data = requests.get(url, timeout=5).json()
if "results" not in data:
return f"未找到 {city} 的天气信息"
result = data["results"][0]
now = result["now"]
return f"{result['location']['name']}:{now['text']},温度 {now['temperature']}°C"
except Exception as e:
return f"获取天气失败: {str(e)}"
@tool def get_current_time() -> str: """获取当前时间""" return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
@tool def search_attractions(city: str) -> str: """搜索城市的热门景点""" attractions = { "北京": "故宫、长城、颐和园、天坛、南锣鼓巷", "上海": "外滩、东方明珠、豫园、南京路、迪士尼", "杭州": "西湖、灵隐寺、宋城、西溪湿地、千岛湖", "成都": "宽窄巷子、锦里、大熊猫基地、都江堰、青城山", } return attractions.get(city, f"{city} 暂无景点信息")
@tool def estimate_travel_time(from_city: str, to_city: str) -> str: """估算两个城市之间的交通时间""" times = { ("北京", "上海"): "高铁约 4.5 小时,飞机约 2 小时", ("上海", "杭州"): "高铁约 1 小时,自驾约 2 小时", ("北京", "杭州"): "高铁约 5 小时,飞机约 2 小时", ("成都", "北京"): "高铁约 8 小时,飞机约 2.5 小时", } key = (from_city, to_city) reverse_key = (to_city, from_city) return times.get(key) or times.get(reverse_key) or f"{from_city} 到 {to_city} 的交通信息暂无"
def create_travel_agent(): llm = ChatOpenAI( temperature=0.7, model="glm-4.7", openai_api_key=os.getenv("ZHIPU_API_KEY"), openai_api_base="https://open.bigmodel.cn/api/paas/v4/", )
memory = InMemorySaver()
tools = [get_weather, get_current_time, search_attractions, estimate_travel_time]
return create_agent(llm, tools, checkpointer=memory)
```
4. Flask 后端
```python import uuid from flask import Flask, jsonify, render_template, request
from agent import create_travel_agent
app = Flask(name) agent = create_travel_agent()
@app.route("/") def index(): return render_template("index.html")
@app.route("/chat", methods=["POST"]) def chat(): try: data = request.json or {} user_message = data.get("message", "") thread_id = data.get("thread_id", str(uuid.uuid4()))
if not user_message:
return jsonify({"error": "消息不能为空"}), 400
config = {"configurable": {"thread_id": thread_id}}
result = agent.invoke(
{"messages": [{"role": "user", "content": user_message}]},
config,
)
tool_calls = []
for msg in result["messages"]:
if type(msg).__name__ == "ToolMessage":
tool_calls.append({
"tool": msg.name,
"result": msg.content[:100] + "..." if len(msg.content) > 100 else msg.content,
})
return jsonify({
"response": result["messages"][-1].content,
"thread_id": thread_id,
"tool_calls": tool_calls,
})
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route("/new_chat", methods=["POST"]) def new_chat(): return jsonify({"thread_id": str(uuid.uuid4())})
if name == "main": app.run(debug=True, port=8080, host="0.0.0.0") ```
5. 前端页面
前端可以保持简单:一个消息列表、一个输入框、一个发送按钮、一个工具调用记录区域。
```html
基于 LangChain Agent 的智能旅行规划
<div class="chat-container">
<div class="messages" id="messages"></div>
<div class="input-area">
<input id="user-input" placeholder="例如:帮我规划一个北京到上海的三日游" />
<button id="send-btn">发送</button>
</div>
</div>
6. 启动方式
```sh cd course/lesson05 ./run.sh ```
然后访问:
```text http://localhost:8080 ```
7. 最终功能清单
- 实时天气查询
- 城市景点推荐
- 出行交通时间估算
- 多轮记忆对话
- 工具调用过程可视化
- Web 端交互体验
完整项目建议
如果你要把这套教程继续往下扩展,可以按这个顺序演进:
- 把天气、交通、酒店都改成真实 API
- 增加预算约束,比如“3000 元以内”
- 增加日期规划,比如“生成 3 天详细行程”
- 把 `InMemorySaver` 替换成数据库持久化
- 给前端加用户会话列表和历史记录
学完后你应该掌握什么
- 能独立创建 LangChain Agent
- 能给 Agent 添加真实世界工具
- 能实现多轮记忆对话
- 能把复杂需求拆成多工具工作流
- 能把 Agent 封装成完整 Web 应用
这个教程和 Claude Code 那条路线不一样。前者偏 Agent 产品开发,后者偏 Coding Agent 架构拆解。放在同一个站点里,正好能形成两条互补的学习路径。