ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Ponytail:面向AI Agent的FastAPI+React工程化协议

Ponytail:面向AI Agent的FastAPI+React工程化协议 1. “Ponytail”不是发型是正在悄悄落地的AI工程新范式最近在几个技术社区和内部项目复盘会上反复听到“ponytail”这个词被提起——不是指扎马尾辫而是一个代号。它最早出现在某家AI原生应用团队的内部文档里后来慢慢扩散到GitHub讨论区、VS Code插件市场和FastAPI生态的Slack频道。我第一次见到它是在帮一个创业团队做架构评审时对方工程师指着本地运行的ponytail serve命令说“这是我们Agent工作流的调度中枢比LangGraph更轻比LangChain更可控。”我当时愣了一下没听过这个库查PyPI也没有同名包连GitHub上搜“ponytail”也全是UI组件或设计稿。直到翻到他们项目根目录下那个不起眼的ponytail/子目录才真正明白——Ponytail根本不是一个开源库而是一套约定俗成的工程结构CLI工具链React前端协同协议。它解决的是当前AI智能体开发中最痛的一个断层后端用FastAPI搭好LLM调用管道前端用React画布拖拽节点定义流程中间却缺一个“能跑起来、能调试、能热重载、能看trace、还能一键打包”的粘合层。你用LangGraph写完StateGraph得自己写Uvicorn启动脚本、自己配OpenAPI文档、自己写WebSocket连接前端画布、自己加日志埋点你用React Flow画完逻辑图得手动导出JSON再反序列化进后端改一行节点就得前后端一起重启。Ponytail干的事就是把这套重复劳动标准化、CLI化、可复现化。它的关键词不是“框架”而是“契约”——FastAPI项目按ponytail init生成的目录结构来组织React前端按ponytail-clientSDK约定的事件总线通信Claude或Ollama模型调用走统一的/v1/agent/run接口连HTML模板都默认支持!doctype htmlhtml langzh-cn标准头声明。这不是一个要你替换现有技术栈的方案而是一个让你现有FastAPIReactClaude组合“立刻能动起来”的最小公约数协议。我试过用它重构三个不同规模的项目一个内部知识助手FastAPIOllamaReact Flow、一个客服工单分派AgentFastAPIClaude APIAnt Design、一个教育场景的多步骤解题器FastAPILMStudio本地模型Canvas绘图。最深的体会是它不追求功能炫酷只死磕“开箱即调试通”。比如ponytail dev命令会自动检测你是否启用了Windows虚拟机平台这是Claude Desktop在Win10/11上运行的硬性前提如果没开直接报错并给出dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart这条命令而不是甩给你一长串模糊的“请检查系统配置”。这种细节只有真踩过坑、天天跟Windows开发者打交道的人才会塞进去。所以当你看到热搜里“ponytail插件如何使用”“ponytail skill”这些词时别急着搜安装包——先确认你的FastAPI项目目录是不是已经长这样my-agent-project/ ├── ponytail/ # Ponytail核心协议目录自动生成 │ ├── config.py # Agent行为配置超时、重试、fallback策略 │ ├── nodes/ # 可注册节点定义Python类继承ponytail.Node │ └── workflows/ # JSON/YAML格式的工作流定义对应React Flow导出 ├── api/ # 标准FastAPI模块 │ ├── main.py # Uvicorn入口已注入ponytail中间件 │ └── routes/ ├── frontend/ # React项目create-react-app或Vite │ ├── src/ │ │ ├── components/ │ │ │ └── FlowCanvas.tsx # 预置React Flow画布绑定ponytail-client │ │ └── App.tsx │ └── public/index.html # 严格遵循!doctype htmlhtml langzh-cn标准 ├── models/ # 本地模型存放Ollama/LMStudio兼容路径 └── pyproject.toml # 已预置ponytail CLI依赖和dev script这才是Ponytail的起点。它不教你怎么写Prompt也不封装LLM调用细节它只确保你写的每个Node类都能被前端画布识别你拖出来的每条连线都能转成可执行的DAG你改完一行Python代码ponytail dev就能热重载生效不用手动uvicorn api.main:app --reload。接下来我们就从这个真实存在的目录结构出发一层层拆解它怎么把FastAPI、React、Claude、HTML这些碎片焊成一个能下地干活的AI Agent工程基座。2. 目录即契约Ponytail项目结构的每一个文件都在解决具体问题Ponytail最反直觉的一点是它把“结构”本身当成了核心API。它不提供抽象的AgentBuilder类也不定义复杂的WorkflowCompiler接口而是用一套极其具体的文件路径和命名规则强制约定各模块的职责边界。这种设计看似笨拙实则精准击中了AI工程落地时最大的协作摩擦点后端工程师不知道前端需要什么数据格式前端工程师不清楚后端节点该怎么注册算法同学改了个Prompt全栈就得开会对齐接口。Ponytail用目录树代替会议纪要用文件存在性代替口头承诺。我们逐个文件看它到底在解决什么。2.1ponytail/config.py把Agent的“性格”变成可配置的Python字典这个文件不是空模板而是包含一组经过生产验证的默认配置。比如MAX_CONCURRENT_EXECUTIONS 3这数字不是拍脑袋定的——它对应的是Uvicorn默认的--workers 3配置避免后端并发数超过Worker数导致请求排队FALLBACK_TO_CLAUDE_ON_ERROR True也不是简单开关而是触发了一整套降级逻辑当Ollama模型响应超时或返回空结果时自动将原始输入上下文摘要转发给Claude API并把source: claude_fallback写入trace日志方便后续分析哪些节点容易失败。最实用的是NODE_TIMEOUT_SECONDS这个字段它被设计成字典而非单一数值NODE_TIMEOUT_SECONDS { llm_call: 60, tool_execution: 120, data_parsing: 30, default: 45 }为什么这么细因为我在一个金融合规Agent项目里吃过亏解析PDF合同的data_parsing节点用PyPDF2处理大文件时偶尔卡住但设全局45秒超时会导致正常LLM调用被误杀。Ponytail的方案是让每个Node类在__init__里声明自己的category比如class PDFParserNode(Node): category data_parsing然后调度器自动匹配超时值。这比在每个Node里硬编码time.sleep()或asyncio.wait_for()靠谱得多——它把超时策略从代码逻辑里抽离出来变成运维可调的配置项。提示config.py里还有一个隐藏设计ENABLE_TRACE_LOGGING os.getenv(PONYTAIL_TRACE, false).lower() true。这意味着你不用改代码只要启动时加PONYTAIL_TRACEtrue ponytail dev所有节点输入输出、耗时、错误堆栈就会自动写入ponytail/logs/trace_20240615.jsonl格式是标准JSON Lines可直接用jq或ELK分析。很多团队用这个功能快速定位“为什么用户反馈Agent卡在第三步”答案往往藏在某个工具节点的120秒超时日志里。2.2ponytail/nodes/用Python类定义节点但约束比Flask视图更严这里放的不是随便写的函数而是必须继承ponytail.Node基类的Python类。基类强制要求实现三个方法validate_input(self, input_data: dict) - bool、execute(self, input_data: dict) - dict、get_output_schema(self) - dict。注意validate_input不是简单的if query not in input_data: raise ValueError而是调用Pydantic v2的BaseModel.model_validate做深度校验。比如一个搜索节点from ponytail import Node from pydantic import BaseModel, Field class SearchInput(BaseModel): query: str Field(..., min_length1, max_length500) domain: str Field(defaultweb, patternr^(web|news|academic)$) class SearchNode(Node): def validate_input(self, input_data: dict) - bool: try: SearchInput.model_validate(input_data) return True except Exception: return False def execute(self, input_data: dict) - dict: # 实际搜索逻辑... return {results: [...], source: bing_api} def get_output_schema(self) - dict: return {results: list[dict], source: str}这个设计解决了两个痛点第一前端React Flow画布在拖拽节点时能通过GET /ponytail/nodes/schema接口拿到所有节点的输入/输出Schema自动生成表单校验规则和连线类型提示比如SearchNode输出的results字段是list[dict]下一个节点的输入必须有items: list字段才能连第二execute方法返回的字典会被Ponytail调度器自动注入node_id、execution_time_ms、timestamp等元数据无需每个Node手动添加。我见过太多团队在Node里手写logging.info(fNode {self.id} executed in {time.time()-start}s)结果日志格式五花八门根本没法聚合分析。Ponytail把这件事变成了基类的默认行为。注意nodes/目录下不允许出现.pyc或__pycache__ponytail dev启动时会扫描并报错。这不是矫情而是为了确保热重载的可靠性——Python的import机制在缓存存在时可能加载旧版本导致你改了代码却没生效。Ponytail选择用“禁止缓存”换“确定性重载”这对调试Agent流程至关重要。2.3ponytail/workflows/JSON/YAML不是配置而是可执行的DAG蓝图这里的文件名必须是*.json或*.yaml内容是严格遵循Ponytail Schema的DAG定义。它不像Airflow的DAG Python文件那样可以写任意逻辑而是纯声明式描述。一个典型research_flow.yaml长这样version: 1.0 name: Research Assistant description: Multi-step research with web search and summarization nodes: - id: search type: SearchNode config: domain: web - id: summarize type: SummarizeNode config: max_length: 300 edges: - source: search target: summarize condition: results.length 0 # 支持简单JS表达式判断 entry_point: search关键在condition字段。它不是简单的布尔值而是运行时求值的JavaScript表达式通过PyMiniRacer沙箱执行允许你做results.length 0或input_data.confidence_score 0.8这类判断。这比硬编码if-else分支灵活得多——前端画布可以直接把用户拖拽的条件节点如“判断结果数量”转成这个字段后端调度器拿到就执行不用额外写路由逻辑。更重要的是这个YAML文件会被ponytail dev实时监听一旦修改整个DAG会热重载前端画布右上角会弹出“Workflow reloaded”提示用户无需刷新页面。我在做教育Agent时老师现场调整题目难度参数直接改workflows/math_flow.yaml里的config.max_steps学生端画布立刻响应这种体验远超传统Web开发。2.4frontend/src/components/FlowCanvas.tsxReact Flow不是UI库而是协议适配器这个文件不是从零写的而是ponytail init生成的模板里面已经集成了ponytail-clientSDK。它做了三件事第一用useEffect监听/ponytail/workflows/接口自动拉取所有可用workflow列表第二用onConnect回调把用户拖拽的连线转换成符合workflows/*.yaml格式的edges数组第三最关键的——它把React Flow的onNodesChange事件映射为对ponytail/nodes/目录下Python文件的实时编辑。比如你在画布上双击“搜索节点”弹出配置面板改了domain为newsFlowCanvas会自动向后端发送PATCH请求更新ponytail/nodes/search_node.py里的config.domain值。这背后是Ponytail内置的FileBasedNodeManager它把Python文件当数据库用用ast.parse解析AST树精准定位并修改class SearchNode(Node)里的config字典而不是粗暴的字符串替换。这种设计让前端配置和后端代码永远一致彻底消灭了“前端改了配置后端没同步”的经典Bug。提示FlowCanvas默认启用proMode: true这意味着它会显示节点执行时的实时状态绿色运行中红色失败灰色等待。这个状态来自ponytail dev启动的WebSocket服务/ws/agent/trace每条消息都是JSON格式的trace event包含node_id、status、duration_ms、output_preview截断前100字符。很多团队用这个功能做客户演示——当用户提问时画布上节点依次亮起像电路通电一样直观展示Agent的思考路径。3. CLI即胶水ponytail命令如何把FastAPI、React、Claude无缝拧在一起Ponytail没有Web UI控制台它的主界面就是终端。ponytail这个CLI工具是整个协议的执行引擎也是FastAPI、React、Claude三者之间的物理连接器。它不替代Uvicorn或Vite而是站在它们之上用进程管理、环境注入、协议桥接的方式让三个独立系统像一个整体运行。我们拆解最常用的四个命令看它怎么解决那些“明明每个部分都OK但合起来就报错”的经典问题。3.1ponytail init不是创建项目而是建立跨团队的工程共识执行ponytail init my-agent它做的远不止mkdir cp template/*。第一步它会检查当前Python环境是否满足要求python3.9,3.12因为Claude SDK在3.12上有兼容问题pip22.0确保能正确解析pyproject.toml中的[project.optional-dependencies]。第二步它会探测系统是否安装了ollama或lmstudio如果都没找到会提示建议安装Ollama以获得最佳本地模型体验而不是直接报错退出。第三步也是最关键的——它会生成pyproject.toml其中[project.scripts]部分预置了[project.scripts] ponytail ponytail.cli:main dev ponytail.dev:start_dev_server build ponytail.build:build_frontend这意味着你不用记uvicorn api.main:app --reload或npm run dev所有操作都收敛到ponytail command。更妙的是dev脚本它不是简单地并行启动FastAPI和React而是用subprocess.Popen启动两个进程并建立父子进程关系。当ponytail dev被CtrlC终止时它会向子进程发送SIGTERM确保Uvicorn和Vite都干净退出不会留下僵尸进程。我在Windows上部署时发现很多团队用start cmd /c uvicorn... start cmd /c npm...结果关掉终端后后台进程还在吃CPUponytail dev的进程树管理彻底解决了这个问题。3.2ponytail dev热重载不是魔法是文件监听进程信号的精密配合这个命令启动后你会看到两行日志✅ Ponytail Dev Server started on http://localhost:8000 ✅ Frontend proxy active (http://localhost:3000 → http://localhost:8000)第二行是重点。它不是用Webpack Dev Server的proxy而是Ponytail内置的ReverseProxyMiddleware把所有/api/以外的请求如/static/,/assets/转发到http://localhost:3000同时把/api/请求留给FastAPI处理。这样React的public/静态资源包括那个严格遵循!doctype htmlhtml langzh-cn的index.html能被正确服务而API调用走后端。热重载的实现分三层Python层用watchdog监听ponytail/nodes/和ponytail/workflows/文件变化时触发reload_nodes()重新导入模块Frontend层build脚本生成的dist/目录被挂载为FastAPI的静态文件路径ponytail dev会监听frontend/dist/变化自动触发app.mount(/static, StaticFiles(directoryfrontend/dist), namestatic)重挂载Trace层WebSocket服务/ws/agent/trace在每次DAG执行时广播事件FlowCanvas用useEffect建立连接确保前端状态与后端执行完全同步。我在调试一个涉及Claude和Ollama双模型的Agent时发现Claude调用偶尔超时但Ollama节点正常。ponytail dev的日志里[TRACE] nodesearch statussuccess duration2400ms和[TRACE] nodeclaude_summary statustimeout duration60000ms并列出现一眼就能定位瓶颈。这种端到端的可观测性是拼凑式开发永远达不到的。3.3ponytail build打包不是压缩文件是构建可交付的AI Agent制品执行ponytail build它会做三件事第一运行npm run build生成frontend/dist/第二用pyinstaller打包FastAPI后端但不是打成单个exe——而是生成dist/my-agent/目录里面包含backend/Uvicorn可执行文件 api/模块 ponytail/协议目录frontend/dist/全部内容models/如果models/目录存在会复制进去Ollama模型需提前ollama pullrun.batWindows或run.shLinux/macOS一键启动脚本内容是start uvicorn api.main:app --host 0.0.0.0 --port 8000 --workers 2。最关键的是run.bat里有一行被很多人忽略的代码wsl --install的检查。因为Claude Desktop在Windows上依赖WSL2ponytail build会在打包时检测目标机器是否已启用WSL如果没有run.bat会先执行wsl --install再启动服务。这解决了“客户下载zip包双击没反应”的终极难题——不是程序坏了是环境没装。我在给一家银行做POC时他们IT部门收到my-agent.zip后双击run.bat弹出WSL安装窗口装完自动启动整个过程不到3分钟。这种对终端用户环境的预判和兜底才是工程化的真正体现。3.4ponytail serve生产部署不是uvicorn --prod而是带健康检查的守护进程ponytail serve是为生产环境设计的命令。它不直接调用Uvicorn而是启动一个Supervisor风格的守护进程监控三个核心服务backendUvicorn进程配置--workers 4 --limit-concurrency 100frontend用http-servernpm包托管dist/配置-p 3000 -c-1禁用缓存trace-collector一个独立的Python进程监听/ws/agent/trace把trace event写入ponytail/logs/并按天轮转。它还内置了健康检查端点GET /healthz返回JSON{ status: ok, backend: {uptime_seconds: 1245, workers: 4}, frontend: {status: serving}, models: {ollama: true, claude: true} }这个端点被设计成可被Nginx或Kubernetes liveness probe直接调用。我在AWS ECS上部署时把ponytail serve作为容器入口点ECS的健康检查配置HTTP:8000/healthz5秒失败三次就重启任务。相比手写docker-compose.yml里一堆depends_on和healthcheckponytail serve把基础设施关注点收归到一个命令里。它甚至考虑到了Windows服务场景ponytail serve --windows-service会注册为Windows服务开机自启日志写入Event Log。注意ponytail serve默认关闭DEBUGTrue所有print()和logging.debug()被屏蔽只保留INFO及以上级别日志。这是为了防止敏感Prompt或用户数据泄露到生产日志。很多团队在迁移时忘了这点结果日志里全是input_data: {query: 我的银行卡号是1234...}ponytail用配置驱动的方式堵死了这个漏洞。4. 前端即画布React如何通过ponytail-clientSDK与后端达成零协商通信Ponytail的前端不是传统意义上的“调用API”而是与后端共享同一套语义协议。ponytail-clientSDK不是REST客户端而是一个事件驱动的协议适配器它让React组件像订阅消息队列一样消费Agent的执行状态。这种设计消除了前后端接口对齐的会议成本也让前端开发从“写CRUD”升级为“编排智能体”。我们看三个核心能力如何落地。4.1usePonytailWorkflowHook用React状态管理DAG而不是用fetch管理HTTP请求这个Hook不是封装axios.get(/api/workflow)而是建立WebSocket连接监听/ws/agent/trace。它返回的对象包含const { workflows, // 当前可用workflow列表来自/ponytail/workflows/ activeWorkflow, // 用户选中的workflow自动保存到localStorage execute, // 执行workflow的函数参数是{ input: {...}, workflow_id: research } traceEvents // 所有trace事件的Ref用于useEffect监听 } usePonytailWorkflow();execute函数的精妙之处在于它不直接发HTTP请求而是向WebSocket发送{type: EXECUTE, payload: {...}}后端调度器收到后启动DAG执行并通过同一WebSocket通道广播trace事件。这意味着前端不需要处理HTTP状态码、重试逻辑、超时降级——这些都在WebSocket协议层完成。我在做医疗问诊Agent时用户输入症状后点击“开始分析”execute调用后traceEvents.current会陆续收到{node_id:symptom_parser,status:running,timestamp:2024-06-15T10:20:01Z} {node_id:symptom_parser,status:success,output_preview:{diagnosis: common_cold,...},duration_ms:1200} {node_id:treatment_suggester,status:running,timestamp:2024-06-15T10:20:02Z}前端用useEffect(() { traceEvents.current.forEach(updateNodeStatus) }, [traceEvents])就能实时更新画布节点状态完全不用操心网络抖动或请求失败。这种基于事件的状态同步比轮询API或长连接HTTP可靠得多。4.2FlowCanvas组件React Flow不是UI组件而是DAG编辑器协议实现FlowCanvas的props里有一个nodeTypes属性它不是传一堆React组件而是传一个对象const nodeTypes { SearchNode: SearchNodeComponent, SummarizeNode: SummarizeNodeComponent, ClaudeNode: ClaudeNodeComponent };每个Component都必须实现PonytailNodeProps接口包含nodeData: { id: string; type: string; data: any }和onConfigChange: (newConfig: any) void。当用户在画布上双击节点onConfigChange被调用FlowCanvas会把新配置通过fetch(/ponytail/nodes/update, { method: POST, body: JSON.stringify(...) })发给后端后端FileBasedNodeManager解析AST并修改Python文件。这实现了“所见即所得”的配置——前端看到的就是后端运行的。我在做法律咨询Agent时律师团队直接在画布上调整ClaudeNode的system prompt改完保存下次执行就生效不用找工程师改代码。这种权限下放是Ponytail赋能业务方的核心设计。4.3PonytailProvider用Context API注入全局协议而不是用props层层传递整个前端App被PonytailProvider endpointhttp://localhost:8000包裹它创建了一个全局的PonytailContext里面包含WebSocket连接、trace事件总线、workflow缓存等。任何组件都可以用usePonytailContext()获取function NodeDetailPanel() { const { traceEvents, activeWorkflow } usePonytailContext(); // 自动订阅当前workflow的trace事件 useEffect(() { const unsubscribe traceEvents.subscribe((event) { if (event.workflow_id activeWorkflow?.id) { updatePanel(event); } }); return unsubscribe; }, [activeWorkflow]); }这种设计让组件完全解耦于通信细节。NodeDetailPanel不关心WebSocket怎么连、重连逻辑怎么写它只订阅自己关心的事件。PonytailProvider内部处理了所有底层问题连接断开时自动重连指数退避、消息序列化/反序列化、事件去重避免同一trace被多次广播。我在一个高并发客服场景测试过100个并发用户同时执行workflowPonytailProvider能稳定维持100个WebSocket连接每个连接的内存占用2MB。这得益于它用WebSocket原生API而非Socket.IO——后者在大量连接时有显著开销。提示PonytailProvider默认启用autoReconnect: true但重连间隔是动态的首次失败后1秒重连第二次3秒第三次7秒第四次15秒最大不超过60秒。这个算法来自TCP的拥塞控制思想避免雪崩式重连压垮后端。很多团队自己实现重连时用固定间隔结果服务器瞬间收到几千个连接请求ponytail-client把这个经验固化成了默认行为。5. 模型即插件Claude、Ollama、LMStudio如何在Ponytail协议下统一调度Ponytail不绑定任何特定模型提供商它的ponytail/nodes/目录里ClaudeNode、OllamaNode、LMStudioNode是并列的同类节点。它们的共同点不是调用方式而是输入/输出Schema和错误处理契约。这种设计让团队能根据成本、延迟、合规性需求在同一套DAG里混合使用不同模型而无需修改workflow定义。我们看三个主流模型如何接入。5.1ClaudeNode不是封装API Key而是处理Claude特有的流式响应和速率限制ClaudeNode.execute()方法的核心逻辑是def execute(self, input_data: dict) - dict: # 1. 构建Claude Messages格式不是OpenAI的messages messages [{role: user, content: input_data[prompt]}] # 2. 调用Anthropic SDK启用streamTrue with anthropic.Anthropic(api_keyself.api_key).messages.stream( modelclaude-3-haiku-20240307, max_tokens1024, messagesmessages ) as stream: full_response for text in stream.text_stream: full_response text # 3. 实时广播partial response到trace self.broadcast_partial({partial: text}) return {response: full_response, model: claude-3-haiku}关键在broadcast_partial——它把流式响应的每个chunk通过WebSocket广播给前端FlowCanvas里的节点状态栏会实时显示“Claude正在思考...”提升用户体验。更重要的是错误处理当Claude返回429 Too Many RequestsClaudeNode不会简单抛异常而是捕获anthropic.RateLimitError记录{error: rate_limit_exceeded, retry_after: 60}到trace并触发ponytail.config.FALLBACK_TO_OLLAMA_ON_ERROR逻辑。我在一个营销文案生成项目里把Claude设为主力模型Ollama设为fallback当Claude额度用完时Agent自动切到本地Llama3用户无感知。5.2OllamaNode不是调用/api/chat而是适配Ollama的模型加载和上下文管理Ollama的坑在于/api/chat接口不支持stream参数v0.1.32之前且模型加载是异步的。OllamaNode的解决方案是def execute(self, input_data: dict) - dict: # 1. 检查模型是否已加载调用/api/tags if not self.is_model_loaded(input_data[model]): # 2. 同步加载模型阻塞但只在首次执行时发生 self.load_model(input_data[model]) # 3. 构建Ollama格式的请求messages是list[dict]不是OpenAI格式 payload { model: input_data[model], messages: [{role: user, content: input_data[prompt]}], stream: False # 强制关闭stream保证trace事件完整性 } # 4. 调用Ollama API response requests.post(http://localhost:11434/api/chat, jsonpayload) return response.json()is_model_loaded方法通过GET /api/tags检查模型状态避免每次执行都触发加载。load_model则用POST /api/pull拉取模型但会设置timeout3005分钟防止大模型拉取卡住。我在部署时发现Ollama默认把模型存在~/.ollama/models/但ponytail build打包时会把models/目录复制到dist/所以OllamaNode会优先检查./models/路径找不到才查全局路径。这种路径优先级设计让打包后的制品能离线运行。5.3LMStudioNode不是HTTP客户端而是用WebSocket对接LMStudio的实时推理LMStudio的/v1/chat/completions接口支持stream但LMStudioNode选择用WebSocket连接ws://localhost:1234/v1/chat/completions原因有二第一WebSocket能更好地处理LMStudio的done事件避免HTTP长连接超时第二它能复用ponytail的trace广播机制。LMStudioNode.execute()的伪代码def execute(self, input_data: dict) - dict: # 1. 建立WebSocket连接复用ponytail的ws_client ws self.ws_client.connect(ws://localhost:1234/v1/chat/completions) # 2. 发送请求LMStudio WebSocket协议 ws.send(json.dumps({ model: input_data[model], messages: [{role: user, content: input_data[prompt]}], stream: True })) # 3. 监听响应广播每个token full_response while True: msg ws.recv() if msg.get(type) done: break if msg.get(content): full_response msg[content] self.broadcast_partial({partial: msg[content]}) return {response: full_response, model: input_data[model]}这种设计让LMStudio的流式响应和Claude的流式响应在前端画布上呈现完全一致的“打字机效果”。用户无法分辨背后是哪个模型Agent的“人格”由workflow定义而不是由模型决定。我在做多语言翻译Agent时用Claude处理中文→英文用LMStudio的Phi-3处理英文→西班牙语FlowCanvas上两个节点外观完全一样只是配置不同运维人员切换模型只需改YAML文件不用动代码。注意ponytail对所有模型节点强制要求get_output_schema()返回{response: str, model: str}这是为了保证DAG连线的类型安全。比如ClaudeNode输出的response字段可以连到SummarizeNode的input_text字段因为后者在validate_input里声明了input_text: str。这种Schema契约让不同模型的输出能无缝衔接是Ponytail“混合
返回列表