|
我是 哈基米,做多智能体系统。从 174 行核心代码的 deep-research 团队,到跑在 Temporal 上的企业级执行内核,想回答的是同一个问题:让一群模型可靠地协作,需要什么样的工程。
|
|
- ⚙️ SwarmCore 冲刺 M5(v1 候选基线):Temporal 持久化编排 · PostgreSQL 单一事实源 · OPA/Vault 治理
- 💼 面壁智能:参与 OpenBMB 的 Agent 产品 ChatDev / StaffDeck / PilotDeck
- 🔍 上游审计:读 Agent 框架的 provider 适配、streaming 聚合、tool 调用层找缺陷,写无网络最小复现,按仓库规则报 issue 或当天带回归测试提 PR
- 📖 nanoteam:把多智能体的最小可用形态写成一本能跑的教科书
一条多智能体谱系:看得懂 → 用得上 → 跑得稳
🐜 nanoteamdeep-research 多智能体团队,174 行核心代码、CI 卡死 1000 行上限,一天读完的「可运行教科书」。Leader–Worker 架构,每次任务固定 N+2 次模型调用。 |
面向本地工程的 AI 编程助手:跨会话项目记忆,Skill 经人工审核后沉淀自进化,长任务上下文压缩,本地权限沙箱。 |
|
医疗问答多智能体原型:Orchestrator–Worker 编排,安全层由确定性代码而非 LLM 兜底,技能白名单 + 隐私脱敏。 |
⚙️ SwarmCore企业级多租户、可持久化的智能体执行内核:不可变执行计划,Temporal 编排,PostgreSQL 单一事实源,OPA/Vault 治理。 |
deer-flow ⭐84k · 合并 14 |
fastmcp ⭐28k · 合并 4 |
agno ⭐43k · 合并 3 |
agents ⭐15k · 合并 3 |
harness-sdk ⭐9k · 合并 2 |
crewAI ⭐60k · 合并 1 |
agentscope ⭐33k · 合并 1 |
phoenix ⭐12k · 合并 1 |
openai-agents-js ⭐4k · 合并 1 |
30 个 PR 合并进 9 个上游仓库 · 按合并时间倒序 · 点 ▸ 看修了什么
2026-10-14
bytedance/deer-flow · fix(mindie): degrade unserializable tool args to str() in XML replay
bytedance/deer-flow#6839:MindIE 的 XML tool-call parser 对
json.loads拒收的{开头参数值回退ast.literal_eval,解析值按设计流入 tool-call args 以满足下游 Pydantic 校验——模型吐出 set 字面量{1, 2, 3}即产出带 Pythonset的 AIMessage;该消息留在会话历史里,下一轮_fix_messages重序列化为 XML wire 格式时对非 str 值走无守卫的json.dumps(v, ensure_ascii=False),TypeError: Object of type set is not JSON serializable,且消息不离开历史,线程此后每个请求都失败而非仅那一次工具调用。同函数 ToolMessage json-block 分支早有「Circular 抛 ValueError、其余不可序列化抛 TypeError,一律降级str()」的先例,AIMessage args join 是唯一没有该兜底的序列化点。改为新增_render_tool_arghelper 路由 args join:字符串原样、其余值 json.dumps 失败降级str(v),与既有分支同构,补 parser→AIMessage→下一轮_fix_messages的 round-trip 回归。
2026-10-14
bytedance/deer-flow · fix(models): tolerate null template keys in the legacy thinking disable path
bytedance/deer-flow#6838:
config.yamlprofile 的空 YAML 键(extra_body:→ 显式 null)被ModelConfig.when_thinking_enabled(无逐键校验的dict | None)静默接受,legacy(无reasoning:契约)thinking 禁用路径却用effective_wte.get("extra_body", {})读模板——dict.get(key, {})只兜「键缺失」,显式None原样返回,链式.get抛AttributeError: 'NoneType' object has no attribute 'get';thinking_enabled=False是每个create_chat_model调用点(标题生成、摘要、子代理与 lead-agent 构建)的默认值,首个模型构建即崩而非罕见开关组合。同模块_infer_dialect与契约路径都已是get(...) or {}的守卫写法,同一 profile 挂上reasoning:契约反而能正常构建。改为 legacy 路径三处模板读取统一(effective_wte.get(key) or {}),null 模板键与缺失键同样落进方言启发式,补双 thinking 方向、三种 null 模板形态的回归。
2026-10-13
livekit/agents · fix: split underscore terms in BM25 description and param tokens
livekit/agents#7733:
BM25SearchStrategy的查询归一化把_换成空格,工具名与参数键的分词也做同样拆分,唯独描述 token 与参数描述值只按空白切——描述里的snake_case术语被索引成单个 token,而每个查询都先替换掉_、不可能含它,checking_balance这类写在描述里的术语搜索永远 0 分,tool_search对描述里明确写着该术语的工具抛No tools found。改为对描述与参数描述 token 应用与名字/键相同的下划线拆分,补跨两种策略、两种查询拼法的回归测试。
2026-10-12
bytedance/deer-flow · fix(memory): read the complete scope in the substring search fallback
bytedance/deer-flow#6732:检索适配器关闭时(
retrieval_adapter: ""是文档化的配置)substring 搜索走FileMemoryStorage._search_substring,它遍历默认limit: 100的分页list_facts且从不翻页——一个 scope 有 150 条匹配、top_k=150也只返回前 100 条,违反MemoryManager.search「return up totop_k」的契约;索引结果非空时上层DeerMem.search不会再落到无上限的兜底路径,同文件 scopedrebuild_index早已为「bulk replace 会丢掉没读到的行」读完整 scope。改为按同样方式读全量事实、原位套用 filters,补回归测试。
2026-10-12
bytedance/deer-flow · fix(memory): keep non-core fact categories instead of rewriting to other
bytedance/deer-flow#6733:
memory_add文档明文允许自由 category 标签(示例给出 "personal"),memory_search承诺「Only facts with this exact category are returned」,storage._normalize_category却把核心类目之外的标签一律改写成 "other"、原文只存进全仓无代码读取的categoryExtension字段——memory_add(category="personal")落库即 "other"、按 "personal" 永远搜不到,记忆设置页的自由文本分类输入与 fact 抽取 prompt 自带的knowledge枚举(不在核心类目里)也被同一改写折进 "other"。改为保留调用方标签作为存储category(空串仍回退 "context"),非核心标签在 create/update、get_memory 文档、FTS5 检索索引与分类过滤中全程 round-trip。
2026-10-12
agentscope-ai/agentscope · fix(model): declare real WAV sizes in the final streamed audio block
agentscope-ai/agentscope#3242:流式语音的最终
is_last音频DataBlock保留流式 WAV 头的0xFFFFFFFFRIFF/data 占位大小——两个流式 parser 只给首个 delta 加流式头,累积器把后续 delta 原样拼进完成块,完成的音频不是合法 WAV、与同一调用非流式路径的产物不一致,信任头部的消费方(wave.getnframes()、时长、seek、严格播放器)看到 4294967295 而非实际音频长度。改为在最终块声明真实 RIFF/data 尺寸。
📦 更早的 24 个 PR
2026-10-12
livekit/agents · fix: reference real tool name in ToolProxyToolset description and errors
livekit/agents#7734:
ToolProxyToolset的call_tool工具描述与 unknown-tool 报错都让模型「用search_tools发现工具」,而该工具集实际暴露的搜索工具由父类ToolSearchToolset以tool_search之名创建、测试也断言这个名字——两段字符串都是 LLM 可见的,照自己指令行事的模型会去调用一个不存在的工具。改为引用真实工具名tool_search。
2026-10-09
PrefectHQ/fastmcp · fix: convert version query param to VersionSpec in component manager routes
PrefectHQ/fastmcp#5575:
set_up_component_manager文档写明支持POST /tools/{name}/disable?version=v1,路由处理器却把原始查询串直接交给Provider.enable/disable的VersionSpec参数,存下str后Visibility._matches调self.version.matches(...)抛AttributeError:一次带?version=的请求返回 200,之后所有客户端的tools/list全部报错直到重启。改为把version查询参数转成VersionSpec(eq=version)再传入,无?version=路径保持None不变,补回归测试。
2026-10-08
bytedance/deer-flow · fix(models): keep escaped newlines inside fences in MindIE native stream
bytedance/deer-flow#6478:MindIE 的无工具原生流式逐 chunk 调
_decode_escaped_newlines_outside_fences,而该助手靠输入里成对的围栏定界符判断围栏——token 大小的 chunk 几乎不可能同时含一对定界符,围栏判定永不生效,chunk 里每个字面\n(包括完整回复中位于围栏内的代码)都被解成真换行:print("a\nb")的转义被拆成两行写进用户可见消息与持久化内容,而非流式路径(_generate/_agenerate)与开工具的模拟流都保留转义。改为用带围栏状态的解码器跨 chunk 维持围栏状态、复用同一实现,跨 chunk 的半个围栏定界符或\留到下一 chunk 决定,流末未决文本作为最后一个 chunk 吐出。
2026-10-06
Arize-ai/phoenix · fix(client): keep tools when anthropic tool_choice is none
Arize-ai/phoenix#16535:
_ToolKwargsConversion.to_anthropic在存档 prompt 的tool_choice为"none"时立即return {},把此前已装好的ans["tools"]一并丢掉——带工具但禁用工具使用的捕获请求,回放时 tools 整个消失;服务端 playground 本就同时发 tools 与tool_choice: none,同文件_ToolChoiceConversion的 to/from 两个方向都早已正确映射"none",只有这条早退绕开了既有处理。改为删掉早退,让 tool_choice 走既有映射、tools 保留,补回归用例。
2026-10-05
PrefectHQ/fastmcp · fix(tools): keep structured content for object schemas without explicit type
PrefectHQ/fastmcp#5271:
TransformedTool.run只拿字面顶层"type": "object"键判定output_schema是不是 object schema——不带该键的 object schema(properties-only 的{"properties": {...}}、根级$ref指向 object)被误判为非 object,transform_fn返回的ToolResult的structured_content被丢弃,重建的ToolResult还把is_error与meta一并丢掉。改为按解析后的 schema 判定(properties-only 与根级$ref都算 object),透传structured_content并保留is_error/meta,补回归测试。
2026-10-05
PrefectHQ/fastmcp · fix(tools): take partial tool names and docstrings from the wrapped function
PrefectHQ/fastmcp#5269:
functools.partial造的工具全叫partial、描述是 partial 类 docstring——ParsedFunction.from_function经fn.__class__.__name__兜底取名,partial 既无__name__也无__doc__,注册两个 partial 工具就在名字partial上相撞、后者顶掉前者(只给一条泛型 warning)。改为取名/取 docstring 时回退到被包裹的 callable(保留 partial 绑定的参数与默认值);partial 上显式给的名字、文档与参数描述仍优先(含故意留空的文档),FastMCP 显式元数据照样覆盖,补回归测试。
2026-10-01
strands-agents/harness-sdk · fix(anthropic): store redacted thinking as bytes and re-encode on send
strands-agents/harness-sdk#4593:Anthropic Messages API 的
RedactedThinkingBlock.data是 base64 字符串,AnthropicModel.stream却经编译期 cast 把它直接塞进声明为Uint8Array、带 base64toJSON()/fromJSON()契约的ReasoningBlock.redactedContent——首轮侥幸原样通过,首次Message.clone()或 session 存取经toJSON()/fromJSON()把它再编码成 base64 文本的 ASCII 字节,下一请求的redacted_thinking块序列化成{"0":69,"1":109,...}被 Messages API 拒收,加密推理的多轮工具使用全断。改为入库时把 base64 解码成字节存进redactedContent、发送时再编码回字符串,往返与类型契约都对齐。
2026-09-30
livekit/agents · fix(llm): block unguarded mutations on read-only chat contexts
livekit/agents#7445:
Agent.chat_ctx返回的_ReadOnlyChatContext有两条变更路径没守卫、静默写进视图的 detached 副本后正常返回:_ImmutableList覆盖了 append/extend/pop/remove/clear/sort/reverse 独漏insert,chat_ctx.insert(item)与走list.insert()的add_message(created_at=...)/merge(...)都把消息写丢,消息从没进真实 chat context;itemsproperty setter 也未覆盖,chat_ctx.items = [...]把不可变列表换成可变列表而readonly仍报 True。改为补上insert的抛错实现并覆盖itemssetter,两条路径都按文档抛RuntimeError,补回归测试。
2026-09-25
openai/openai-agents-js · fix(agents-core): handle same-server MCP tool name collisions
openai/openai-agents-js#1935:
getAllMcpTools只拦跨 server 的重名:toFunctionToolName把非字母数字全替成_,同一 server 里search-a与search_a归一后都叫search_a,两个工具一起返回,resolveModelVisibleToolNameCollisions丢掉其中一个、模型只剩一个可达——守卫只拿当前 server 的名字与之前 server 已占的比较,还先把当前批次收进Set,批内重复根本到不了比较;prefixed 路径按前缀后的 base name 计数,-与_都算安全字符、base 不同就不强制 hash 后缀,归一后才相撞。改为findDuplicateToolNames同时报批内重复与先前 server 的占用(非前缀路径抛既有UserError),前缀路径按归一后的名字计数预留、相撞时给同一确定性 hash 后缀,两个工具都保可达;补/扩回归测试,修前 2 败、修后 69 全过。
2026-09-25
bytedance/deer-flow · fix(tools): route bound task and batch tools' sync path through the bound runtime
bytedance/deer-flow#5861:
bind_task_tool/_bind_batch_tool给副本重绑的只有coroutine,func仍是进程级单例的 sync 包装——包着未绑定的 coroutine,同步调用 bound 副本因此绕过显式 SDK submitter 与执行容量、落回进程全局回退(恰是bind_batch_toolsdocstring 明令禁止的「never fall through to another application's process-global submitter」),在单例从未被 sync 包装过的新进程里则直接NotImplementedError。改为把副本的func也重绑成make_sync_tool_wrapper(bound_coroutine, ...)(与_ensure_sync_invocable_tool同一 helper),两条调用路径都走 bound coroutine 及其 runtime 的 submitter 与容量;补三条回归测试,main 上红、本分支绿。
2026-09-25
bytedance/deer-flow · fix(sandbox): list the skills root when only category mounts exist
bytedance/deer-flow#5857:默认(非 policy-scoped)skills 投影下
ls /mnt/skills报 Directory not found:该布局只挂四个 category 子目录、没有/mnt/skills根自身的PathMapping,LocalSandbox.list_dir把根解析成字面宿主路径,宿主扫描的FileNotFoundError抢在虚拟子目录 overlay——专为让 agent 用ls /mnt/skills发现 category 而写的代码块——之前抛出。改为宿主扫描抛FileNotFoundError时,只要请求的容器路径内至少挂了一个映射,就按空宿主列表处理、让既有 overlay 浮出挂载的子目录,什么都没挂的路径照旧报错;补回归测试,main 上红、本分支绿。
2026-09-25
agno-agi/agno · fix: store raw video bytes in GeminiTools.generate_video artifact
agno-agi/agno#10554:
GeminiTools.generate_video把返回的Videoartifact 用 base64 文本构造(base64.b64encode(generated_video.video_bytes).decode("utf-8")),而agno.media.Video.content声明为原始视频bytes,Pydantic 把该字符串强转成 base64 文本的 UTF-8 字节——Video.get_content_bytes()的每个消费者(媒体卸载上传、artifact 落盘)拿到的都是 base64 文本而不是视频。改为直接传原始字节,对齐同工具箱的generate_image分支与其余六个视频工具箱(opencv/fal/minimax/replicate/wavespeed/lumalab),补回归测试。
2026-09-20
bytedance/deer-flow · fix(models): tolerate a null Codex account_id before it reaches the request header
bytedance/deer-flow#5601:Codex 凭据加载:
account_id以data.get("account_id") or tokens.get("account_id", "")读取,""兜底只覆盖「键缺失」——键存在但值为 JSONnull时 falsy 落穿到下一环,表达式求值为None灌进CodexCliCredential(account_id=None),在model_post_init的account_id[:8]处抛裸TypeError,account 未知的凭据文件让所有CodexChatModel构造失败而非以未记账账号运行。改为任何非字符串值一律归一为字段文档的未知账号值"",补回归测试。
2026-09-20
bytedance/deer-flow · fix(models): skip Claude credentials sources with a non-numeric expiresAt
bytedance/deer-flow#5591:Claude 凭据加载:
_extract_claude_code_credential把claudeAiOauth.expiresAt原样拷进ClaudeCodeCredential.expires_at,is_expired随即拿它和0比大小——字符串、null、list、object 一律抛TypeError且无人捕获,凭据查找循环停在坏文件上不再推进,$CLAUDE_CODE_CREDENTIALS_PATH里一个坏expiresAt就足以让~/.claude/.credentials.json永远读不到,还顺着ClaudeChatModel.model_post_init冒出去使该文件存在时所有模型构造失败而非降级。改为非数值expiresAt的候选源按 #5494 已立的契约跳过并记 debug 日志(缺失键仍回落默认0、不视为过期),补齐各形态回归测试。
2026-09-19
bytedance/deer-flow · fix(models): degrade a non-object Codex auth file to no credential
bytedance/deer-flow#5584:Codex 凭据加载:
load_codex_cli_credential经_load_json_file读~/.codex/auth.json,而json.loads的产物不限于对象,加载器却直接对它调data.get("tokens", {})——顶层是数组/字符串/数字的 auth 文件因此抛AttributeError: 'list' object has no attribute 'get'。异常无人捕获,从CodexChatModel.model_post_init冒出去,在 provider 来得及抛它那句文档化的「Codex CLI credential not found」之前就中止了模型构造,与该模块「读不到就降级」的约定正好相反。嵌套的tokens早已有这道守卫,同文件的 Claude 加载器也在 #5494 补了等价的顶层守卫,Codex 的顶层是唯一漏掉的一处。
2026-09-18
bytedance/deer-flow · fix(mcp): insert bare-filename rewrites literally
bytedance/deer-flow#5522:MCP 工具结果重写:
_rewrite_unique_bare_filenames把相关好的虚拟路径当成Pattern.subn的替换模板传入,而替换串来自真实文件的相对路径、反斜杠在 POSIX 文件名里是普通字符(模型给 stdio server 传了 Windows 风格路径就会产生名为screenshots\q3.png的文件)。模板在找匹配之前编译,于是\q这种未知转义直接抛re.error逃出_convert_call_tool_result、整个工具调用失败(文件其实已写好);能被re接受的转义则把该字节替换进返回文本(\r变成路径中间的真实回车)。改为用 callable 替换逐字插入,并补两半回归测试。文件来自 workspace 快照 diff,所以触发文件不必是本次调用写的。
2026-09-17
bytedance/deer-flow · fix(models): pair Codex invalid tool calls with their tool results
bytedance/deer-flow#5509:Codex Responses 序列化:模型发出
arguments不是合法 JSON 的function_call时,该调用被_parse_response收进invalid_tool_calls,中间件会用带同一call_id的占位ToolMessage就地兜住,但序列化器只回放msg.tool_calls,于是占位结果的call_id在请求里找不到对应的function_callitem,Responses 直接拒收——恰好是中间件要恢复的那种可恢复错误。改为把invalid_tool_calls一并回放;同时处理InvalidToolCall字段可空,缺 name/call_id 的调用直接跳过(中间件已为这类调用补了合成 id 与兜底名,跳过不会让占位结果变孤儿)。
2026-09-16
agno-agi/agno · [fix] keep falsy tool results (0, False, []) on the sync tool execution path
agno-agi/agno#9948:同步工具执行路径把
0/False/[]这类有意义的假值结果当成空结果发给模型,异步路径arun_function_calls却照发str(result),同一个工具在run()与arun()下给模型的结果不一致,模型无法区分“零条”和“没有输出”,空串还会被持久化进会话。去掉真值判断让两条路径一致,并补无网络回归测试。
2026-09-15
PrefectHQ/fastmcp · fix(local-provider): apply transforms in get_tasks
PrefectHQ/fastmcp#5117:MCP 本地 Provider:
LocalProvider.get_tasks()直接返回原始组件键,绕过了 Provider 基类的 transform 管线,于是add_transform(Namespace(...))之后后台任务注册到的名字与工具/资源不一致,按命名空间调用会找不到任务。改为在get_tasks里同样应用 transforms,并补回归测试。
2026-09-15
crewAIInc/crewAI · fix(azure): key streamed tool calls by wire index
crewAIInc/crewAI#7487:Azure 流式补全:tool call 的增量按数组位置归并,但后续 chunk 会重排顺序,导致首个 chunk 的
id配上最后一个 chunk 的name、参数被拼到错误的调用上。改为按 wire index 归并,并补回归测试。
2026-09-14
bytedance/deer-flow · fix(mcp): treat a Human Input Card reply as the current user request
bytedance/deer-flow#5426:HITL 澄清与 MCP 路由:Human Input Card 的回执被
is_real_user_message当成“非真实用户消息”跳过,用户只在澄清回答里提到的关键词不会触发延迟 MCP 工具提升。改用同包已有的is_genuine_user_message谓词(与 summarization / tool_receipt 中间件一致),并补回归测试。
2026-09-04
bytedance/deer-flow · fix(mcp): keep ToolRuntime injection for sync-wrapped MCP tools
bytedance/deer-flow#5164:MCP 工具调用:同步包装的 MCP 工具在 PEP 563 延迟注解下丢失
ToolRuntime注入,工具拿不到会话上下文。修复后注入在两种注解模式下都生效,按 maintainer review 补了契约说明和测试。
2026-09-03
strands-agents/harness-sdk · fix(openai): report max_tokens when a Responses function call is cut off
strands-agents/harness-sdk#4139:OpenAI Responses 流式解析:function call 被
max_output_tokens截断时stop_reason仍报tool_use,Agent 会拿残缺参数直接执行工具。修正判定优先级,截断时如实报max_tokens,并补回归测试。
2026-09-02
agno-agi/agno · [fix] Resolve Gemini image MIME type instead of hard-coding image/jpeg
agno-agi/agno#9887:Gemini 多模态输入:图片 MIME 类型被硬编码为
image/jpeg,PNG/WebP 等格式会被贴错标签导致 API 拒收或误解析。改为从图片数据实际解析类型后再传给模型。
自己审代码发现、附无网络最小复现的缺陷:有讨论的走 issue,能直接修的当天带回归测试提 PR(这类条目的「问题」列直接指向 PR)。修复合并后自动进上一栏。
🐛 196 发现缺陷 · 📦 33 涉及仓库 · ✅ 40 已修复 · 🔧 106 修复已提交 · ⏳ 37 等待中 · ⚪ 13 已关闭




