基于 CrewAI 与 CopilotKit AG-UI 协议的实时股票组合分析 Agent 实战指南
【免费下载链接】ai-engineering-hubIn-depth tutorials on LLMs, RAGs and real-world AI agent applications.项目地址: https://gitcode.com/GitHub_Trending/ai/ai-engineering-hub
导读
本文围绕 ai-engineering-hub 仓库中的stock-portfolio-analysis-agent项目,完整讲解如何构建一个实时流式输出分析流程的 AI 股票组合分析 Agent:后端使用 CrewAI 编排多阶段分析工作流(意图解析 → 行情拉取 → 组合分配 → 牛熊洞察),前端使用 React/Next.js 搭建可交互图表界面,并通过 CopilotKit 的AG-UI Protocol将工具调用、进度日志与中间结果以事件流(SSE)方式实时推送到浏览器。读完本文,你将掌握 AG-UI 事件驱动的 Agent 流式架构、CrewAI Flow 编排方法、基于 yfinance 的投资回测/分配模拟实现,以及一套可直接复制运行的前后端联调方案。
项目总览与技术栈
该项目演示了一条完整的"AI Agent + 实时可视化"链路:用户在前端输入一句自然语言投资请求(例如 "Analyze AAPL and MSFT with $10k each"),Agent 立即开始工作——拉取历史行情、计算组合分配、生成牛熊分析——而这一切都以事件流的形式实时呈现在 UI 上,用户无需等待最终结果,可以"看着 Agent 干活"。
核心技术栈(依据 README.md 与 pyproject.toml):
| 层级 | 技术 | 职责 |
|---|---|---|
| 前端 UI | React + Next.js 15 | 交互式投资仪表盘与聊天面板 |
| 后端 API | FastAPI + Uvicorn | 提供/crewai-agent流式接口 |
| 流式协议 | CopilotKit + AG-UI Protocol | 实时传输 Agent 事件(SSE) |
| Agent 编排 | CrewAI Flow | 多阶段工作流调度 |
| 市场数据 | yfinance + pandas/numpy | 行情下载与收益计算 |
后端依赖在 pyproject.toml 中锁定:crewai>=0.140.0、copilotkit>=0.1.52、ag-ui-protocol>=0.1.7、fastapi>=0.115.14、yfinance>=0.2.64、pandas>=2.3.0,Python 版本要求>=3.12,<3.13。
环境搭建与运行
1. 安装依赖
项目使用uv管理 Python 依赖,前端使用 npm 管理 Node 依赖:
# 在仓库根目录安装后端依赖 uv sync # 安装前端依赖 cd frontend npm install cd ..提示:
stock-portfolio-analysis-agent目录下同时存在uv.lock与pnpm-lock.yaml,README 推荐使用npm install安装前端依赖;若你偏好 pnpm,也可使用pnpm install并基于 frontend/package.json 中的脚本运行。
2. 配置环境变量
需要两个.env文件,分别供后端与前端使用:
后端agent/.env:
OPENAI_API_KEY=your-key前端frontend/.env:
OPENAI_API_KEY=your-openai-key NEXT_PUBLIC_CREWAI_URL=http://127.0.0.1:8000/crewai-agent其中NEXT_PUBLIC_CREWAI_URL告诉前端代理层后端流式接口的位置。在 frontend/src/app/api/copilotkit/route.ts 中,HttpAgent会读取该变量并回退到默认值http://0.0.0.0:8000/crewai-agent:
const crewaiAgent = new HttpAgent({ url: process.env.NEXT_PUBLIC_CREWAI_URL || "http://0.0.0.0:8000/crewai-agent", });3. 启动应用
# 终端一:启动后端(默认端口 8000) uv run python agent/main.py # 终端二:启动前端(默认端口 3000) cd frontend npm run dev后端入口 agent/main.py 中main()读取环境变量PORT(默认8000),并以host="0.0.0.0"、开发热重载reload=True启动 Uvicorn。
4. 调整后端地址(可选)
README 明确提示:前端默认假定后端运行在本机。若你修改了后端 host/port,需要同步更新前端 API 调用配置(即上面的NEXT_PUBLIC_CREWAI_URL),否则前端无法建立到后端的代理连接。
后端架构:FastAPI + AG-UI 事件流
5. 状态管理:继承 CopilotKitState
后端用自定义的AgentState贯穿整个分析流程,它继承自CopilotKitState(后者进一步继承 LangGraph 的MessagesState),见 agent/main.py:
class AgentState(CopilotKitState): tools: list messages: list be_stock_data: Any # 拉取到的行情 DataFrame be_arguments: dict # 从用户输入中解析出的投资参数 available_cash: int # 可用现金 investment_summary: dict # 分配/收益/基准对比结果 tool_logs: list # 供 UI 展示的进度日志这份状态在/crewai-agent接口中初始化,并在工作流各阶段被持续读写,最终回流到前端用于渲染。
6. 流式接口:事件生成器与 SSE
核心接口POST /crewai-agent接收前端传来的RunAgentInput(含用户消息、工具、thread_id/run_id、当前 state),并返回media_type="text/event-stream"的StreamingResponse,见 agent/main.py。
整个事件流由异步生成器event_generator()驱动,其关键机制如下:
- 事件编码:
EventEncoder将 AG-UI 事件编码为 SSE 格式; - 事件队列:
asyncio.Queue作为工作流与流式循环之间的桥梁,工作流通过emit_event回调把StateDeltaEvent等事件put_nowait入队; - 异步编排:
asyncio.create_task(StockAnalysisFlow().kickoff_async(...))在后台运行 CrewAI 工作流,主循环以asyncio.wait_for(event_queue.get(), timeout=0.1)轮询队列并逐条转发。
事件流生命周期包含以下 AG-UI 事件类型(见 agent/main.py):
RunStartedEvent:通知客户端一次运行开始(携带 thread_id/run_id);StateSnapshotEvent:推送初始快照(available_cash、investment_summary、investment_portfolio,并清空 tool_logs);StateDeltaEvent:工作流推进时增量更新/tool_logs、/investment_portfolio等路径;ToolCallStartEvent/ToolCallArgsEvent/ToolCallEndEvent:渲染图表工具render_standard_charts_and_table的调用与参数(内含完整的investment_summary);TextMessageStartEvent/TextMessageContentEvent/TextMessageEndEvent:普通文本回复,内容被切成最多 100 个分片、每片间隔 50ms 推送,形成打字机效果;RunFinishedEvent:标记运行结束。
7. 智能节流:先出图表、后出洞察
值得注意的一个工程细节:主循环中实现了"事件节流"逻辑(agent/main.py),目的是先让图表数据完整送达,再放行后续洞察内容,避免 UI 渲染抖动:
- 当检测到
/tool_logs某条日志被替换为completed,或检测到TOOL_CALL_ARGS中包含render_standard_charts_and_table时,标记chart_data_sent = True; - 此后,
/investment_portfolio的更新继续放行,而包含insights/processing/extracting的增量事件被拦截; - 若图表已发送而工作流仍在生成洞察,则短暂 sleep 后提前结束流,让图表先进入可交互状态。
这个设计说明:流式 Agent 不仅要"能推送事件",还要能按用户感知的优先级编排事件顺序,这是该示例区别于普通聊天流的关键点。
Agent 工作流:CrewAI Flow 的六个阶段
工作流主体是 agent/stock_analysis.py 中的StockAnalysisFlow,使用 CrewAI Flow 的@start、@listen、or_装饰器串联:
start → chat → simulation → allocation → insights → end └──────────── chat(未解析出投资参数时直通 end)各阶段职责与关键实现如下。
阶段一:start——注入组合上下文
@start()方法(stock_analysis.py)将当前投资组合 JSON 替换进系统提示词模板中的{PORTFOLIO_DATA_PLACEHOLDER}占位符(提示词见 agent/prompts.py),使 LLM 从一开始就"知道"用户已经持有哪些股票,从而正确处理"追加买入"而非"替换持仓"。
阶段二:chat——意图解析与结构化提取
chat()(stock_analysis.py)负责把自然语言翻译成结构化投资参数:
- 在
tool_logs中追加 "Analyzing user query" 日志并通过StateDeltaEvent(op: "add",path: "/tool_logs/-")推送给 UI; - 调用 OpenAI
gpt-4o-mini,并挂载函数调用工具extract_relevant_data_from_user_prompt; - 若
finish_reason == "tool_calls",将工具调用转换为内部格式(convert_tool_call),追加AssistantMessage与ToolMessage,返回"simulation"进入下一阶段; - 若没有触发工具调用,说明用户只是在闲聊,追加普通助手消息并返回
"end"直接收尾。
extract_relevant_data_from_user_prompt工具(stock_analysis.py)的参数 schema 是理解整个系统的关键:
| 参数 | 类型 | 说明 |
|---|---|---|
ticker_symbols | string[] | 股票代码列表,如['AAPL', 'GOOGL'](必填) |
investment_date | string (date) | 投资起始日期,如'2023-01-01'(必填) |
amount_of_dollars_to_be_invested | number[] | 每只股票的投入金额,与 ticker 列表一一对应(必填) |
interval_of_investment | enum | 1d/5d/7d/1mo/3mo/6mo/1y/2y/3y/4y/5y/single_shot,未指定时默认single_shot |
to_be_added_in_portfolio | boolean | 是否加入真实组合(false 表示进入沙盒组合)(必填) |
同时,agent/prompts.py 中的系统提示词对工具调用行为做了强约束:一次调用传入多个 ticker,而不是每个 ticker 调用一次;对于组合修改(增/删/替换)分别规定返回"完整最新列表 / 剔除后的列表 / 仅新股票列表"。
阶段三:simulation——行情拉取
simulation()(stock_analysis.py)负责真实市场数据的获取与预处理:
- 解析上一阶段遗留的
be_arguments,将新投资与既有组合做加性合并(existing_portfolio + new_investments),并通过StateDeltaEvent(op: "replace",path: "/investment_portfolio")实时更新前端; - 日期校验:投资日期距今超过 4 年则自动修正为
当前年-4-01-01(yfinance 数据可得性限制),并据此推导history_period(如1y、2y……); - 用
yf.download(all_tickers, start=..., end=..., interval="3mo")拉取全部 ticker(含既有持仓)的季度收盘价,存储到self.be_stock_data(data["Close"]DataFrame); - 若数据为空则直接
return "end"兜底,否则继续到 allocation。
注意一个产品约束:README 与前端初始话术(prompt-panel.tsx)都明确提示"AI agent 只能访问过去 4 年的行情数据"——这与源码中的 4 年截断逻辑一致。
阶段四:allocation——组合分配与收益模拟
allocation()(stock_analysis.py)是整个系统最核心的"算钱"环节,包含以下要点:
两种投资策略(由interval_of_investment决定):
- single_shot(一次性买入):仅取行情第一个日期,为每个 ticker 用
allocated // price整数除法买入整股,现金不足时记录add_funds_dates; - DCA(定投):遍历行情所有日期,只要有可用现金就按
total_cash // price尽可能买入,逐笔写入investment_log; - 若用户只给了一个金额但包含多个 ticker,代码会自动等额拆分(
amount_per_ticker = amounts[0] / len(tickers))。
收益与分配指标:对每个 ticker 计算已投入金额、持仓市值、绝对收益、百分比分配(invested / total_invested * 100)与百分比收益((holding_value - invested) / invested * 100),汇总成investment_summary,包含holdings、final_prices、cash、returns、total_value、investment_log、add_funds_needed、add_funds_dates、total_invested_per_stock、percent_allocation_per_stock、percent_return_per_stock等字段。
SPY 基准对比:下载同期 SPY(标普 500 ETF)日线数据,用与组合相同的策略(single-shot 一次买入或 DCA 等额分批)模拟投入同等资金,逐日期计算组合净值与 SPY 净值,生成performanceData: [{date, portfolio, spy}, ...],为前端折线图提供对比数据。源码对日期对齐做了处理(SPY 起始日早于组合数据时,将 stock_data 截断到 SPY 首个可用日期,并用reindex(..., method="ffill")前向填充),取数失败时回退为占位 Series。
图表触发:本阶段末尾,工作流在messages中追加一条携带render_standard_charts_and_table工具调用的AssistantMessage(参数为完整investment_summary),这一工具调用随后被后端流式循环转换为ToolCallStart/Args/End事件推给前端,从而触发 UI 渲染图表。
阶段五:insights——牛熊洞察生成
insights()(stock_analysis.py)调用gpt-4o-mini并挂载generate_insights工具,为当前 ticker 列表生成平衡的多空观点:
generate_insights工具(stock_analysis.py)要求输出bullInsights与bearInsights两组结构化数据,每组项包含title、description、emoji三个必填字段;- 拿到洞察后,将其合并进图表工具调用的参数(
args_dict["insights"] = ...),这样图表渲染和洞察可以在同一次工具调用参数中一起到达前端; - 失败兜底:洞察生成异常时置空
self.state['state']["insights"] = {}。
阶段六:end——收尾
end()监听or_("chat", "insights")(stock_analysis.py):无论是"chat 阶段未解析出投资参数"还是"insights 阶段完成",都会进入此步返回完整 state,随后由主循环清空 tool_logs 并发送RunFinishedEvent。
前端架构:CopilotKit 驱动的实时画布
8. 前端数据流
前端入口 frontend/src/app/page.tsx 通过 CopilotKit React 核心 Hook 与后端 Agent 建立双向连接:
useCoAgent:声明 Agent 名称crewaiAgent并注入初始状态(available_cash: 1000000、空investment_summary、空investment_portfolio),见 page.tsx;useCoAgentStateRender:订阅后端流式推送的状态渲染ToolLogs组件,将 "Analyzing user query / Gathering Stock Data / Allocating cash / Extracting Key insights" 等实时进度以动态卡片呈现(见 tool-logs.tsx,processing 态为黄色脉冲动画、completed 态为绿色对勾);useCopilotAction:声明render_standard_charts_and_table与render_custom_charts两个前端动作,前者渲染折线图 + 柱状图 + 分配表并提供 Accept/Reject 交互按钮(page.tsx),后者用于沙盒组合的自定义图表对比;useCopilotReadable:把当前investment_portfolio暴露给 Copilot 上下文,让对话模型"看得到"组合状态(page.tsx);useCopilotChatSuggestions:基于 frontend/src/utils/prompts.ts 中的INVESTMENT_SUGGESTION_PROMPT生成 3~5 条可点击的投资建议(增持/减持/替换格式,金额范围 5,000~50,000 美元,建议日期不早于 2020 年且距今至少 6 个月)。
9. 界面布局与可视化组件
页面采用三栏布局(page.tsx):
- 左栏 PromptPanel:CopilotChat 聊天面板,展示可用现金与初始化引导语(prompt-panel.tsx);
- 中栏 GenerativeCanvas:渲染 Performance(折线图)、Allocation(分配表)、Returns(柱状图)、Market Insights(牛/熊洞察卡片)与 Custom Charts 区块(generative-canvas.tsx);
- 顶栏 CashPanel:展示并支持编辑 Total Cash、Invested、Portfolio Value、4-Year Return 与 Portfolio Allocation 进度条(cash-panel.tsx)。
图表组件基于Recharts(recharts@^3.0.2,见 frontend/package.json),位于frontend/src/app/components/chart-components/:line-chart.tsx(组合 vs SPY 净值曲线)、bar-chart.tsx(各 ticker 收益)、allocation-table.tsx(Ticker/% /Value/Return 表格)、insight-card.tsx(牛熊卡片)。
10. Next.js 代理路由
frontend/src/app/api/copilotkit/route.ts 中,前端通过CopilotRuntime注册crewaiAgent的HttpAgent,并借助copilotRuntimeNextJSAppRouterEndpoint把/api/copilotkit端点与OpenAIAdapter组合起来,实现浏览器 → Next.js 路由 → FastAPI 后端的请求转发与 SSE 回流。
端到端使用流程
- 打开 UI:浏览器访问
http://localhost:3000,左侧聊天面板会显示 Agent 的自我介绍与示例引导("Invest in Apple with 10k dollars since Jan 2023",并注明仅支持过去 4 年数据); - 发起分析:在输入框提交类似
"Analyze AAPL and MSFT with $10k each"的投资请求。可观察以下实时事件流:- ToolLogs 依次出现并点亮 "Analyzing user query → Gathering Stock Data → Allocating cash → Extracting Key insights";
- 组合面板的 investment_portfolio 被增量更新(新 ticker 追加);
- 图表工具调用到达后,Performance 折线图(组合 vs SPY)、Returns 柱状图、Allocation 表格渲染完成,并出现 Accept/Reject 按钮供用户确认(page.tsx);
- 接受后,牛/熊洞察卡片显示在 Market Insights 区块,CashPanel 更新总现金、已投入与 4 年回报;
- 查看结果:若投资参数未被解析(例如纯闲聊消息),Agent 会以打字机效果输出普通文本回复,不触发任何图表。
开发注意事项与扩展方向
- 解释器环境:若编辑器报缺失导入,请确保其指向安装了依赖的同一 Python 环境(
uv、venv、conda均可);README 推荐在仓库根目录执行uv sync。FastAPI 应用位于agent/main.py,核心工作流逻辑位于agent/stock_analysis.py。 - 模型配置:意图解析与洞察生成硬编码使用
gpt-4o-mini(见 stock_analysis.py 与 #L978-L986),依赖OPENAI_API_KEY;如需更换模型,修改这两处model=参数即可,但需确认目标模型兼容 OpenAI 函数调用格式。 - 数据边界:行情仅覆盖最近 4 年、粒度为季度(
interval="3mo"),SPY 基准使用日线;如需更精细回测,可调整 simulation 阶段 的 interval 参数。 - 可扩展方向:从源码结构看,前端已预留
render_custom_charts动作与sandBoxPortfolio状态,可以在此基础上扩展"沙盒组合 vs 真实组合"的对比分析;也可以将extract_relevant_data_from_user_prompt的 enum 值扩展更多投资频率。
总结
stock-portfolio-analysis-agent是一个值得完整研读的端到端示例:它把AG-UI 事件协议(事件类型、SSE 编码、增量状态)、CrewAI Flow 多阶段编排、yfinance 行情模拟与 SPY 基准对比、CopilotKit React 前端渲染四层技术无缝串联,并额外展示了"事件节流优先渲染图表"这类贴近真实产品体验的工程技巧。无论你是要构建实时可观测的 Agent 应用,还是需要一套可复用的金融分析 Agent 参考实现,都可以从 agent/main.py、agent/stock_analysis.py 与 frontend/src/app/page.tsx 这三处源码入手,逐步拆解其设计并迁移到自己的项目中。
【免费下载链接】ai-engineering-hubIn-depth tutorials on LLMs, RAGs and real-world AI agent applications.项目地址: https://gitcode.com/GitHub_Trending/ai/ai-engineering-hub
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考