news 2026/5/7 4:28:39

从PySide6到Rich+FastAPI:如意Agent终端版架构重构全记录

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从PySide6到Rich+FastAPI:如意Agent终端版架构重构全记录

我是张大鹏,做了十多年人工智能,带过不少项目。说实话,最难的不是把功能做出来,是在需求变化时让架构跟得上。最近如意Agent经历了一次彻底的架构转型——从桌面GUI全面转向终端版,采用前后端分离架构。本文记录这次重构的完整思路和实现细节。


一、为什么要推倒重来

如意Agent最初是基于PySide6的桌面应用。PySide6确实好用,信号槽机制成熟,QSS样式灵活,我们甚至做了8套主题皮肤。

但跑了几个月后,问题逐渐暴露:

问题具体表现影响
打包体积PyInstaller + Qt 依赖,单文件 180MB+分发困难,更新成本高
跨平台Windows/Mac/Linux 表现不一致维护三套UI代码
远程使用必须在本地运行,无法远程调用服务器场景完全不可用
测试成本GUI自动化测试脆弱,CI/CD 难集成每次发版手工验证
资源占用运行时内存 200MB+低配机器卡顿明显

最致命的是部署场景。有用户想在服务器上跑如意Agent作为后台服务,但桌面GUI在 headless 环境下直接报错。我们不得不告诉他们:“先装个桌面环境。”

这显然不合理。

二、新架构的核心思路

重构目标很明确:让Agent回归服务本质,UI只是多种消费方式之一

新架构采用“共享后端 + 多端前端”模式:

┌─────────────────┐ HTTP/WebSocket ┌─────────────────┐ │ ruyi-cli │ ◄──────────────────────► │ ruyi-server │ │ (Rich终端UI) │ │ (FastAPI服务) │ └─────────────────┘ └────────┬────────┘ │ ┌────────────────────────────┼────┐ │ │ │ ┌─────┴─────┐ ┌──────┴───┐ │ │ Web │ │ Mobile │ │ │ (Vue3) │ │ (未来) │ │ └───────────┘ └──────────┘ │ │ ┌─────────────┴─────┐ │ Core Agent │ │ (agentmain.py) │ └───────────────────┘

技术选型

层级技术选型理由
后端服务FastAPI + Uvicorn异步支持好,自动API文档,WebSocket原生支持
终端UIRich + TyperPython终端渲染天花板,比很多GUI还好看
配置管理YAML人机双友好,注释支持,层级清晰
进程通信HTTP + WebSocket松耦合,支持远程,调试方便

三、后端服务层实现

后端是独立进程,负责承载核心Agent和对外提供API。

3.1 FastAPI应用入口

ruyi-server/src/server/app.py

fromfastapiimportFastAPIfromfastapi.middleware.corsimportCORSMiddlewareimportuvicornimportthreadingfromserver.configimportload_server_configfromserver.routesimportchat,system,llmfromagentmainimportGeneraticAgentfromstorage.chatimportmake_chat_repo# 加载配置config=load_server_config()# 创建FastAPI应用app=FastAPI(title="如意Agent API",version="0.1.5",description="如意Agent 后端服务 API")# CORS配置,支持跨域调用app.add_middleware(CORSMiddleware,allow_origins=config["cors"]["allow_origins"],allow_credentials=True,allow_methods=["*"],allow_headers=["*"],)# 全局状态管理classAppState:def__init__(self):self.agent:GeneraticAgent|None=Noneself.chat_repo=Noneself.active_tasks:dict={}app.state.app_state=AppState()# 注册路由app.include_router(chat.router)app.include_router(system.router)app.include_router(llm.router)@app.on_event("startup")asyncdefstartup_event():"""应用启动时初始化Agent"""agent=GeneraticAgent()chat_repo=make_chat_repo()agent.set_chat_persistence(chat_repo)# 后台线程运行Agentthreading.Thread(target=agent.run,daemon=True).start()app.state.app_state.agent=agent app.state.app_state.chat_repo=chat_repoprint(f"[Server] Agent 初始化完成,当前模型:{agent.get_llm_name()}")

关键点:

  • Agent运行在后台线程,主线程处理HTTP请求,互不阻塞
  • 全局状态通过app.state共享,避免全局变量污染
  • CORS全开放,方便前端开发和跨域调用

3.2 配置分离

服务端配置独立为config/server.yaml

server:host:"0.0.0.0"port:8000workers:1cors:allow_origins:["*"]allow_credentials:trueallow_methods:["*"]allow_headers:["*"]llm:default_provider:"kimi"fallback_providers:["openai","deepseek"]storage:chat_db_path:"data/chat.db"log_level:"INFO"

