Tracking Market Zeitgeist
一条正在原地重构的市场情报流水线。确定性的循环今天在生产环境每天跑约48次;紧挨着它的是下一代架构层——一个跨五个可替换专家(采集、富化、新颖度、评估、信号)的ReAct编排器,外加一个可回放的推理轨迹存储——已经建成、测试过,并且刻意保持关闭,因为接缝要先上线,才谈接管。一个受环境变量控制的A2A服务器把结果暴露给生态系统里的其他系统,已经通过三个上线的能力向Trader Joe承载了数千次成功的协议调用。
分享这篇案例 →难点在于:原地重构一条正在生产环境运行的流水线——每个专家都先以行为完全一致的封装形式上线,经过一致性测试验证,然后才允许编排逻辑真正改变运行方式。
ReAct orchestrator over the five replaceable Experts, gated behind a single environment flag. The production daemon has never set it, so each of the ~48 cycles a day still runs the deterministic pipeline these Experts wrap — which is exactly the migration order this rearchitecture committed to.
Python, Postgres, Dagu (daily scoring DAG), Grafana
今天的生产环境循环已经是分阶段的:main.py的循环驱动器依次运行schema初始化、股票代码解析、抓取校验、落库、富化、三个自愈式回填、评估、信号导出,每个阶段都把结果写进pipeline_runs表里的一条记录。这套结构正是重构的地基,而不是被丢弃的东西——阶段变成专家,循环驱动器变成编排器。但今天,这套结构挤在一个1047行的巨型模块里,没有稳定的接口:阶段封装函数接收的是具体函数作为参数,而不是一个可以被另一种实现满足的接口。每个专家上线的第一步都是对同一个已验证函数做纯粹的重构——行为完全一致,在任何编排逻辑被打开之前先验证过——这样一个今晚还在生产环境跑着的系统,可以在不下线的情况下演进。
采集、富化、新颖度、评估、信号,这五个专家各自实现一个最简的专家协议,并封装流水线里已经验证过的逻辑——采集专家负责抓取、校验、落库;富化专家负责L1情绪打分、L2深度阅读,外加回填;新颖度专家负责指纹去重和打分;评估专家负责对照真实市场结果算T+1/T+5准确率;信号专家负责Trader Joe今天就在直接从Postgres读取的那份导出契约。每一个都能独立单测、独立替换——未来接入Finnhub或RSS,只是多实现一个采集专家,而不是去改抓取脚本本身。它们刻意只保持一层:没有专家会派生子专家,和这个作品集里其他每一个系统一样的"编排器加专家"结构。
编排器跑的是一个有边界的"思考-行动-观察"循环,默认策略就是今天这套固定的阶段顺序——ReAct在这里只是机制,不是不确定性的许可证,一个一致性测试会验证编排器开关状态下写入数据库的结果完全一致。每一步都会写进一张新的推理轨迹表:哪个专家跑的、它考虑了什么、它做了什么、一份脱敏后的观察数据,还有一个和A2A层共用的关联ID,让一次跨系统调用和催生它的内部推理能通过同一个键对上。一个回放CLI可以从这张表里把任意一个循环完整重建出来——这是真正的审计能力,而今天,一次运行结束后只留得下计数和耗时,推理过程本身是留不下来的。这一层今天还没有在生产环境跑起来:编排器挂在一个环境变量开关后面,而生产守护进程从来没有打开过它,所以今天每天约48次循环走的仍然是这些专家所封装的确定性路径,那张轨迹表也还是空的。这个顺序是刻意的——先让接缝上线、被验证,然后才让它接管。
一个受环境变量控制的对等服务器,把TMZ的信号契约同时暴露给Trader Joe和Family CFO,把准确率契约暴露给Trader Joe,序列化时套用比底层导出表更严格的隐私白名单——原始链接和原始数据永远不会离开这个进程。它刻意做成双通道:现有的Postgres直读照常工作,HTTP接口是一层附加的、独立开关的能力,在有对等方真正切换过去读取之前,行为完全零变化。TMZ不只被设计成提供方,也被设计成消费方:读回Trader Joe自己自选股列表的反向通道,在这一侧已经实现、契约已冻结,在Trader Joe那一侧启用该能力之前一直保持黑屏。在那之前,扫描仍然跑在配置好的股票清单上——和提供方向遵循的是同一条“只叠加、不改变现有行为”的规则。
TMZ今天所有LLM调用用的提示词,都是写死在富化模块里的字符串常量;重构把它们外置成一个按语言和名字分类、有版本管理的提示词库,存放路径可以用环境变量覆盖,还留着内嵌兜底,这样即便镜像打包出错也只是降级,而不是直接崩溃。同样的原则也用在其他一切运维人员可能想调的东西上——每个agent用哪个LLM提供商和模型、打分权重公式、新颖度和评估阈值——统统从代码常量搬进一份YAML配置文件,遵循"环境变量优先于YAML、YAML优先于代码默认值"的顺序,这样金丝雀部署可以指向一份备用配置文件,一次阈值调整也变成一次可评审的单行diff,而不用重新构建镜像。它还顺手根治了一个真实发生过、反复中招的故障模式:新文件因为Dockerfile和CI工作流各自手写了一份文件清单而悄悄漏部署——包结构重整之后,两边都改成了一次性的整目录拷贝。
Python、Postgres、OpenRouter(DeepSeek V4 Flash)、Dagu调度的批处理周期、yfinance数据采集、自建Gitea Actions CI/CD。