banner
约 3,900 字
13 分钟

从Qwen-Agent到Nanobot搭建股票ChatBI助手

-
-
无标签

摘要

本文记录了构建股票 ChatBI 助手的完整实战过程。项目利用 Tushare 采集数据,在 Qwen-Agent 上快速验证了自然语言查询、SQL 工具及 ARIMA、布林带等分析能力。随后,在明确业务边界后,将架构迁移至工程化更强的 Nanobot 框架,并补齐 Gradio Web 界面。核心经验在于遵循“先轻后稳”路线:先用轻量框架验证思路,再进行架构迁移与体验完善。

如何搭建一个股票 ChatBI 助手

写在前面

记录AI Coding 过程及经验分享:从数据收集、数据入库、智能体原型验证,到框架迁移和 Web 界面调试。

理解业务需求,先做轻量级 demo 验证,再做架构迁移与体验完善。


项目目标

股票分析场景下的 ChatBI (Chat Business Intelligence)助手:把数据采集、存储、分析、预测和可视化串在一起,让用户可以直接通过自然语言完成查询和分析。

当前项目围绕以下几只股票做本地验证:

  • 贵州茅台

  • 五粮液

  • 广发证券

  • 中芯国际

目标中的典型能力包括:

  • 历史行情查询:收盘价、成交量、成交额、涨跌幅等

  • 多股票对比:区间涨跌幅、走势对比、统计汇总

  • 自动可视化:根据查询结果自动生成折线图或柱状图

  • 智能预测:基于 ARIMA 预测未来 N 个交易日价格

  • 技术指标分析:使用布林带识别超买、超卖和异常点

当前版本 主要聚焦于本地 SQLite 数据库、历史行情查询、ARIMA、布林带和 Gradio 界面。后续可开发像“实时新闻查询”等能力。


本项目用到的工具与框架

1. nanobot

这个项目最终运行在 nanobot 框架上。它可以理解为一个偏工程化、偏运行时的智能体骨架,一个轻量级的 openclaw 。具备以下特点:

  • 配置驱动,模型、Provider、工具开关都可以放到 config.json

  • 运行链路清晰,有 AgentLoop、上下文构建、工具循环这些明确的边界

  • 很适合做本地 CLI 型智能体,调试时比黑盒框架更容易定位问题

2. Tushare

股票历史数据来源使用 Tushare获取。

  • 通过 TUSHARE_TOKEN 环境变量读取密钥

  • 拉取指定股票从 2020-01-01 到当天的历史日线数据

  • 先保存为 .xlsx

  • 再写入本地 SQLite 数据库

这样做的好处是简单、可控、便于复现,也特别适合本地开发和提示词迭代。

3. SQLite

本地验证阶段使用 SQLite。

  • 零部署

  • 易于迁移和备份

  • 查询可控

  • 适合和 Pandas、SQLAlchemy、脚本工具链一起使用

4. Qwen-Agent

Qwen-Agent 的优势是结构相对直接,非常适合快速做一个带工具调用的聊天型助手,且功能完善,提供工程化抽象和开箱即用高级组件。

  • 代码量少,容易看懂

  • Assistant + tool + WebUI 的组合很适合快速试错

  • 适合把“自然语言 -> SQL -> 查询结果 -> 可视化”这条链先跑通

在本项目里,Qwen-Agent 更多承担的是 轻量原型框架 的角色。

5. Codex、Trae、Cursor 等 AI 编程工具

AI编程工具是AI开发的核心,或者说是新一代编程的核心,它让人由写代码转化为了读代码,提供思路,框架。但是AI编程的幻觉,针对复杂开发推荐使用TDD模式,本次开发较为简单,由Codex 直接完成。

6. 大模型 API

本项目使用的是阿里云百炼的 Qwen 系列模型,密钥通过环境变量 DASHSCOPE_API_KEY 注入。


为什么先用 Qwen-Agent,再迁移到 Nanobot

很多时候我们一上来就会问:到底该选哪个框架?但真正更有效的问题其实是:

当前阶段我最需要的是“快速验证”,还是“长期扩展”?

对于这个项目:

  • 在原型阶段,我更需要快速验证

  • 在迁移阶段,我更需要更清晰的运行架构

因此路线就自然变成了:

blog image
blog image

Qwen-Agent 负责“跑通思路”,Nanobot 负责“承接工程化版本”。当然,不止是Nanbot框架,可以根据需要迁移到不同的框架,我们需要提供一个能跑通的demo,让AI学习新框架的用法,在把当前版本代码迁移。


Qwen-Agent 框架