配置加载用标准YAML解析,约30行代码:

importyamlfrompathlibimportPathdefload_server_config(config_path:str|None=None)->dict:ifconfig_pathisNone:config_path="config/server.yaml"withopen(Path(config_path),"r",encoding="utf-8")asf:returnyaml.safe_load(f)

3.3 Chat路由设计

聊天是核心功能,支持两种模式:

同步模式(REST API):

@router.post("/send")asyncdefsend_message(request:SendMessageRequest)->dict[str,str]:state=app.state.app_stateifstate.agentisNone:raiseHTTPException(status_code=503,detail="Agent未初始化")conv_id=request.conversation_idorstr(uuid.uuid4())task_id=str(uuid.uuid4())# 异步处理任务asyncdefprocess_task()->None:display_queue=state.agent.put_task(request.message,source="api")whileTrue:try:chunk=display_queue.get(timeout=0.1)ifchunkisNone:break# 收集响应片段state.active_tasks[task_id]["chunks"].append(str(chunk))exceptqueue.Empty:ifnotstate.agent.is_running:breakawaitasyncio.sleep(0.05)asyncio.create_task(process_task())return{"task_id":task_id,"conversation_id":conv_id}

流式模式(WebSocket):

@router.websocket("/ws/{task_id}")asyncdefwebsocket_endpoint(websocket:WebSocket,task_id:str):awaitwebsocket.accept()state=app.state.app_statetry:whileTrue:iftask_idinstate.active_tasks:task=state.active_tasks[task_id]# 发送已收集的chunkschunks=task["chunks"]forchunkinchunks:awaitwebsocket.send_text(chunk)iftask["status"]=="completed":awaitwebsocket.send_text("[DONE]")breakawaitasyncio.sleep(0.1)exceptWebSocketDisconnect:print(f"[WebSocket] 客户端断开:{task_id}")

WebSocket的设计很务实:Agent内部用Queue生产数据,WebSocket循环消费并推送给客户端。不追求零延迟,保证不丢消息、不乱序

四、终端客户端实现

终端版不是简陋的print,而是基于Rich的现代化TUI。

4.1 为什么选Rich

Rich的能力远超预期:

  • Markdown渲染:代码高亮、表格、引用块,全部原生支持
  • Panel布局:消息气泡、系统提示,用Panel轻松实现
  • Spinner/Progress:Agent思考时显示动画,体验接近GUI
  • 颜色主题:256色支持,暗色主题下的显示效果非常舒服

4.2 三段式流式显示

这是终端版最核心的UX创新。Agent的响应分为三个阶段:

# thinking 阶段🔍 正在分析问题...# summary 阶段💡 关键结论:建议采用方案B,因为...# answer 阶段详细解释...代码示例...

Rich的Live组件让流式更新很流畅:

fromrich.liveimportLivefromrich.panelimportPanelfromrich.markdownimportMarkdownwithLive(console=console,refresh_per_second=10)aslive:forchunkinstream_response():ifchunk["type"]=="thinking":content=f"🔍{chunk['content']}"elifchunk["type"]=="summary":content=f"💡{chunk['content']}"else:content=chunk["content"]live.update(Panel(Markdown(content),title="如意Agent"))

4.3 CLI命令结构

Typer构建命令行入口:

importtyperfromrich.consoleimportConsole app=typer.Typer(help="如意Agent 终端客户端")console=Console()@app.command()defchat(server:str=typer.Option("http://localhost:8000","--server","-s"),model:str=typer.Option(None,"--model","-m")):"""启动交互式聊天会话"""client=RuyiClient(base_url=server)session=ChatSession(client,model=model)session.run()@app.command()defstatus(server:str=typer.Option("http://localhost:8000","--server","-s")):"""查看Agent运行状态"""client=RuyiClient(base_url=server)info=client.get_status()console.print(f"模型:{info['model']}")console.print(f"状态:{info['status']}")if__name__=="__main__":app()

五、重构过程中的关键决策

5.1 为什么不是TUI框架(Textual)?

Textual确实更强大,但我们评估后放弃:

维度RichTextual
学习成本低(熟悉print即可上手)高(需要理解组件树、事件循环)
调试难度低(print可辅助调试)高(屏幕刷新会覆盖print)
灵活性高(自由控制输出)中(受框架约束)
包体积小(核心仅依赖)大(额外依赖)

