Day 6:Streamlit 审查面板 + 项目三总结
今天做什么
前五天你做了三个完整的项目。
项目三的最后一天,惯例——用 Streamlit 做一个 Web 界面,让非技术人员也能用你的代码审查能力。 天做什么前五天你做了三个完整的项目。项目三的最后一天,惯例——用 Streamlit 做一个 Web 界面,让非技术人员也能用你的代码审查能力。
你需要安装
bash
pip install streamlit📁 代码目录
code_assistant/
├── sample_code.py
├── day1.py
├── day2.py
├── day3.py
├── day4.py
├── code_reviewer_server.py ← Day5 MCP Server
├── app.py ← 新增!Streamlit审查面板
└── test_module/
├── calc.py
├── utils.py
└── helpers.py代码
python
# app.py — Streamlit 代码审查面板
import streamlit as st
import os
from pathlib import Path
# ===== 审查逻辑(同 Day5)=====
def review_code(code: str) -> list:
"""返回 (issues, score)"""
issues = []
for line in code.split("\n"):
line_stripped = line.strip()
if line_stripped.startswith("def "):
func_name = line_stripped.split("(")[0].replace("def ", "").strip()
issues.append({
"severity": "medium",
"function": func_name,
"category": "文档",
"message": f"函数 `{func_name}` 缺少文档字符串",
"suggestion": "在函数定义下一行添加三重引号注释"
})
if any(w in code for w in ["/ 0", "/0"]):
issues.append({
"severity": "high",
"function": "除法相关",
"category": "安全",
"message": "存在除零风险",
"suggestion": "在除法前添加 `if b == 0:` 检查"
})
if "open(" in code and "with " not in code:
issues.append({
"severity": "high",
"function": "文件操作",
"category": "安全",
"message": "`open()` 未使用 `with` 语句",
"suggestion": "改用 `with open(path) as f:` 确保文件自动关闭"
})
if "except:" in code:
issues.append({
"severity": "medium",
"function": "异常处理",
"category": "规范",
"message": "使用了裸 `except:`",
"suggestion": "指定具体异常类型,如 `except ValueError:`"
})
if "def " in code and ("=[]" in code or "={}" in code):
issues.append({
"severity": "high",
"function": "函数定义",
"category": "陷阱",
"message": "使用可变对象作为默认参数",
"suggestion": "将默认值改为 `None`,在函数体内初始化"
})
score = max(30, 100 - len(issues) * 10)
return issues, score
# ===== 页面设置 =====
st.set_page_config(
page_title="AI 代码审查助手",
page_icon="🔍",
layout="wide"
)
st.title("🔍 AI 代码审查助手")
st.caption("上传 Python 代码,自动检测代码质量问题")
# ===== 侧边栏 =====
with st.sidebar:
st.header("⚙️ 审查设置")
rules = {
"docstring": st.checkbox("文档字符串检查", value=True),
"zero_division": st.checkbox("除零风险检查", value=True),
"file_close": st.checkbox("文件未关闭检查", value=True),
"bare_except": st.checkbox("裸 except 检查", value=True),
"mutable_default": st.checkbox("可变默认参数检查", value=True),
}
st.divider()
input_method = st.radio(
"输入方式",
["粘贴代码", "上传文件"],
horizontal=True
)
st.divider()
st.caption("💡 审查规则可定制——在侧边栏勾选/取消")
# ===== 主区域 =====
if input_method == "粘贴代码":
code_input = st.text_area(
"Python 代码",
height=300,
placeholder="粘贴你的 Python 代码到这里...",
key="code_input"
)
else:
uploaded = st.file_uploader("上传 Python 文件", type=["py"])
if uploaded:
code_input = uploaded.read().decode("utf-8", errors="ignore")
st.text_area("文件内容", code_input, height=300, disabled=True)
else:
code_input = ""
# 加载示例按钮
col1, col2 = st.columns([1, 5])
with col1:
if st.button("📋 加载示例"):
code_input = '''class Calculator:
"""简单计算器"""
def add(self, a, b):
return a + b
def divide(self, a, b):
return a / b
def process_items(items=[]):
"""处理项目列表"""
result = []
for item in items:
result.append(item * 2)
return result
def read_config(path):
f = open(path, "r")
try:
return f.read()
except:
return None
finally:
f.close()
'''
st.session_state["code_input"] = code_input
st.rerun()
# ===== 审查按钮 =====
if code_input and st.button("🔍 开始审查", type="primary", use_container_width=True):
issues, score = review_code(code_input)
# 评分仪表盘
st.divider()
st.subheader("📊 审查结果")
col1, col2, col3 = st.columns(3)
# 总体评分
score_color = "green" if score >= 80 else ("orange" if score >= 60 else "red")
col1.metric("总分", f"{score}/100")
# 问题统计
high_count = sum(1 for i in issues if i["severity"] == "high")
med_count = sum(1 for i in issues if i["severity"] == "medium")
low_count = sum(1 for i in issues if i["severity"] == "low")
col2.metric("🔴 严重", high_count)
col3.metric("🟡 建议", med_count + low_count)
# 评分条
st.progress(score / 100)
if score >= 80:
st.success("✅ 代码质量良好!")
elif score >= 60:
st.warning("⚠️ 有一些问题需要关注")
else:
st.error("🔴 存在较多问题,建议优先修复")
# 问题列表
if issues:
st.divider()
st.subheader(f"🔍 发现 {len(issues)} 个问题")
for i, issue in enumerate(issues):
severity_icon = {"high": "🔴", "medium": "🟡", "low": "🟢"}.get(issue["severity"], "⚪")
with st.container():
cols = st.columns([0.5, 8, 1.5])
cols[0].markdown(f"### {
severity_icon}")
cols[1].markdown(f"**{issue['message']}** \n*建议:{issue['suggestion']}*")
cols[2].caption(f"`{issue['category']}`")
if i < len(issues) - 1:
st.divider()
else:
st.success("🎉 未发现明显问题!")
# ===== 空状态 =====
if not code_input:
st.info("👆 粘贴代码或上传文件,然后点击「开始审查」")运行
bash
streamlit run app.py浏览器打开 http://localhost:8501。你会看到一个清爽的审查面板——粘贴代码,点按钮,秒出结果。每个问题有 emoji 严重程度标记、具体描述和修改建议。
项目三总结
从"读一个 py 文件"到"Web 端的代码审查面板"——六个步骤,跟项目一和项目二一样扎实。
| 天 | 核心 | 你掌握了 |
|---|---|---|
| Day 1 | 代码解析骨架 | ast 模块解析 Python AST,提取类/函数/注释统计 |
| Day 2 | LCEL 审查链 | 四维度评分(命名/职责/文档/安全),MockLLM 模拟审查 |
| Day 3 | 并发审查 | asyncio.gather() 同时审查多文件,整个项目一起审 |
| Day 4 | 审查缓存 | SHA256 文件哈希判重,只审"脏文件" |
| Day 5 | MCP 服务化 | 三个工具(片段/文件/项目审查),标准协议调用 |
| Day 6 | Streamlit 面板 | Web 界面,粘贴代码即审查,可视化评分和问题列表 |
三个项目,一套招式
回头看这 18 天(6+6+6),你三次重复了同一个核心模式:
| 能力 | 项目一:客服 | 项目二:文档 | 项目三:代码 |
|---|---|---|---|
| 搭骨架 | 回声筒函数 | TextLoader | AST 解析 |
| 结构化输出 | 客服回复 | ContractInfo | CodeReview |
| 并发处理 | 多用户对话 | 多文档分析 | 多文件审查 |
| 记忆/缓存 | LangGraph MemorySaver | 文档内容哈希缓存 | 文件内容哈希缓存 |
| MCP 服务化 | 客服工具 MCP | 文档分析 MCP | 代码审查 MCP |
| Web 部署 | FastAPI 部署 | Streamlit 界面 | Streamlit 面板 |
不是三个不同的项目学了三套不同的东西——而是一套东西通过三个不同的场景反复强化。你现在面对一个新的 AI 需求,脑子里会自动浮现这张表:输入是什么、输出结构长什么样、要不要并发、要不要缓存、怎么暴露出 MCP 工具。
这就是这本书第 16-18 章的价值——不是你学会了三个项目,而是你形成了一套 Agent 开发的思维模型。
下一步可以去哪里
三个项目给了你扎实的 AI Agent 开发基础。接下来你可以:
- 换领域尝试:不用客服、文档、代码。试试做营销文案生成、数据分析日报、法律合同比对——底层全是一样的套路
- 接入真实 LLM:把三个项目里的 MockLLM 替换为 OpenAI/Claude/Gemini 的 API,代码不需要重构——MockLLM 挡住的只是 LLM 调用那一步
- 进阶阅读:LangGraph 的更多模式(Human-in-the-loop、Multi-agent)、LangChain 的更多组件(Agents、Toolkits、Callbacks)
🎉 项目三完成,实战篇全三章完成。Agent 能读代码、审查质量、并发审多文件、缓存优化、MCP 服务化、Web 面板部署。跟项目一和项目二一起——你已经有能力从零开发一个完整的 AI Agent 应用了。