Qwen-Agent 框架
Qwen-Agent 框架

在本项目中的用法

在本项目原型阶段,Qwen-Agent 的角色主要是:

  • 接收用户关于股票查询的自然语言问题

  • 根据提示词理解数据库结构和查询口径

  • 生成 SQL 或调用封装好的工具

  • 自动返回表格和图表

这种方式特别适合早期验证,因为它足够直接,改动反馈也快。

你可以把它理解成一个很适合做“第一版能跑起来 demo”的框架。


Nanobot 架构简介

Nanobot 架构
Nanobot 架构

关键模块理解

config.json

这个文件定义了模型、Provider 和工具开关,是整个项目的配置入口。例如当前项目里最关键的是这几个部分:

JSON
{
  "agents": {
    "defaults": {
      "model": "qwen-plus",
      "context_window_tokens": 32768,
      "max_tool_iterations": 20,
      "timezone": "Asia/Shanghai"
    }
  },
  "providers": {
    "dashscope": { "api_key": "" }
  },
  "tools": {
    "web": { "enable": false },
    "exec": { "enable": true, "timeout": 120 }
  }
}

当前版本重点依赖本地脚本执行,因此 exec 打开,web 暂时关闭。

AgentLoop

AgentLoop 是整个运行时的核心引擎。它把消息总线、模型、工作目录、工具执行限制、上下文长度和时区等能力统一串起来。

本项目中的核心代码如下:

Python
loop = AgentLoop(
    bus=MessageBus(),
    provider=provider,
    workspace=WORKSPACE,
    model=defaults.model,
    max_iterations=defaults.max_tool_iterations,
    context_window_tokens=defaults.context_window_tokens,
    max_tool_result_chars=defaults.max_tool_result_chars,
    web_config=config.tools.web,
    exec_config=config.tools.exec,
    restrict_to_workspace=False,
    timezone=defaults.timezone,
)

从这段代码就能看出,Nanobot 比较适合做“边界清楚的智能体系统”。

ContextBuilder

ContextBuilder 类把系统规则、技能说明、当前记忆和环境上下文拼接成模型真正看到的 System Prompt。

本项目里这样实现类似功能,在启动时写入时间上下文:

Python
def inject_time_context() -> None:
    memory_file = WORKSPACE / "memory" / "MEMORY.md"
    today = datetime.now().strftime("%Y-%m-%d")
    memory_file.write_text(
        "# 当前时间上下文\n\n"
        f"当前日期:{today}\n"
        "股票日线数据为已收盘数据;预测与技术指标仅供学习和分析参考。\n",
        encoding="utf-8",
    )

AgentRunner / LLM + 工具循环

从本质上看,Nanobot 的执行过程依然是“模型判断是否需要工具,再进入工具循环”。

本项目中的 ChatBIHook 把工具调用过程打印出来,方便观察执行过程:

Python
class ChatBIHook(AgentHook):
    async def before_execute_tools(self, ctx: AgentHookContext) -> None:
        for tool_call in ctx.tool_calls:
            args_text = str(tool_call.arguments)[:160]
            print(f"  [{tool_call.name}] {args_text}")

项目具体实现步骤

第一步:用 Tushare 准备数据

让 AI 帮我写出一个数据采集脚本,从 Tushare 拉取四只股票从 2020-01-01 到今天的历史价格,并输出到 Excel 和 SQLite。

这里的思路非常直接:

Python
STOCKS = {
    "600519.SH": "贵州茅台",
    "000858.SZ": "五粮液",
    "000776.SZ": "广发证券",
    "688981.SH": "中芯国际",
}

然后通过 TUSHARE_TOKEN 读取密钥:

Python
def get_tushare_token() -> str:
    token = os.environ.get("TUSHARE_TOKEN")
    if not token:
        raise RuntimeError("环境变量 TUSHARE_TOKEN 未设置或为空。")
    return token

最后统一按时间升序排序,写入 Excel 和 SQLite。

这一步很重要,因为后面的所有自然语言查询、预测和可视化,都是建立在数据准备正确的前提上。

第二步:生成建表 SQL,并把数据落入 SQLite

建表语句很简单,但它明确了后续整个项目的数据边界:

SQL
CREATE TABLE IF NOT EXISTS stock_daily_price (
    stock_name TEXT NOT NULL,
    ts_code TEXT NOT NULL,
    trade_date TEXT NOT NULL,
    open REAL,
    high REAL,
    low REAL,
    close REAL,
    pre_close REAL,
    change REAL,
    pct_chg REAL,
    vol REAL,
    amount REAL,
    PRIMARY KEY (ts_code, trade_date)
);

对应文件是:

create_stock_daily_price.sql

这张表的设计虽然不复杂,但已经足够支撑当前版本的大部分能力:

  • 历史价格查询

  • 涨跌幅分析

  • 成交量和成交额分析

  • 时间序列建模

  • 布林带计算

第三步:先用 Qwen-Agent 做出“能聊、能查、能画图”的原型

在这个阶段,我并没有急着做复杂架构,而是先做一个轻量级原型,把“自然语言 -> 数据库 -> 结果可视化”跑通。

这一阶段的经验可以概括成一句话:

不要一开始就追求完美架构,先验证交互链路和分析口径。

原型里最重要的并不是代码写得多复杂,而是把下面几件事说清楚:

  • 数据库在哪

  • 表名是什么

  • 用户问“价格”时默认查什么字段

  • 用户问“区间涨跌幅”时如何计算

  • 哪些 SQL 允许执行,哪些绝对不允许

这也是为什么在这类项目中,Prompt 设计的价值非常高。

第四步:不断打磨 SQL 工具的返回结果

早期版本中,我对 SQL 工具做了几轮非常实用的增强:

  • 查询结果只有一行时,不做统计,也不画图

  • DataFrame 预览不只看前 10 行,而是更综合地展示头尾信息

  • 结果返回中加入描述统计,帮助模型更准确地总结

  • 自动根据数据规模选择折线图或柱状图

  • 横坐标做抽样显示,避免图表标签挤成一团

这些小改动看起来不起眼,但对用户体验和模型最终回答质量影响非常大。

真正让一个查询助手“好用”的,往往不是会不会写 SQL,而是它是否知道:

  • 什么情况该画图

  • 什么情况不该画图

  • 什么情况需要统计摘要

  • 什么情况应该把上下文压缩得更适合模型理解

第五步:补上 ARIMA 预测和布林带分析

在基础查询跑通之后,我继续加入了两个比较典型的分析能力:

ARIMA

当前项目的 ARIMA 技能说明中,约定使用最近一年收盘价,基于 ARIMA(5,1,5) 预测未来 N 个交易日价格。

技能入口命令是:

纯文本
python skills/arima-predict/scripts/run_arima.py --ts-code 600519.SH --n 7

它适合做趋势性预测验证,但必须明确写清楚:

预测结果仅供学习和分析参考,不构成投资建议。

Bollinger

布林带工具使用的是 20 日周期 + 2σ,用于检测超买和超卖点。

技能入口命令是:

纯文本
python skills/bollinger/scripts/run_bollinger.py --ts-code 600519.SH

布林带分析很适合做异常点可视化,也能让这个项目从“纯查询助手”进一步扩展为“带技术指标分析能力的助手”。

第六步:从 Qwen-Agent 迁移到 Nanobot

当整个思路在 Qwen-Agent 中被验证得比较清楚之后,我开始把项目迁移到 Nanobot 版本。

迁移的重点不是“把代码原样搬过去”,而是重新梳理运行结构:

  • 配置归拢到 config.json

  • 模型 Provider 通过 DashScope 注入

  • 通过技能文件约束不同分析能力的调用方式

  • 利用 AgentLoop 承接整个运行时

  • 保留 CLI 作为最稳定的调试入口

当前项目主入口是:

agent.py

其中最核心的构造函数是:

Python
def build_bot() -> Nanobot:
    dashscope_key = os.environ.get("DASHSCOPE_API_KEY", "")
    config = load_config(WORKSPACE / "config.json")
    config.providers.dashscope.api_key = dashscope_key
    config.agents.defaults.workspace = str(WORKSPACE)

    provider = _make_provider(config)
    defaults = config.agents.defaults

    loop = AgentLoop(
        bus=MessageBus(),
        provider=provider,
        workspace=WORKSPACE,
        model=defaults.model,
        max_iterations=defaults.max_tool_iterations,
        context_window_tokens=defaults.context_window_tokens,
        max_tool_result_chars=defaults.max_tool_result_chars,
        web_config=config.tools.web,
        exec_config=config.tools.exec,
        restrict_to_workspace=False,
        timezone=defaults.timezone,
    )

    inject_time_context()
    return Nanobot(loop)

这一步让我对整个项目的“运行时结构”有了更强的掌控感。

第七步:补上 Gradio WebUI

最后一步,是把 CLI 助手接到一个更适合演示的 Web 界面上。

当前界面入口在:

webui.py