Rich的"增强版print"哲学更符合我们的需求:渐进增强,随时可回退到基础模式

5.2 进程间通信为什么不用gRPC?

gRPC性能更好,但HTTP/JSON在调试和开发体验上碾压:

  • curl直接测试API
  • 浏览器打开http://localhost:8000/docs看Swagger文档
  • 错误信息JSON可直接阅读

对于AI Agent场景,开发效率 > 极致性能。瓶颈在LLM API调用,不在内部通信。

5.3 保留的核心资产

重构不是重写,核心层完全保留:

  • agentmain.py—— Agent主逻辑
  • llmcore.py—— LLM路由与调用
  • agent_loop.py—— 执行循环
  • storage/—— 持久化层(刚做完chat.db迁移)
  • logstack/—— 结构化日志
  • memory/—— 记忆系统

删除的只有UI层:src/desktop/src/pet/src/frontends/

六、重构收益

指标重构前(桌面版)重构后(终端版)变化
打包体积180MB+15MB-92%
启动时间3-5秒<1秒-80%
内存占用200MB+40MB-80%
CI/CD集成困难原生支持质变
远程部署不支持开箱即用质变
跨平台需分别测试Python标准库质变

最意外的收获是测试覆盖率。终端版可以全量跑E2E测试,桌面版只能测核心逻辑。重构后测试从 600+ 提升到785 passed,0 skipped

总结

维度内容
核心思路共享后端 + 多端前端,Agent回归服务本质
关键技术FastAPI(后端)、Rich+Typer(终端)、YAML(配置)
关键决策HTTP/JSON优于gRPC,Rich优于Textual,保留核心层
注意事项终端版适合服务器/开发场景,桌面版可基于Web技术重建

这次重构验证了一个原则:架构要服务于场景,不要服务于技术栈本身。PySide6不是不好,是不适合如意Agent当前的发展阶段。当用户从"本地尝鲜"转向"生产部署"时,轻量、可远程、可集成的架构才是正解。


参考资料:

  • FastAPI官方文档
  • Rich文档
  • Typer文档
  • 如意Agent终端版实施计划:docs/superpowers/plans/2026-05-06-terminal-version.md

作者:张大鹏
日期:2026-05-06
团队:大鹏AI教育
GitHub:项目地址(含完整源码)

相关推荐:

  • 如意Agent六边形架构改造:从单体巨石到端口适配器
  • 如意Agent对话持久化与滚动记忆引擎设计
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/7 4:27:34

real-anime-z多语言支持实战:中英混合提示词生成精准二次元角色效果

real-anime-z多语言支持实战&#xff1a;中英混合提示词生成精准二次元角色效果 1. 平台介绍 real-anime-z是一款专为二次元插画创作设计的文生图工具&#xff0c;能够快速生成高质量的动漫角色、头像、海报等视觉内容。这个镜像已经预装了所有必要的模型和组件&#xff0c;用…

作者头像 李华
网站建设 2026/5/7 4:26:48

突破LLM上下文限制:openclaw-memory-kit记忆增强套件实战指南

1. 项目概述&#xff1a;一个为开源AI模型打造的“记忆增强套件”如果你最近在折腾那些开源的、可以本地部署的大语言模型&#xff08;LLM&#xff09;&#xff0c;比如Llama、Qwen或者ChatGLM&#xff0c;你大概率会遇到一个共同的痛点&#xff1a;上下文长度限制。模型本身可…

作者头像 李华
网站建设 2026/5/7 4:26:27

第三章:GEM分析:3.3 drm_mm 范围分配器——设计与接口

本节介绍 drm_mm 的设计思想、数据结构和 API 接口。关于内部实现的源码逐行分析(哨兵节点、hole 分裂/合并、对齐算法、驱逐扫描状态机、调试子系统),请见下一篇 3.3.1 drm_mm 实现分析。 1. 概述与作用 1.1 基本定位 drm_mm 是 Linux DRM子系统提供的通用范围分配器(Ra…

作者头像 李华
网站建设 2026/5/7 4:19:30

基于MCP协议的AI语音对话系统:VoiceMode架构解析与实战部署

1. 项目概述&#xff1a;解放双手的AI语音对话新范式作为一名长期与代码和命令行打交道的开发者&#xff0c;我深知长时间盯着屏幕、双手离不开键盘的疲惫感。很多时候&#xff0c;一个灵光乍现的想法&#xff0c;或者一段复杂的逻辑梳理&#xff0c;恰恰发生在你无法立刻坐下敲…

作者头像 李华