智能体框架一:AutoGen
为何需要一个智能体框架?
- 提升代码复用与开发效率:无论是 ReAct 还是 Plan-and-Solve,都可以基于框架提供的标准组件快速搭建,从而避免重复劳动。
- 实现核心组件的解耦与可扩展性:一个健壮的智能体系统应该由多个松散耦合的模块组成。框架的设计会强制我们分离不同的关注点:
- 模型层 (Model Layer):负责与大语言模型交互,可以轻松替换不同的模型(OpenAI, Anthropic, 本地模型)。
- 工具层 (Tool Layer):提供标准化的工具定义、注册和执行接口,添加新工具不会影响其他代码。
- 记忆层 (Memory Layer):处理短期和长期记忆,可以根据需求切换不同的记忆策略(如滑动窗口、摘要记忆)。 这种模块化的设计使得整个系统极具可扩展性,更换或升级任何一个组件都变得简单。
- 标准化复杂的状态管理:在真实的、长时运行的智能体应用中,状态管理是一个巨大的挑战,它需要处理上下文窗口限制、历史信息持久化、多轮对话状态跟踪等问题。一个框架可以提供一套强大而通用的状态管理机制,开发者无需每次都重新处理这些复杂问题。
- 简化可观测性与调试过程:当智能体的行为变得复杂时,理解其决策过程变得至关重要。一个精心设计的框架可以内置强大的可观测性能力。例如,通过引入事件回调机制(Callbacks),我们可以在智能体生命周期的关键节点(如
on_llm_start,on_tool_end,on_agent_finish)自动触发日志记录或数据上报,从而轻松地追踪和调试智能体的完整运行轨迹。
主流框架:
- AutoGen:AutoGen 的核心思想是通过对话实现协作[1]。它将多智能体系统抽象为一个由多个“可对话”智能体组成的群聊。开发者可以定义不同角色(如
Coder,ProductManager,Tester),并设定它们之间的交互规则(例如,Coder写完代码后由Tester自动接管)。任务的解决过程,就是这些智能体在群聊中通过自动化消息传递,不断对话、协作、迭代直至最终目标达成的过程。 - AgentScope:AgentScope 是一个专为多智能体应用设计的、功能全面的开发平台[2]。它的核心特点是易用性和工程化。它提供了一套非常友好的编程接口,让开发者可以轻松定义智能体、构建通信网络,并管理整个应用的生命周期。其内置的消息传递机制和对分布式部署的支持,使其非常适合构建和运维复杂、大规模的多智能体系统。
- CAMEL:CAMEL 提供了一种新颖的、名为角色扮演 (Role-Playing) 的协作方法[3]。其核心理念是,我们只需要为两个智能体(例如,
AI研究员和Python程序员)设定好各自的角色和共同的任务目标,它们就能在“初始提示 (Inception Prompting)”的引导下,自主地进行多轮对话,相互启发、相互配合,共同完成任务。它极大地降低了设计多智能体对话流程的复杂度。 - LangGraph:作为 LangChain 生态的扩展,LangGraph 另辟蹊径,将智能体的执行流程建模为图 (Graph)[4]。在传统的链式结构中,信息只能单向流动。而 LangGraph 将每一步操作(如调用LLM、执行工具)定义为图中的一个节点 (Node),并用边 (Edge) 来定义节点之间的跳转逻辑。这种设计天然支持循环 (Cycles),使得实现如 Reflection 这样的迭代、修正、自我反思的复杂工作流变得异常简单和直观。
AutoGen
核心机制:AutoGen 的设计哲学根植于”以对话驱动协作”。它巧妙地将复杂的任务解决流程,映射为不同角色的智能体之间的一系列自动化对话。基于这一核心理念,AutoGen 框架持续演进。
示例:实时显示比特币当前价格。
软件开发的典型环节:从需求分析、技术选型、编码实现到代码审查和最终测试。
智能体团队角色:
- ProductManager (产品经理): 负责将用户的模糊需求转化为清晰、可执行的开发计划。
- Engineer (工程师): 依据开发计划,负责编写具体的应用程序代码。
- CodeReviewer (代码审查员): 负责审查工程师提交的代码,确保其质量、可读性和健壮性。
- UserProxy (用户代理): 代表最终用户,发起初始任务,并负责执行和验证最终交付的代码。
代码实现:
# 代码目录结构
autogen_demo:
/llm
index.py
/roles
pm.py
enginer.py
reviewer.py
userProxy.py
/methods # 定义合作方式
teamChat.py
/projects # 放置一些程序,解决用户问题
创建LLM客户端(/llm/index.py):
from autogen_ext.models.openai import OpenAIChatCompletionClient;
import os;
from dotenv import load_dotenv;
load_dotenv();
def create_model_client():
return OpenAIChatCompletionClient(
api_key=os.getenv('API_KEY'),
model=os.getenv('MODEL_ID', 'deepseek-v4-flash'),
base_url=os.getenv('BASE_URL'),
model_info={ # deepseek非openai官方model,此字段必传
'vision': True,
'function_calling': True,
'json_output': True,
'family': 'unknown'
}
);
创建角色(roles/*)
产品经理(Product_Manager):
from autogen_agentchat.agents import AssistantAgent;
def create_product_manager(model_client):
"""创建产品经理智能体"""
system_message = """你是一位经验丰富的产品经理,专门负责软件产品的需求分析和项目规划。
你的核心职责包括:
1. **需求分析**:深入理解用户需求,识别核心功能和边界条件
2. **技术规划**:基于需求制定清晰的技术实现路径
3. **风险评估**:识别潜在的技术风险和用户体验问题
4. **协调沟通**:与工程师和其他团队成员进行有效沟通
当接到开发任务时,请按以下结构进行分析:
1. 需求理解与分析
2. 功能模块划分
3. 技术选型建议
4. 实现优先级排序
5. 验收标准定义
请简洁明了地回应,并在分析完成后说"请工程师开始实现"。"""
return AssistantAgent(
name="ProductManager",
model_client=model_client,
system_message=system_message,
)
工程师 (Engineer):
from autogen_agentchat.agents import AssistantAgent;
def create_engineer(model_client):
"""创建软件工程师智能体"""
system_message = """你是一位资深的软件工程师,擅长 Python 开发和 Web 应用构建。
你的技术专长包括:
1. **Python 编程**:熟练掌握 Python 语法和最佳实践
2. **Web 开发**:精通 Streamlit、Flask、Django 等框架
3. **API 集成**:有丰富的第三方 API 集成经验
4. **错误处理**:注重代码的健壮性和异常处理
当收到开发任务时,请:
1. 仔细分析技术需求
2. 选择合适的技术方案
3. 编写完整的代码实现
4. 添加必要的注释和说明
5. 考虑边界情况和异常处理
请提供完整的可运行代码,并在完成后说"请代码审查员检查"。"""
return AssistantAgent(
name="Engineer",
model_client=model_client,
system_message=system_message,
)
代码审查员(Code Reviewer):
from autogen_agentchat.agents import AssistantAgent;
def create_code_reviewer(model_client):
"""创建代码审查员智能体"""
system_message = """你是一位经验丰富的代码审查专家,专注于代码质量和最佳实践。
你的审查重点包括:
1. **代码质量**:检查代码的可读性、可维护性和性能
2. **安全性**:识别潜在的安全漏洞和风险点
3. **最佳实践**:确保代码遵循行业标准和最佳实践
4. **错误处理**:验证异常处理的完整性和合理性
审查流程:
1. 仔细阅读和理解代码逻辑
2. 检查代码规范和最佳实践
3. 识别潜在问题和改进点
4. 提供具体的修改建议
5. 评估代码的整体质量
请提供具体的审查意见,完成后说"代码审查完成,请用户代理测试"。"""
return AssistantAgent(
name="CodeReviewer",
model_client=model_client,
system_message=system_message,
)
用户代理(User Proxy):
from autogen_agentchat.agents import UserProxyAgent;
def create_user_proxy():
"""创建用户代理智能体"""
return UserProxyAgent(
name="UserProxy",
description="""用户代理,负责以下职责:
1. 代表用户提出开发需求
2. 执行最终的代码实现
3. 验证功能是否符合预期
4. 提供用户反馈和建议
完成测试后请回复 TERMINATE。""",
)
规定他们之间的合作流程(methods/teamchat)
from autogen_agentchat.teams import RoundRobinGroupChat;
from autogen_agentchat.conditions import TextMentionTermination;
from llm.index import create_model_client;
from roles.PM import create_product_manager;
from roles.Enginer import create_engineer;
from roles.Reviewer import create_code_reviewer;
from roles.UserProxy import create_user_proxy;
model_client = create_model_client();
product_manager = create_product_manager(model_client=model_client);
engineer = create_engineer(model_client=model_client);
code_reviewer = create_code_reviewer(model_client=model_client);
user_proxy = create_user_proxy();
# 定义团队聊天和协作规则
team_chat = RoundRobinGroupChat(
participants=[
product_manager,
engineer,
code_reviewer,
user_proxy
],
termination_condition=TextMentionTermination("TERMINATE"),
max_turns=20,
)
接下来就可以合作一起做项目啦~
我们在/projects文件夹下,创建bitCoinInfo的项目文件:
from methods.teamChat import team_chat;
import asyncio;
from autogen_agentchat.ui import Console
async def run_software_development_team():
# 定义任务描述
task = """我们需要开发一个比特币价格显示应用,具体要求如下:
核心功能:
- 实时显示比特币当前价格(USD)
- 显示24小时价格变化趋势(涨跌幅和涨跌额)
- 提供价格刷新功能
技术要求:
- 使用 Streamlit 框架创建 Web 应用
- 界面简洁美观,用户友好
- 添加适当的错误处理和加载状态
请团队协作完成这个任务,从需求分析到最终实现。"""
# 异步执行团队协作,并流式输出对话过程
result = await Console(team_chat.run_stream(task=task))
return result
# 主程序入口
if __name__ == "__main__":
result = asyncio.run(run_software_development_team())
直接运行刚刚的项目文件:python -m projects.bitCoinInfo
观察输出:



/Users/shane/dev/AutoGen/venv/lib/python3.14/site-packages/autogen_ext/models/openai/_openai_client.py:466: UserWarning: Missing required field 'structured_output' in ModelInfo. This field will be required in a future version of AutoGen.
validate_model_info(self._model_info)
---------- TextMessage (user) ----------
我们需要开发一个比特币价格显示应用,具体要求如下:
核心功能:
- 实时显示比特币当前价格(USD)
- 显示24小时价格变化趋势(涨跌幅和涨跌额)
- 提供价格刷新功能
技术要求:
- 使用 Streamlit 框架创建 Web 应用
- 界面简洁美观,用户友好
- 添加适当的错误处理和加载状态
请团队协作完成这个任务,从需求分析到最终实现。
---------- ThoughtEvent (ProductManager) ----------
我们被要求开发一个比特币价格显示应用,使用Streamlit框架。作为产品经理,我们需要进行需求分析、功能模块划分、技术选型、优先级排序、验收标准。然后请求工程师开始实现。
需求理解:
- 核心功能:实时显示BTC/USD价格,24小时变化趋势(涨跌幅和涨跌额),刷新功能。
- 技术要求:Streamlit,界面简洁美观,错误处理,加载状态。
需要分析API源:常见的免费API如CoinGecko、CoinCap、Binance等。建议使用CoinGecko的免费API。
功能模块:
1. 数据获取模块:从API获取BTC价格和24小时变化数据。
2. 显示模块:显示价格、涨跌幅、涨跌额,使用颜色区分涨跌。
3. 刷新模块:提供按钮或自动刷新(Streamlit的st.rerun或st.experimental_rerun)。
4. 错误处理:API请求失败时显示错误信息。
5. 加载状态:获取数据时显示spinner或进度条。
技术选型:Streamlit + requests库。
优先级排序:先实现核心数据获取和显示,再添加自动刷新和样式。
验收标准:
- 页面加载后自动显示比特币价格。
- 价格以绿色显示上涨,红色显示下跌。
- 显示24小时涨跌额和百分比。
- 有刷新按钮。
- 网络故障时显示友好错误。
现在输出分析结果,然后说"请工程师开始实现"。
---------- TextMessage (ProductManager) ----------
### 需求理解与分析
用户需要一个简洁、实时的比特币价格监控工具,核心关注点为当前价格、24小时价格变化趋势(涨跌幅与涨跌额)以及手动刷新能力。使用 Streamlit 框架意味着需要快速构建单页 Web 应用,强调低代码和交互性。技术选型上,应采用免费的加密货币 API(如 CoinGecko 公共 API)获取数据,无需用户登录。
### 功能模块划分
1. **数据获取模块**
- 调用 CoinGecko API `/simple/price` 获取 BTC/USD 当前价格及 24h 涨跌幅
- 处理超时、网络异常、API 限流等错误
2. **价格显示模块**
- 大字体显示当前价格(USD)
- 涨跌额与涨跌幅并排显示,上涨用绿色(+),下跌用红色(-)
- 显示最后更新时间(或数据时效性提示)
3. **交互控制模块**
- “刷新价格”按钮,触发 `st.rerun` 重新获取数据
- 数据加载时显示加载动画(`st.spinner`)
- 错误时显示提示信息而非崩溃
4. **自动刷新(可选,低优先级)**
- 使用 `st.empty` 占位 + `time.sleep` 循环或 `streamlit-autorefresh` 组件
### 技术选型建议
- **框架**:Streamlit(v1.28+)
- **HTTP 库**:`requests`(Python 内置)
- **数据源**:CoinGecko 免费 API(`https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd&include_24hr_change=true`)
- **样式优化**:Streamlit 原生 markdown + CSS 自定义(通过 `st.markdown` 嵌入 `<style>`)
### 实现优先级排序
1. **P0**:核心数据获取与价格/涨跌显示(含错误处理)
2. **P1**:刷新按钮与加载状态
3. **P2**:界面美化(颜色、对齐、字体)
4. **P3**:自动刷新选项(视需求可推迟)
### 验收标准
- 应用启动后自动展示比特币当前 USD 价格(保留两位小数)
- 24h 涨跌额(如 +123.45)和涨跌幅(如 +2.34%)同时显示
- 上涨时数字为绿色,下跌为红色,平盘为灰色
- 点击“刷新”按钮后重新请求数据,期间有加载动画
- 当 API 请求失败(网络断开、限流等),页面显示“数据获取失败,请稍后重试”而不是空白或异常
- 页面整体布局整洁,标题清晰
请工程师开始实现。
---------- ThoughtEvent (Engineer) ----------
我们被要求开发一个比特币价格显示应用,使用Streamlit。需要实现实时显示比特币当前价格(USD),24小时变化趋势(涨跌幅和涨跌额),刷新功能。需要添加错误处理和加载状态。
作为软件工程师,我们需要编写一个完整的Streamlit应用。我将在代码中包含:
- 导入必要的库:streamlit, requests, time(用于显示更新时间),datetime(可选)。
- 定义一个函数fetch_btc_price()来调用CoinGecko API:https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd&include_24hr_change=true
- 解析返回的JSON,提取usd价格和usd_24h_change(百分比变化)。还要计算涨跌额?API返回usd_24h_change是百分比变化,但我们需要涨跌额吗?24小时价格变化趋势通常包括涨跌幅(百分比)和涨跌额(具体金额)。CoinGecko的简单价格端点只提供24小时百分比变化。要获得24小时涨跌额,可能需要另一个API端点,比如/coins/bitcoin?localization=false&tickers=false&community_data=false&developer_data=false&sparkline=false,它包含market_data.price_change_24h_in_currency.usd。为了简单起见,我可能会选择只显示百分比变化,但需求明确要求“显示24小时价格变化趋势(涨跌幅和涨跌额)”。所以最好两者都提供。
我们可以使用两个请求:一个获取当前价格和24小时变化百分比,另一个获取24小时价格变化金额。或者使用/coins/bitcoin端点,一次获取所有信息。这个端点返回较慢但数据丰富。考虑到免费API限制,建议使用/coins/bitcoin的简洁请求。例如:https://api.coingecko.com/api/v3/coins/bitcoin?localization=false&tickers=false&community_data=false&developer_data=false&sparkline=false。响应中的market_data字段包含:current_price.usd, price_change_percentage_24h, price_change_24h_in_currency.usd。
我们可以使用这个端点来减少请求次数。代码中我们定义fetch_btc_data()返回字典包含价格、涨跌额、涨跌幅。
实现:
- 使用st.set_page_config设置标题和布局。
- 在页面顶部显示标题和子标题。
- 使用st.empty()作为占位符,用于后续更新数据。
- 定义一个按钮“刷新价格”,点击后调用st.rerun()触发重新运行。
- 在数据加载时使用st.spinner显示加载状态。
- 使用session_state或缓存来避免重复请求?无需缓存,因为每次重新运行都会请求新数据。
- 显示价格时,使用st.metric组件可以很好地显示指标和增量。st.metric(label, value, delta)其中delta可以是数字或字符串,自动显示颜色(绿色为正,红色为负)。这对我们很合适。我们可以用st.metric显示24小时变化,其中delta为涨跌额或涨跌幅?但需求要求显示涨跌幅和涨跌额两个数值。我们可以用两个st.metric并排?或者在一个st.metric中只显示一个,另一个通过markdown显示。为了简单,我们将涨跌幅和涨跌额合并为delta字符串,例如“+123.45 (2.34%)”。这样st.metric的delta会显示为字符串,但不会自动着色。我们可以手动用markdown着色。
更好的方法:使用自定义CSS和列布局。例如:第一列显示当前价格(大字体),第二列显示涨跌额(带颜色),第三列显示涨跌幅(带颜色)。这样更清晰。
考虑设计:让页面居中,使用大字体显示价格,并排显示变化。
我将采用以下布局:
- 使用st.columns(3)创建三个列:左列显示BTC图标或标签,中间列显示价格(大号),右列显示变化。
- 或者更简单的:顶部标题,然后一个大的metric显示当前价格,下面一行用两列分别显示涨跌额和涨跌幅。
我倾向于使用st.metric显示价格,然后在其下方用两列显示变化。但st.metric本身只支持一个delta,而我们需要两个delta。所以我们可以用两个st.metric并排,一个显示涨跌额,一个显示涨跌幅。但这样会显示两个指标和它们的标题,可能比较拥挤。我们可以用st.columns创建两个列,每个列用一个st.markdown自定义格式。
计划:
- 使用st.metric显示当前价格,label="BTC/USD",value=curent_price,delta=price_change_24h_in_usd(涨跌额),这样delta自动显示颜色,但我们需要同时显示涨跌幅。我们可以把涨跌幅放在子标题或旁边的文字中。例如在metric的帮助下,我们用st.write或st.caption显示涨跌幅。
但是需求说“显示24小时价格变化趋势(涨跌幅和涨跌额)”,并没有严格要求必须分开。我们可以将涨跌幅作为辅助信息显示。
为了更清晰,我决定:
- 使用st.metric显示当前价格,label="Bitcoin (BTC)", value="$ {price:,.2f}",delta=f"{change_24h_usd:+.2f} ({change_24h_pct:+.2f}%)",这样delta字符串包含两者。但st.metric的delta如果是字符串,不会自动着色,但我们可以通过CSS或HTML着色。实际上st.metric的delta参数如果传入字符串,Streamlit会原样显示,不会自动应用颜色。所以我们最好自定义。
自定义CSS:我们可以用markdown写一个包含样式的大块,显示价格和变化。但保持Streamlit的交互特性,我们仍然使用st.metric但将颜色手动控制。或者使用st.markdown配合HTML和CSS完全自定义。
我选择使用st.metric,但将涨跌额和涨跌幅合并为一个delta字符串,并借助CSS在字符串中嵌入颜色。但是st.metric的value和delta参数是纯文本,不能内嵌HTML。所以无法着色。
另一种方法:完全使用st.markdown构建布局,用大字体显示价格,用带颜色的文字显示变化。
我倾向于用st.markdown,因为更灵活。但需要保持好响应式。
具体实现:
1. 在app中定义一个函数get_btc_data(),使用requests.get调用CoinGecko API,设置超时10秒。
2. 解析响应,提取所需数据。
3. 在主函数中,使用streamlit的session_state记录错误状态。
4. 使用st.spinner在获取数据时显示。
5. 成功后将数据放入变量,显示在页面上。
6. 错误时显示错误信息。
7. 按钮的放置:放在st.button中,点击后调用st.rerun()。
注意:st.rerun()需要Streamlit 1.27.0+,我们假设用户有较新版本。
另外,添加自动刷新?可选,但需求没强制,不实现。
下面是代码的骨架:
```python
import streamlit as st
import requests
from datetime import datetime
st.set_page_config(page_title="Bitcoin Price Tracker", page_icon="₿", layout="centered")
def fetch_btc_data():
url = "https://api.coingecko.com/api/v3/coins/bitcoin?localization=false&tickers=false&community_data=false&developer_data=false&sparkline=false"
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
data = response.json()
market_data = data['market_data']
price = market_data['current_price']['usd']
change_24h_usd = market_data['price_change_24h_in_currency']['usd']
change_24h_pct = market_data['price_change_percentage_24h']
return {
'price': price,
'change_usd': change_24h_usd,
'change_pct': change_24h_pct,
'last_updated': datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
except requests.exceptions.RequestException as e:
st.error(f"Failed to fetch data: {e}")
return None
except KeyError as e:
st.error(f"Unexpected API response structure: {e}")
return None
def main():
st.title("₿ Bitcoin Price Tracker")
st.markdown("Real-time price and 24h change from CoinGecko")
# Placeholder for data
data_placeholder = st.empty()
# Refresh button
if st.button("🔄 Refresh Price"):
st.rerun()
# Fetch data
with st.spinner("Fetching latest Bitcoin price..."):
btc_data = fetch_btc_data()
if btc_data:
price = btc_data['price']
change_usd = btc_data['change_usd']
change_pct = btc_data['change_pct']
last_updated = btc_data['last_updated']
# Determine color class
color_class = "green" if change_usd >= 0 else "red"
# Display price using columns
col1, col2, col3 = st.columns([2, 1, 1])
with col1:
st.markdown(f"<h1 style='font-size:3em;'>${price:,.2f}</h1>", unsafe_allow_html=True)
with col2:
st.markdown(f"<p style='font-size:1.2em; color:{color_class};'>{'+' if change_usd >= 0 else ''}${change_usd:,.2f}</p>", unsafe_allow_html=True)
with col3:
st.markdown(f"<p style='font-size:1.2em; color:{color_class};'>{'+' if change_pct >= 0 else ''}{change_pct:.2f}%</p>", unsafe_allow_html=True)
st.caption(f"Last updated: {last_updated}")
else:
st.warning("Unable to fetch data. Please try again later.")
```
以上代码基本满足需求。但还有改进之处:页面顶部刷新按钮可能不够显眼,我们可以在价格显示区域附近放置按钮。另外,错误处理可以使用session_state来避免连续请求失败时一直显示错误?当前错误显示在页面上,但下次刷新会清除。已经可以。
注意:使用st.spinner时,如果数据获取失败,spinner会消失并显示错误信息。但spinner只包裹fetch_btc_data(),如果失败返回None,我们仍然可以显示warnings。很好。
另外,我们可以添加一个自动刷新功能(例如每60秒)使用streamlit_autorefresh,但需要额外安装,不强制。
对于布局,我选择三列显示价格、涨跌额、涨跌幅。但看起来可能需要更美观的对齐。或许使用st.metric的简洁方式更好。让我重新考虑:使用st.metric显示价格,然后在下面一行用两个st.metric分别显示涨跌额和涨跌幅。但这样会占据更多空间,且重复label。我们可以将涨跌额和涨跌幅作为辅助信息放在同一行。
我倾向于使用st.metric显示价格,并在其下方用两列显示变化。每个变化也用st.metric来显示,但label分别为“24h Change (USD)”和“24h Change (%)”。这样自动着色。但st.metric的delta只显示一个值,这里是涨跌额和涨跌幅本身值,不需要delta。实际上,st.metric用于显示指标值,可以用value来显示涨跌额或涨跌幅。但value的颜色不会自动根据正负变化,除非我们手动设置color。在st.metric中无法直接设置颜色。所以我们还是使用自定义HTML。
我决定采用自定义HTML,但为了更简洁,可以使用st.write配合emoji和颜色字体。
我最终采用方案:使用两个st.metric: 第一个显示当前价格(不带delta),第二个显示涨跌额(用delta参数传递涨跌额,自动颜色)。第三个显示涨跌幅(用delta参数传递涨跌幅,自动颜色)。但st.metric的delta参数接受一个数值,会自动着色。那么我们可以将涨跌额和涨跌幅分别作为两个单独的metric的delta,而value设为“24h Change”。但这样显示的是“24h Change: +123.45”这样,可能不太直观。
更合理的:使用四个metric: 价格(value=price),涨跌额(value=change_usd, label="24h Change (USD)"),涨跌幅(value=change_pct, label="24h Change (%)")。但value不会自动着色。st.metric的delta只显示变化方向,如果我们把value设为零或什么?不好。
最佳做法:用st.columns嵌入三个自定义HTML卡片。我选择最初的自定义HTML方案。
修改代码,使用st.markdown和CSS内置样式,确保在不同屏幕上的响应式。
此外,加入时间戳显示。
还有一个要点:价格变化趋势有时需要显示“涨跌额”和“涨跌幅”,我们用了$和百分号。注意正负号。
代码已经基本实现。还需要添加异常处理细节:如果API返回错误,例如超时,我们捕获异常并显示错误信息。
测试:假设用户运行。
最后加一句“请代码审查员检查”。
让我们写出最终代码。
---------- TextMessage (Engineer) ----------
```python
"""
比特币价格显示应用 - Streamlit 版本
功能:
- 实时显示比特币当前美元价格
- 显示24小时价格变化趋势(涨跌额和涨跌幅)
- 提供手动刷新按钮
- 错误处理与加载状态
"""
import streamlit as st
import requests
from datetime import datetime
# ==================== 页面配置 ====================
st.set_page_config(
page_title="比特币价格追踪器",
page_icon="₿",
layout="centered",
initial_sidebar_state="collapsed"
)
# ==================== 数据获取模块 ====================
def fetch_btc_data():
"""
调用 CoinGecko API 获取比特币最新市场数据
返回字典包含:价格、24h涨跌额、24h涨跌幅、更新时间
若失败则返回 None
"""
url = (
"https://api.coingecko.com/api/v3/coins/bitcoin"
"?localization=false"
"&tickers=false"
"&community_data=false"
"&developer_data=false"
"&sparkline=false"
)
try:
response = requests.get(url, timeout=10)
response.raise_for_status() # 抛出 HTTP 错误
data = response.json()
market_data = data['market_data']
price = market_data['current_price']['usd']
change_24h_usd = market_data['price_change_24h_in_currency']['usd']
change_24h_pct = market_data['price_change_percentage_24h']
return {
'price': price,
'change_usd': change_24h_usd,
'change_pct': change_24h_pct,
'last_updated': datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
except requests.exceptions.RequestException as e:
st.error(f"网络请求失败: {e}")
return None
except KeyError as e:
st.error(f"API 响应结构异常: {e}")
return None
except Exception as e:
st.error(f"未知错误: {e}")
return None
# ==================== 主界面 ====================
def main():
st.title("₿ Bitcoin 价格追踪器")
st.markdown("数据来源: CoinGecko · 实时更新")
# ---------- 刷新按钮 ----------
col_refresh, _ = st.columns([1, 5])
with col_refresh:
if st.button("🔄 刷新价格", use_container_width=True):
st.rerun()
# ---------- 获取数据 ----------
with st.spinner("正在获取最新比特币价格 ..."):
btc_data = fetch_btc_data()
# ---------- 数据显示 ----------
if btc_data is None:
st.warning("⚠️ 数据获取失败,请稍后重试" )
return # 结束渲染,保留错误信息
price = btc_data['price']
change_usd = btc_data['change_usd']
change_pct = btc_data['change_pct']
last_updated = btc_data['last_updated']
# 根据涨跌决定颜色
color = "#22c55e" if change_usd >= 0 else "#ef4444" # 绿色 / 红色
arrow = "▲" if change_usd >= 0 else "▼"
# ---------- 布局:三列(价格 | 涨跌额 | 涨跌幅)----------
col1, col2, col3 = st.columns([3, 1.2, 1.2], gap="medium")
with col1:
st.markdown(
f"<div style='font-size: 3.2rem; font-weight: 700; "
f"line-height: 1.2;'>${price:,.2f}</div>",
unsafe_allow_html=True
)
with col2:
st.markdown(
f"<div style='font-size: 1rem; color: {color}; "
f"border-left: 2px solid #e2e8f0; padding-left: 12px;'>"
f"<span style='font-size: 0.75rem; color: #64748b;'>变化额</span><br>"
f"<span style='font-size: 1.3rem; font-weight: 600;'>{arrow} "
f"{'+' if change_usd >= 0 else ''}${change_usd:,.2f}</span>"
f"</div>",
unsafe_allow_html=True
)
with col3:
st.markdown(
f"<div style='font-size: 1rem; color: {color}; "
f"border-left: 2px solid #e2e8f0; padding-left: 12px;'>"
f"<span style='font-size: 0.75rem; color: #64748b;'>变化率</span><br>"
f"<span style='font-size: 1.3rem; font-weight: 600;'>{arrow} "
f"{'+' if change_pct >= 0 else ''}{change_pct:.2f}%</span>"
f"</div>",
unsafe_allow_html=True
)
# ---------- 更新时间 ----------
st.caption(f"最后更新: {last_updated}")
# ==================== 入口 ====================
if __name__ == "__main__":
main()
```
---
请代码审查员检查。
---------- ThoughtEvent (CodeReviewer) ----------
我们被要求作为代码审查员审查这个比特币价格显示应用的代码。需要关注代码质量、安全性、最佳实践、错误处理。给出具体的审查意见,最后说"代码审查完成,请用户代理测试"。
我将逐行审查代码,注意:
1. 整体结构良好,注释清晰。
2. 使用了streamlit,页面配置,数据获取模块,主界面模块。
3. 关注点:
- API请求是否正确?CoinGecko API 的URL是?当前是`/coins/bitcoin`端点,返回大量数据,不如用`/simple/price`更轻量。但当前代码也可以工作,只是速度可能稍慢。不过这不是错误,但可以建议优化。
- 错误处理:使用了请求异常、KeyError、通用异常,捕获并显示错误信息,但st.error会直接显示在页面上,如果数据为None,会显示warning并返回。但注意:fetch_btc_data内部用了st.error,这会导致错误信息在页面上显示,然后主函数检查btc_data为None时又显示提示。这可能会重复显示错误信息。更好的做法是fetch_btc_data内部不直接调用st.error,而是抛出异常或者返回错误信息,让主函数统一处理。当前设计是捕获异常后st.error并返回None。然后主函数又st.warning。这样用户会看到两个提示:“网络请求失败”和“数据获取失败,请稍后重试”,可能有点冗余。可以优化。
- 安全性:使用requests,无直接风险。但API返回的数据信任吗?我们只是显示,没有执行。
- 性能:每次点击刷新都会重新请求,没问题。
- 样式:使用了unsafe_allow_html,需要确保内容是安全的,这里是固定的格式字符串,没有问题。
- 数据更新时间:用`datetime.now()`,不是真正的API返回时间。可以显示API返回的`last_updated_at`字段(如果有)。但CoinGecko的coins/bitcoin端点返回`last_updated`字段,是ISO时间,建议使用API的时间更准确。当前代码简单用本地时间,也可以接受,但最好使用API返回的时间。
- 变量命名:合理。
- 是否应该添加自动刷新?用户要求添加手动刷新即可,P3可选。当前没有自动刷新,符合要求。
- 数据获取模块中的URL构建,使用f-string没问题。
- 注意:当response.json()返回的数据中`market_data`可能不存在,或者`current_price`等字段不存在,KeyError会捕获,但可能不能区分具体字段缺失。可以更精确地使用dict的get方法或检查。
- 还应该考虑API限流,没有特殊处理,可以建议添加重试逻辑。
- 代码复用性:目前良好。
- Streamlit rerun可能导致无限循环?点击按钮触发rerun,没问题。
- 布局:col1, col2, col3比例合理。
其他:
- 是否应该使用session来减少连接开销?不是必须。
- 是否添加缓存?st.cache_data可以缓存数据一段时间(例如30秒),但手动刷新就不需要了。但如果不添加自动刷新,每次点击按钮都会请求,没有缓存问题。但建议使用st.cache_data来避免重复请求如果用户多次点击相同页面?实际上每次rerun都会重新执行脚本,cache_data可以避免在短时间内重复请求。但用户要求手动刷新,可能需要最新数据,缓存会使得刷新不更新。所以不使用缓存合理。
改进点:
1. 错误处理优化:避免双重错误提示。
2. 使用API返回的时间戳而非本地时间。
3. 使用更轻量的simple price API。
我将撰写审查意见,包括上述改进建议,并指出代码质量和安全性良好。最后说"代码审查完成,请用户代理测试"。
Enter your response: ---------- TextMessage (CodeReviewer) ----------
## 代码审查报告
### 总体评价
代码结构清晰、注释完整,功能完整实现了需求中的核心功能(价格显示、涨跌额/率、刷新按钮、错误处理与加载状态)。使用 Streamlit 框架和 CoinGecko API 完成数据获取,整体实现符合最佳实践。但仍有几点可优化之处,以提升数据准确性、用户体验和代码稳健性。
### 审查要点
#### 1. ✅ 代码质量与可读性
- **良好**:函数功能单一(`fetch_btc_data` 负责数据获取,`main` 负责 UI),变量命名清晰,注释充分。
- **改进建议**:
- `fetch_btc_data` 内部使用了 `st.error`,这会导致错误信息直接输出到主界面。若发生错误,后续在 `main` 中又会显示 `st.warning`,用户会看到两条提示(如“网络请求失败: ...”、“⚠️ 数据获取失败,请稍后重试”),略显冗余。建议让 `fetch_btc_data` 只返回数据或 `None`,由主函数统一管理错误消息。例如:
```python
except Exception as e:
st.session_state.error_msg = str(e)
return None
```
然后在 `main` 中根据 `btc_data` 是否为 `None` 显示统一提示,避免重复。
#### 2. ✅ 安全性
- **安全**:所有用户输入均为固定 UI 控件,无外部注入风险。使用了 `unsafe_allow_html=True`,但渲染的内容均为程序生成的静态字符串(固定 HTML 模板 + 数值变量),无 XSS 风险。
#### 3. ⚠️ 数据时效性
- **问题**:`last_updated` 使用 `datetime.now()` 表示更新时间,实际上用户看到的是本地请求完成的时间,而非 CoinGecko 服务器生成该数据的真实时间。当网络延迟或 API 缓存时,该时间可能不准确。
- **建议**:使用 CoinGecko API 返回的 `last_updated` 字段(ISO 8601 格式),转换为本地时间后显示。示例:
```python
from datetime import datetime, timezone
api_time = data['last_updated'] # 如 "2025-04-09T12:34:56.789Z"
localized_time = datetime.fromisoformat(api_time.replace('Z', '+00:00')).astimezone()
updated_str = localized_time.strftime("%Y-%m-%d %H:%M:%S")
```
#### 4. ⚙️ API 选择与性能
- **当前选择**:使用 `/coins/bitcoin` 端点返回完整市场数据(含 tickers、community_data 等),虽然通过参数禁用了部分字段,但仍比 `/simple/price?ids=bitcoin&vs_currencies=usd&include_24hr_change=true` 更重,响应速度稍慢。
- **建议**:切换至 `/simple/price` 端点,仅获取价格和 24h 变化,可减少数据量、提升加载速度。调整后的 `fetch_btc_data` 示例:
```python
url = (
"https://api.coingecko.com/api/v3/simple/price"
"?ids=bitcoin&vs_currencies=usd&include_24hr_change=true"
)
response = requests.get(url, timeout=10)
data = response.json()
price = data['bitcoin']['usd']
change_24h_pct = data['bitcoin']['usd_24h_change'] # 该端点只返回百分比变化
# 注意:simple price 端点不直接提供 24h 涨跌额(usd 变化量),需额外计算或使用其他端点
```
若需同时显示涨跌额,仍建议保留 `/coins/bitcoin` 端点,但可进一步精简参数(如只取 `market_data` 部分)。当前实现已可接受,仅作优化提示。
#### 5. 🔄 用户体验与自动刷新
- **手动刷新**:通过 `st.button` + `st.rerun()` 实现,功能正常。
- **缺失**:需求中未强制要求自动刷新,但可以考虑添加可选的自动刷新间隔(如每30秒),通过 `st.empty` 占位 + `time.sleep` 循环或 `streamlit-autorefresh` 组件实现。若用户未来需要可轻松扩展。
#### 6. 🧹 错误处理的健壮性
- **当前**:捕获了 `requests.exceptions.RequestException`、`KeyError`、通用 `Exception`,覆盖了大部分异常情况。
- **可改进**:
- 未处理 API 限流(HTTP 429)重试。建议使用 `time.sleep(1)` 后重试1-2次,或检查响应头 `Retry-After`。
- `KeyError` 捕获范围较大,若 `market_data` 缺失但 `current_price` 存在,仍会捕获并报错。更精细的做法是使用 `.get()` 和条件判断,例如:
```python
market_data = data.get('market_data')
if not market_data:
return None
price = market_data.get('current_price', {}).get('usd')
if price is None:
return None
```
#### 7. 📐 UI 细节
- **样式**:使用 CSS 实现涨跌颜色和箭头,简洁美观。
- **布局**:`st.columns` 比例 `[3,1.2,1.2]` 在窄屏上可能拥挤,建议增大间距或设置 `st.columns` 的 `gap="large"`。
- **刷新按钮**:放置于单独列,`use_container_width=True` 使按钮填满列宽,体验良好。可微调列宽度比例,使按钮更明显(如 `[1, 1, 4]`)。
### 最终建议
- **优先级高**:修复重复错误提示、使用 API 真实更新时间。
- **优先级中**:考虑切换至更轻量 API 端点或保留当前并增加 API 限流重试。
- **优先级低**:可选添加自动刷新功能、微调 UI 布局。
### 审查结论
代码整体质量优秀,核心功能已正确实现,错误处理和 UI 均达到要求。按上述建议调整后可进一步提升专业性和用户体验。
代码审查完成,请用户代理测试。
用AI最终输出的代码,试着跑一下:

