多智能体 AI 出价系统设计:master 如何管住预算与决策 📅 发布时间:2026/9/13 13:50:39 👁 浏览次数: 简介面向量化交易、强化学习及多智能体系统研究者的AI竞拍仿真资源以Python为基础构建真实交易市场模拟环境可帮助用户设计、对比并优化多种自适应竞价策略解决传统回测中缺乏对手交互与动态博弈的问题。资源共38个文件、约963KB包含22个Python源码、7个Jupyter Notebook、6个PDF文档另有Shell脚本与示例图源码覆盖策略算法层如随机出价、策略梯度、线性出价、集成方法、数据处理器、评估器与模型实现Notebook则提供CTR预估、XGBoost、逻辑回归等完整竞价方案。目前已有128人浏览学习适合具备一定Python基础的读者按图索骥。通过此资源可快速掌握多智能体竞拍系统的整体目录结构与核心逻辑获得可复用的策略基线和评估脚本并借助报告与图表理解不同算法在实际竞拍中的表现为学术实验或量化交易研究提供扎实起点。1. 多智能体 AI 出价系统在设计什么master 是预算的唯一持有者如果把 Multi-Agent-AI-bidding 做成一个大模型直接出价的开关你会很快发现预算先失控。这个标题里的 master 并不是工程图里多余的中间层而是预算、状态和决策的唯一持有者agent 各自带着上下文来报价master 负责做最后取舍。它适合程序化广告、云资源竞价、供应链招标签约这类受总预算约束的交易场景。我下面把 agent 怎么拆、prompt 怎么设、master 的状态怎么恢复、上线前怎么验证一条线讲完。做过 AI agent 应用开发的人也可以直接把这里的职责边界搬走。2. bidding 系统里的 agent 拆法master 管状态agent 管感知2.1 三种多智能体协作模式为什么出价场景选 master 调度如果去翻现成的多智能体框架能看到三种协作模式。第一种是主从调度master 派任务agent 报结果第二种是黑板模式大家往共享内存写观察master 到点取结果第三种是协商模式agent 之间互相出价自行撮合。竞价场景通常不选第三种。一次 RTB 请求从广告曝光到决策返回只有几十毫秒agent 之间反复协商没有时间窗口预算约束又是全局的任一 agent 都不知道同伴已经承诺花掉多少。主从调度让 master 成为唯一的状态入口它持有剩余预算、出价上限、流量黑名单agent 相当于派到不同数据源的查询器。这个结论听着保守但在线上跑是稳定的。2.2 本地跑通最小 Multi-Agent bidding 骨架下面代码是我在本地验证职责边界时常用的最小骨架两个角色Agent 返回建议Master 强制预算。为了便于阅读Agent 里没有接模型先用随机分代替感知结果。import asyncio import random class Agent: def __init__(self, name: str, base_price: float): self.name name self.base_price base_price async def suggest(self, ctx: dict) - dict: # 模拟对当前拍卖环境的感知生产环境会换成模型或统计服务 score random.uniform(0.0, 1.0) return { agent: self.name, price: self.base_price * (0.8 score), # 只给建议不扣钱 score: score, } class Master: def __init__(self, agents: list[Agent], budget: float): self.agents agents self.budget_left budget self.decisions [] async def bid_round(self, auction_id: str, ctx: dict) - dict | None: suggestions await asyncio.gather(*(a.suggest(ctx) for a in self.agents)) suggestions.sort(keylambda s: -s[score]) best suggestions[0] if best[price] self.budget_left: best[price] self.budget_left if best[price] 0.01: return None self.budget_left - best[price] self.decisions.append((auction_id, best)) return best async def main(): agents [ Agent(market_agent, 30.0), Agent(trend_agent, 20.0), Agent(risk_agent, 15.0), ] master Master(agents, budget100.0) for i in range(10): decision await master.bid_round(fauction-{i}, {type: banner}) if decision: print(decision) print(budget_left:, master.budget_left) asyncio.run(main())这里的关键是gather并发收集多个 agent 建议再交给 master 排序。suggest方法只拿到自身的 base_price 和拍卖上下文不接收全局预算扣预算只发生在 master 的bid_round方法里。预算扣减是同步且单线程的在这个阶段不产生并发写。把 budget 设为 100 是给自己留出观察空间production 里这个值应来自指标系统或账户余额服务。2.3 一张表定清楚每个 agent 的上下文与输出物agent 多了之后职责容易乱。拿程序化广告举例我会把它拆成下面这张表。Agent输入上下文输出选型market agent广告位 ID、设备、地区、时段、页面内容竞争程度 low/medium/high轻量分类模型trend agent同广告位过去 7 天成交价、点击率价格趋势、赢标概率时序模型或 LLM 摘要risk agent流量质量、IP 信誉、转化回传延迟是否值得参与规则加黑名单master上述全部建议、剩余预算、单次出价上限最终价格规则决策不接模型这张表保证每路 agent 的输入输出都是可测的。master 不需要知道页面内容只需要知道 market agent 给出的判断risk agent 也不该拿着预算表去改出价。谁手里有状态谁就能影响系统的钱所以状态必须集中在 master。3. AI 大模型在 bidding 链路里做预测时参数和上下文的组装方式3.1 模型只做信号不做最终价格出价上限留在 master如果把 2.2 的 Agent.suggest 换成真实 LLM 调用就进入正式的 AI bidding 环节。常见做法是让大模型输出一个质量分或者竞争等级而不是直接输出价格。LLM 对钱没有连续性记忆把剩余预算写进 prompt 也经常会在下一条上下文里被忽略出价又对抖动极为敏感模型每次生成数值相差 0.5日消耗就会差出一个量级。因此在生产里模型只做信号最终出价留在 master 的规则函数里。把这一步包进 AI agent 时建议用一个入口函数封装模型调用不让 agent 直接持有 API key 和预算参数。入口函数可以兼容 OpenAI SDK、Spring AI 或本地部署的模型内部只暴露get_ai_signal(auction, recent_wins)。3.2 用提示词强制 JSON 输出temperature 与 max_tokens 这样设import os import json from openai import OpenAI client OpenAI( api_keyos.environ[LLM_API_KEY], base_urlos.environ.get(LLM_BASE_URL), ) def build_prompt(auction: dict, recent_wins: list) - str: return f 你是程序化广告竞价辅助分析器只输出 JSON。 当前拍卖信息{json.dumps(auction, ensure_asciiFalse)} 同广告位最近 10 次成交{json.dumps(recent_wins, ensure_asciiFalse)} 请返回 flow_quality: 0-1 之间的浮点数 competition: low 或 medium 或 high 不要包含任何解释。 def get_ai_signal(auction: dict, recent_wins: list) - dict: resp client.chat.completions.create( modelos.environ.get(LLM_MODEL, bid-model), temperature0.2, max_tokens100, response_format{type: json_object}, messages[{role: user, content: build_prompt(auction, recent_wins)}], ) return json.loads(resp.choices[0].message.content)temperature0.2是出价场景的默认起点可以降低分类结果在 low/medium/high 之间跳变的概率max_tokens100防止模型输出解释文字response_format强制 JSON后面直接json.loads不用正则。这里提示词只承担感知分析不承担资金决策预算和出价上限始终在 master 代码里不做成大模型可控参数。3.3 把模型分数映射成真实出价乘数表与约束函数模型返回的flow_quality和competition不能直接当钱用。我一般会先转成乘数再乘底价最后用bid_cap强制截止。COMP_LEVEL_MULT { low: 0.95, medium: 1.0, high: 1.25, } def calc_bid(base: float, flow_quality: float, competition: str, bid_cap: float) - float: score 0.5 flow_quality bid base * score * COMP_LEVEL_MULT.get(competition, 1.0) return max(0.01, min(bid, bid_cap))flow_qualitycompetitionbasebid_cap实际出价0.8high3.05.04.8750.4low3.05.02.5650.9medium3.04.04.0 被 cap 截断第二个例子里 base 是 3.0competitionlow时乘数小于 1相当于保守观察。第三个例子里calc_bid结果已经超过 capmaster 必须把它压到 4.0。bid_cap通常来自历史成交价分位数而不是模型输出这一步不能省。4. master 的同步与合并多智能体出价系统上线会踩的三个坑4.1 并发出价重复扣费用 bid_id 幂等键挡住竞价链路是重试敏感场景。请求到 master 后消费方超时重试、MQ 消息重投、多个 worker 拉取同一拍品都会让同一 auction_id 进入两次决策。第一版方案很多人直接在内存里if not exists判断在单进程没问题多副本必然穿透。我一般用 bid_id 做幂等键。def build_bid_id(auction_id: str, agent_name: str, round_no: int) - str: return f{auction_id}:{agent_name}:{round_no}在 Redis 里抢占时使用SET bid:{bid_id} 1 NX EX 300返回 OK 才允许继续扣预算。再把 bid_id 写到数据库的唯一索引等于上了双保险。假如两条请求同时到达Redis 锁先拒掉一个后一个直接返回已处理。round_no很重要因为同一个 auction 在多次轮询中可能被 agent 重新评估不能用 auction_id 一口封死。4.2 master 重启以后预算怎么恢复事件日志回放重启后预算恢复是 master 最容易测漏的点。内存计数器在进程挂了以后丢失生产上不能靠定期快照。常见做法是事件追加日志每次接受出价先追加一条bid_accepted再更新内存预算顺序不能反。import json class BidEventStore: def __init__(self, path: str): self.f open(path, a) def append(self, event: dict) - None: self.f.write(json.dumps(event) \n) self.f.flush() def restore_budget(path: str, init_budget: float) - float: budget init_budget for line in open(path): e json.loads(line) if e[type] bid_accepted: budget - e[price] return budget写文件成功以后才允许扣内存预算。如果只有内存扣了但日志没落盘重启后会多扣如果日志有但内存误回滚下一事件会把差异打平。批量出价场景里flush可以改成攒批写但恢复正确性永远优先于单条写入速度。4.3 先 merge master 还是先处理 comment发布顺序与分支命名AI bidding 系统的模型会频繁迭代master 代码也会改。典型现场是feature 分支还在 reviewmaster 已经前进几个 commitreview comment 停在旧代码上。我的固定顺序是先把 master 合进 feature 分支本地解决冲突再回头补 comment。原因很简单comment 挂在旧基线上时直接修改会产生大量重复劳动基线对齐后 comment 提醒的位置才可能是仍然存在的问题位置。git checkout feature/bidding-schema-v3 git fetch origin git rebase origin/master git push --force-with-lease # 基线对齐后再处理 review comment git commit --amend git push --force-with-lease如果当前 master 没有配置上游分支直接 push 会看到fatal: the current branch master has no upstream branch这个报错在团队共享环境里是在提醒你不要推错目标。解决方法是先确认 origin 地址再执行git push -u origin master。标题里Multi-Agent-AI-bidding-master_hardly9de_holdm1l_lampdfw_designfv这种长后缀我一般只作为实验归档名不直接作为 master 分支常态。master 分支保留可发布版本hash 放到模型 registry 和发布清单里避免分支名携带大量过期实验信息。现场事故根因修复同一拍卖在两个 worker 里各扣一次预算缺少幂等键bid_id Redis NX DB 唯一索引master 重启后预算凭空变多只存内存计数器先写事件日志再扣内存feature 和 master 冲突越来越大长期不 rebase master每次开发先 rebase origin/master再补 comment5. 用历史拍卖日志回放 master 决策链上线前做一轮离线校验5.1 回放前固定两样东西模型版本和随机种子回放不是把历史日志重新跑一遍网络请求而是拿历史拍卖现场上下文让现在的 master 再出一次价并对比当时真实决策。回放前先固定两样东西agent 的模型版本要用容器镜像 digest 锁定所有随机源要固定 seed否则两次回放结果对不上。回放里不连真实预算服务直接用 mock 账户。5.2 回放脚本骨架import json import random class ReplayBudget: def __init__(self, total: float): self.left total self.spent 0.0 def replay(path: str, master, fixed_seed: int 42) - None: random.seed(fixed_seed) for line in open(path): ev json.loads(line) if ev[type] ! auction_start: continue ctx ev[context] bid master.bid_round(ev[auction_id], ctx) if bid: print(ev[auction_id], bid[agent], bid[price])回放脚本里的 master 应该和线上是同一份代码只是把预算服务替换成 mock。budget 初始化用历史当天预算。auction_start事件里要保留完整上下文字段例如广告位、设备、时段、当时最近 10 次成交记录否则大模型信号无法复现。5.3 回放结果指标表指标建议阈值说明总花费偏差 5%回放花费与线上花费的差异偏差过大代表模型迭代影响超出预期单次决策 p95 50 ms超过说明模型响应太长需要换轻量模型或加缓存预算超支次数0回放中出现超支代表幂等或出价上限失效出价分布重合度变化集中在中高价位所有价位都变化时优先检查 prompt 上下文窗口是否截断上线前跑这个命令红灯全灭再进灰度python replay.py --events logs/2025-06-01.jsonl --mock-budget 10000 --model-digest sha256:xxxx --seed 42。本文还有配套的精品资源点击获取