问题描述、复现和解决方案:tool call fails with Cannot read properties of undefined (reading 'prepare')
#7062
Closed
WXY-YXW996
started this conversation in
General
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
问题概述
按 README 的方式启动 dsh(
pnpm dsh <profile>,实际执行node --import tsx/esm apps/cli/src/bin.ts)时,@deepseek-ai/dsh-tools会被求值两次,来自两个不同的文件:@deepseek-ai/dsh-tools导入的地方,被 tsx 按tsconfig.base.json的paths别名解析到
packages/core/tools/src/index.ts;ctx.tools上的ToolRuntime实例,是由该包自己声明的入口packages/core/tools/lib/index.js构造的。TOOL_RUNTIME_SCHEDULER的声明是Symbol('@deepseek-ai/dsh-tools.scheduler'),每次模块求值都会产生一个新的、互不相等的 symbol,二者仅仅是 description 相同。于是
packages/core/agent-loop/src/tool-calls.ts用其中一份 symbol 去查找,而ctx.tools是用另一份作为键写入的,查找结果静默变成
undefined:结果是该进程内所有会话的每一次工具调用都会失败(
ToolRuntime是 host 层单例),100% 必现。本轮对话以如下方式结束:
{"type":"turn/end","data":{"turn":3,"reason":{"kind":"error", "error":{"message":"Cannot read properties of undefined (reading 'prepare')","code":"UNKNOWN"}}}}这个错误被折叠进了轮次结果里,服务端控制台不会打印任何内容,所以从外部看只是"模型用不了任何工具",
很难定位。
改用编译产物启动(
node apps/cli/lib/bin.js <profile>)则不复现。环境
master,ddefc45fbc)复现步骤
不需要 API key、浏览器或任何会话——符号失配在启动时就已经成立。在干净的检出上执行
pnpm install && pnpm run build之后,把下面的脚本保存到仓库根目录并运行:实际输出
其中
scheduler key came from: load B正是问题所在的不对称:真正的服务实例上带的是lib/那份副本铸造的键,而源码里每一处
import { TOOL_RUNTIME_SCHEDULER } from '@deepseek-ai/dsh-tools'拿到的都是src/那份铸造的键。期望结果
TOOL_RUNTIME_SCHEDULER in ctx.tools为true,工具调用正常派发。repro-scheduler-symbol.ts为什么偏偏是这一个 symbol 出问题
TOOL_RUNTIME_SCHEDULER似乎是整个仓库里唯一一个用裸Symbol()创建、且被当作跨包(经裸包名)共享对象的属性键来使用的 symbol。其余模块级
Symbol()(DYNAMIC_TOOL、REMOTE_EVENT_NEXT、OVERSIZED、CDP_METHOD_NOT_HANDLED)要么是模块私有,要么只在本包内通过相对路径导入使用——相对路径只可能解析到同一个文件,不存在两份。其他
unique symbol则都是declare const的纯类型 brand,运行时并不存在。
而本仓库所基于的 cordis 框架本身,早已在所有跨边界符号上规避了这个坑
(
vendor/cordis/src/utils.ts):这恰好解释了为什么在同一个被重复加载的
ctx.tools对象上,Symbol(cordis.tracker)照常工作,唯独scheduler 这个键失效。
建议的修复
packages/core/tools/src/index.ts:463Symbol.for按字符串走进程级全局注册表,因此无论模块被求值多少次,这个契约都能保持一致。它与unique symbol类型兼容,也与 cordis 既有的写法保持一致。已在同一份检出上本地验证:仅改这一行并重新
pnpm run build后,上述复现脚本的结果变为TOOL_RUNTIME_SCHEDULER in ctx.tools? true,工具调用正常派发,npx vitest run packages/core/agent-loop packages/core/tools保持全绿(36 个文件 / 843 个用例)。补充说明
这个改动修的是契约,不是重复加载本身。 修复之后
same module instance? false依然成立——模块仍然被求值了两次。
dsh-tools中其他依赖跨副本同一性的假设(instanceof、类的身份、模块级缓存)仍然是暴露的。更彻底的修法是让两条解析路径归一,例如运行时不启用 tsconfig 的
paths别名,或让插件加载器与进程其余部分走同一套别名解析。
可能与 discussion [Bug] 所有工具调用都报 "Cannot read properties of undefined (reading 'prepare')" — ToolRuntime 调度器未注册(rc.6) #1677 相关,该讨论报告了相同的错误信息。这里单独提交,是因为本机上的重复
并非重复安装:该包在本机只有一份物理副本(所有
node_modules/@deepseek-ai/dsh-tools都是指向同一目录的符号链接,没有 injected 或复制出来的实例),两个 symbol 来自同一份文件树经两条解析
路径被求值了两次。
README.md中写明pnpm dsh web"uses those built artifacts without rebuilding",但dsh这个脚本实际是通过 tsx 从源码运行
apps/cli/src/bin.ts的。无论哪一个才是预期行为,二者目前是不一致的——而上述 bug 只存在于源码运行这条路径上。
All reactions