这个阶段最有意思的,其实不是写 UI 本身,而是调试:

  • Gradio 5 和 Gradio 6 的参数不兼容

  • 图片本地路径在浏览器端 404

  • 不同 Conda 环境里的依赖版本不一致

  • Chatbot 的消息格式和复制按钮参数在不同版本下不同

最后的解决方案包括:

  • 根据 Gradio 版本动态选择 Chatbot 参数

  • 将本地生成的图表转为 Base64 内嵌图片,避免 /file=... 路由兼容问题

  • 保留独立 webui.py,不污染 CLI 入口

这一步其实很能说明一个真实项目的特点:

真正耗时间的,往往不是“写页面”,而是处理不同框架和依赖之间那些看似很小、但很影响体验的兼容细节。



本项目中我比较认可的几个实践

1. 先做本地验证

SQLite、本地图片、本地脚本、本地 CLI,这些选择看起来“朴素”,但非常适合项目初期。

因为在这个阶段,最重要的是减少外部变量,把问题压缩到真正需要思考的地方。

2. 先原型,后迁移

Qwen-Agent 让我快速验证思路,Nanobot 让我把结构理清。这种“先轻后稳”的路线,对个人项目尤其有效。

3. 用技能文件固化分析规则

当前项目里把历史 SQL、ARIMA、布林带分析分别写成技能说明,是一个很实用的做法。这样做的价值在于:

  • 分析口径更清晰

  • 调用命令更明确

  • 更容易约束模型行为

4. 把调试过程当成项目资产

这次 Gradio 的图片问题、版本兼容问题,其实都很典型。如果只是临时修掉,它们很快会再次出现;但如果把这些问题写进复盘,后面就会少踩很多坑。


一些调试心得

这次项目里,我最大的感受不是“写功能有多难”,而是:

真正困难的是在多框架、多依赖、多版本之间,持续把系统保持在一个可验证、可运行、可解释的状态。

有几条经验我觉得特别值得记下来。

1. 先看根因,不要急着补丁式修复

比如图片不显示的问题,最后根因并不是“图没生成”,而是 Gradio 对本地文件路由的处理在当前版本下返回了 404。

如果一开始只盯着前端页面,很容易误判方向。

2. 版本兼容问题永远值得优先确认

Gradio 5 和 Gradio 6 的 Chatbot 参数不同,这类问题如果不先确认版本,后面会浪费很多时间。

3. 调试日志非常重要

ChatBIHook 这种打印工具调用参数的设计,在智能体项目里非常有用。因为很多时候,模型“为什么答成这样”,必须结合它实际调了什么工具才能判断。

4. 让项目始终保留一个最稳定的入口

这也是为什么我保留了 CLI。WebUI 可以坏,前端可以兼容出问题,但 CLI 往往是最稳定的诊断入口。


这次项目的局限与后续方向

当前版本已经可以完成:

  • 本地股票历史数据查询

  • 自动图表展示

  • ARIMA 预测

  • 布林带分析

  • CLI 交互

  • Gradio WebUI 交互

但它仍然有很明确的边界:

  • 数据仍然是本地验证数据,不是生产级实时数据流

  • 当前没有真正接入实时新闻查询

  • Prophet 周期性分析尚未在这版落地

  • WebUI 更偏演示版,还不是成熟产品化前端

如果后续继续扩展,我最想补的方向会是:

  1. 接入实时行情或新闻检索能力

  2. 补充 Prophet 或更多时序分析模型

  3. 引入更稳定的前端状态管理和历史会话管理

  4. 让技能体系进一步模块化,便于横向扩展到更多金融分析场景


结语

这次项目对我来说,最重要的收获不是“又做了一个股票助手”,而是把一条很完整的 AI Coding 路线真正走了一遍:

  • 从业务需求出发

  • 用 Tushare 准备数据

  • 用 SQLite 固定本地验证场景

  • 用 Qwen-Agent 快速做出可交互 demo

  • 用 ARIMA 和布林带补足分析能力

  • 再迁移到 Nanobot,理顺运行架构

  • 最后补上 Gradio,并处理真实调试问题

回头看,这个项目最值得总结的并不是某个单点技术,而是这条方法论:

理解业务需求 -> 打造 skill 或工具能力 -> 用 Qwen-Agent 做轻量级 demo -> 明确边界后做架构迁移。

这条路线很适合个人项目,也很适合 AI 时代的软件原型开发。

如果后面我继续补实时新闻、Prophet、更多股票池、前端产品化体验,这篇文章也会成为一个很好的阶段性起点。

END

相关文章

暂无相关文章