第 7 章 · 综合实战:智粮系统智能助手
本章目标:把前面所有技术(Prompt + 结构化输出 + RAG + Agent)串成一个完整可运行的应用。做完它,你就完成了"从零到一"的闭环——这也是你转型的"代表作"雏形。
7.1 我们要做什么
一个「智粮系统智能助手」,用户用自然语言就能干三件事:
- 查数据:问"玉米库存多少",它去查数据库。
- 问知识:问"玉米含水率超标怎么折算",它从企业知识库(RAG)找答案。
- 算结果:问"5000 公斤玉米含水率 14% 折成 12% 是多少",它调用折算工具算给你。
核心设计:不写死 if-else 分流,而是把这三件事都注册成"工具",交给 Agent 自己决定"该调哪个、怎么调"。这就是前面几章技术的真正用武之地。
7.2 完整代码
把下面整段保存为 assistant.py:
import os
os.environ["HF_ENDPOINT"] = "https://hf-mirror.com" # 国内下模型走镜像
import json
import sqlite3
from openai import OpenAI
from sentence_transformers import SentenceTransformer
import chromadb
# ============ 1. 初始化 ============
llm = OpenAI(api_key=os.environ.get("DEEPSEEK_API_KEY", "sk-你的key"), base_url="https://api.deepseek.com")
embedder = SentenceTransformer("BAAI/bge-small-zh-v1.5")
# 业务数据:库存表(真实项目换成你的 MySQL/PG)
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE stock (name TEXT, qty INTEGER)")
conn.executemany("INSERT INTO stock VALUES (?,?)",
[("玉米", 5000), ("小麦", 3000), ("水稻", 2000)])
conn.commit()
# 企业知识库:几段文档(真实项目换成你的文档,可上万段)
kb_docs = [
"收粮入库流程:车辆过磅 → 抽样化验含水率 → 按标准折算 → 入库登记。玉米标准含水率 12%,小麦 13%。",
"含水率折算公式:折合重量 = 实测重量 × (1-实测含水率)/(1-标准含水率)。",
"称重系统并发写入可能出现数据不一致,需为地磅串口读取增加重试机制,并加分布式锁。",
]
kb = chromadb.Client().get_or_create_collection("kb")
kb.add(
ids=[f"d{i}" for i in range(len(kb_docs))],
documents=kb_docs,
embeddings=[embedder.encode(d).tolist() for d in kb_docs],
)
# ============ 2. 工具实现(真正的业务逻辑在这里) ============
def query_stock(grain_name: str) -> str:
"""查库存"""
row = conn.execute("SELECT qty FROM stock WHERE name=?", (grain_name,)).fetchone()
return f"{grain_name} 当前库存 {row[0]} 公斤" if row else f"未找到 {grain_name} 的库存记录"
def search_kb(question: str) -> str:
"""从企业知识库检索最相关的资料(RAG)"""
res = kb.query(
query_embeddings=[embedder.encode(question).tolist()],
n_results=2,
)
chunks = res["documents"][0]
return "\n".join(f"- {c}" for c in chunks)
def calc_fold(actual_weight: float, actual_moisture: float, std_moisture: float) -> str:
"""含水率折算"""
folded = actual_weight * (1 - actual_moisture) / (1 - std_moisture)
return f"折合重量为 {folded:.1f} 公斤"
TOOL_MAP = {"query_stock": query_stock, "search_kb": search_kb, "calc_fold": calc_fold}
# ============ 3. 工具的 JSON 描述(告诉模型"有什么工具、怎么用") ============
tools = [
{
"type": "function",
"function": {
"name": "query_stock",
"description": "查询某粮食品种的库存数量(公斤)",
"parameters": {
"type": "object",
"properties": {"grain_name": {"type": "string", "description": "粮食品种,如 玉米、小麦、水稻"}},
"required": ["grain_name"],
},
},
},
{
"type": "function",
"function": {
"name": "search_kb",
"description": "从企业知识库检索收粮相关的规章制度、流程、公式",
"parameters": {
"type": "object",
"properties": {"question": {"type": "string", "description": "要查询的问题"}},
"required": ["question"],
},
},
},
{
"type": "function",
"function": {
"name": "calc_fold",
"description": "按含水率折算粮食重量。折合重量=实测重量×(1-实测含水率)/(1-标准含水率)",
"parameters": {
"type": "object",
"properties": {
"actual_weight": {"type": "number", "description": "实测重量,公斤"},
"actual_moisture": {"type": "number", "description": "实测含水率,如 0.14 表示 14%"},
"std_moisture": {"type": "number", "description": "标准含水率,如 0.12 表示 12%"},
},
"required": ["actual_weight", "actual_moisture", "std_moisture"],
},
},
},
]
# ============ 4. Agent 循环(决策 + 执行 + 兜底) ============
SYSTEM_PROMPT = (
"你是智粮系统的智能助手。你可以调用工具查询库存、检索知识库、做含水率折算。\n"
"规则:\n"
"1. 优先用工具获取真实数据,不要凭空编造数字;\n"
"2. 回答要简洁,用中文,直接给结论;\n"
"3. 如果工具没查到,如实说明,不要编造。"
)
def chat(question: str, max_steps=5) -> str:
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": question},
]
for _ in range(max_steps):
resp = llm.chat.completions.create(
model="deepseek-v4-flash",
messages=messages,
tools=tools,
)
msg = resp.choices[0].message
if msg.tool_calls: # 想调工具 → 执行,继续循环
messages.append(msg)
for call in msg.tool_calls:
name = call.function.name
args = json.loads(call.function.arguments)
print(f" [调用工具] {name}{args}")
result = TOOL_MAP[name](**args)
messages.append({"role": "tool", "tool_call_id": call.id, "content": result})
continue
else: # 不调工具了 → 返回最终答案
return msg.content
return "抱歉,任务步骤过多,未能完成。"
# ============ 5. 命令行入口 ============
if __name__ == "__main__":
for q in [
"玉米现在库存多少?",
"玉米含水率超标了怎么折算?",
"5000 公斤玉米,实测含水率 14%,按标准 12% 折算后是多少?",
]:
print("\n用户:", q)
print("助手:", chat(q))7.3 跑起来看效果
pip install openai sentence-transformers chromadb
python assistant.py你会看到类似这样的输出——注意模型自己决定了每个问题该调哪个工具:
用户: 玉米现在库存多少?
[调用工具] query_stock{'grain_name': '玉米'}
助手: 玉米当前库存 5000 公斤。
用户: 玉米含水率超标了怎么折算?
[调用工具] search_kb{'question': '玉米含水率超标怎么折算'}
助手: 根据知识库,含水率折算公式为:折合重量 = 实测重量 × (1-实测含水率)/(1-标准含水率)。
玉米标准含水率 12%,小麦 13%。
用户: 5000 公斤玉米,实测含水率 14%,按标准 12% 折算后是多少?
[调用工具] calc_fold{'actual_weight': 5000, 'actual_moisture': 0.14, 'std_moisture': 0.12}
助手: 折合重量为 4886.4 公斤。这个例子浓缩了全部技术:
- 第一个问题 → 走了"查数据"(第 3 章的 SQL 能力,这里简化成查库存表)
- 第二个问题 → 走了"RAG 知识库检索"(第 4 章)
- 第三个问题 → 走了"工具调用 + 参数解析"(第 5 章的 Function Calling)
而"该走哪条路",是模型自己判断的——这就是 Agent。
7.4 加一个界面:用 Streamlit 10 分钟出成品
命令行不够"像产品"。用 Streamlit 加一个网页界面,几行代码的事:
pip install streamlit新建 app.py:
import streamlit as st
from assistant import chat # 复用上面的 chat 函数
st.set_page_config(page_title="智粮系统智能助手")
st.title("🌾 智粮系统智能助手")
if "history" not in st.session_state:
st.session_state.history = []
question = st.chat_input("用大白话问我:查库存、问制度、算折算……")
if question:
st.session_state.history.append({"role": "user", "content": question})
answer = chat(question)
st.session_state.history.append({"role": "assistant", "content": answer})
for m in st.session_state.history:
with st.chat_message(m["role"]):
st.write(m["content"])运行:
streamlit run app.py浏览器自动打开,一个带聊天界面的智能助手就出来了。
注意:
from assistant import chat会执行assistant.py顶层的初始化代码(加载模型、建库),这是故意的——那些只需要在启动时执行一次。真实项目里你可以把初始化挪进一个init()函数,或加if __name__ == "__main__"保护。这里为了简单直接 import。
7.5 怎么把这个项目"变大"
这个 6.3 的雏形,可以按下面几条路继续长成真正的产品,每一条都对应你已学的技术:
- 知识库换真实文档:把
kb_docs换成你公司真正的制度/规范/README,上千份也能塞(第 4 章)。 - 库存表换真数据库:把 sqlite 换成 MySQL/PG,
query_stock里写真实 SQL(第 3 章)。 - 加权限:工具执行前校验当前用户有没有权限查这个数据(第 8 章安全)。
- 加记忆:把用户历史存进向量库,实现"它记得你上次问过什么"(第 6 章)。
- 加评测:建 50 条标准问答,每次改 prompt 都回归测试(第 6 章)。
把上面任意两条做完并开源到 GitHub,你这个"转型代表作"就立住了。
本章小结
- 综合项目 = Agent 循环 + 多个业务工具(查库 / RAG / 计算)。
- "该调哪个工具"交给模型决策,业务逻辑和权限控制在你的代码里。
- Streamlit 能 10 分钟给项目套上网页界面。
- 这个雏形就是你所有学习成果的"集大成",也是面试/转岗时的最好证明。