09. LLM 接缝:把“厂商协议“关进一个可替换的盒子

你在哪 :运行示例的第 6--7 步。上一篇把前缀装配好了,这一篇讲它怎么变成一次 HTTP 请求、以及流回来的东西怎么变成一条 assistant/message

读完你会知道LlmAdapter 的八条契约(每一条都对应一个真实事故)、llm/stream waterfall 为什么在适配器查找之前、空回复为什么算错误,以及推理链 replay 状态的归属规则。


缩写对照表

缩写 英文全称 中文
LLM Large Language Model 大语言模型
HTTP HyperText Transfer Protocol 超文本传输协议
JSON JSON Object Notation 一种数据交换格式
API Application Programming Interface 应用程序编程接口
SDK Software Development Kit 软件开发工具包

一、角色回顾

拥有 :消息与流式分片的词汇表(Message / ContentBlock / StreamChunk)、适配器契约、路由与重试策略。

刻意不做 :不管 agent 级恢复(一次适配器调用 = 一次厂商尝试)、不管块重组(统一由 BlockAssembler 处理)。

在示例中出场:第 6 步(发请求)、第 7 步(收流)。

服务 key 是 ctx.llm。注意它同时拥有服务定义消费者两个角色------文档说这是可以的,因为它们是同一件关切。

二、接缝的形状

#mermaid-svg-QP1SO4q8EsQ4Z0NT{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-QP1SO4q8EsQ4Z0NT .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-QP1SO4q8EsQ4Z0NT .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-QP1SO4q8EsQ4Z0NT .error-icon{fill:#552222;}#mermaid-svg-QP1SO4q8EsQ4Z0NT .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-QP1SO4q8EsQ4Z0NT .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-QP1SO4q8EsQ4Z0NT .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-QP1SO4q8EsQ4Z0NT .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-QP1SO4q8EsQ4Z0NT .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-QP1SO4q8EsQ4Z0NT .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-QP1SO4q8EsQ4Z0NT .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-QP1SO4q8EsQ4Z0NT .marker{fill:#333333;stroke:#333333;}#mermaid-svg-QP1SO4q8EsQ4Z0NT .marker.cross{stroke:#333333;}#mermaid-svg-QP1SO4q8EsQ4Z0NT svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-QP1SO4q8EsQ4Z0NT p{margin:0;}#mermaid-svg-QP1SO4q8EsQ4Z0NT .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-QP1SO4q8EsQ4Z0NT .cluster-label text{fill:#333;}#mermaid-svg-QP1SO4q8EsQ4Z0NT .cluster-label span{color:#333;}#mermaid-svg-QP1SO4q8EsQ4Z0NT .cluster-label span p{background-color:transparent;}#mermaid-svg-QP1SO4q8EsQ4Z0NT .label text,#mermaid-svg-QP1SO4q8EsQ4Z0NT span{fill:#333;color:#333;}#mermaid-svg-QP1SO4q8EsQ4Z0NT .node rect,#mermaid-svg-QP1SO4q8EsQ4Z0NT .node circle,#mermaid-svg-QP1SO4q8EsQ4Z0NT .node ellipse,#mermaid-svg-QP1SO4q8EsQ4Z0NT .node polygon,#mermaid-svg-QP1SO4q8EsQ4Z0NT .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-QP1SO4q8EsQ4Z0NT .rough-node .label text,#mermaid-svg-QP1SO4q8EsQ4Z0NT .node .label text,#mermaid-svg-QP1SO4q8EsQ4Z0NT .image-shape .label,#mermaid-svg-QP1SO4q8EsQ4Z0NT .icon-shape .label{text-anchor:middle;}#mermaid-svg-QP1SO4q8EsQ4Z0NT .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-QP1SO4q8EsQ4Z0NT .rough-node .label,#mermaid-svg-QP1SO4q8EsQ4Z0NT .node .label,#mermaid-svg-QP1SO4q8EsQ4Z0NT .image-shape .label,#mermaid-svg-QP1SO4q8EsQ4Z0NT .icon-shape .label{text-align:center;}#mermaid-svg-QP1SO4q8EsQ4Z0NT .node.clickable{cursor:pointer;}#mermaid-svg-QP1SO4q8EsQ4Z0NT .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-QP1SO4q8EsQ4Z0NT .arrowheadPath{fill:#333333;}#mermaid-svg-QP1SO4q8EsQ4Z0NT .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-QP1SO4q8EsQ4Z0NT .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-QP1SO4q8EsQ4Z0NT .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QP1SO4q8EsQ4Z0NT .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-QP1SO4q8EsQ4Z0NT .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QP1SO4q8EsQ4Z0NT .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-QP1SO4q8EsQ4Z0NT .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-QP1SO4q8EsQ4Z0NT .cluster text{fill:#333;}#mermaid-svg-QP1SO4q8EsQ4Z0NT .cluster span{color:#333;}#mermaid-svg-QP1SO4q8EsQ4Z0NT div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-QP1SO4q8EsQ4Z0NT .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-QP1SO4q8EsQ4Z0NT rect.text{fill:none;stroke-width:0;}#mermaid-svg-QP1SO4q8EsQ4Z0NT .icon-shape,#mermaid-svg-QP1SO4q8EsQ4Z0NT .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QP1SO4q8EsQ4Z0NT .icon-shape p,#mermaid-svg-QP1SO4q8EsQ4Z0NT .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-QP1SO4q8EsQ4Z0NT .icon-shape .label rect,#mermaid-svg-QP1SO4q8EsQ4Z0NT .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QP1SO4q8EsQ4Z0NT .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-QP1SO4q8EsQ4Z0NT .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-QP1SO4q8EsQ4Z0NT :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 提供者
消费者
Agent Loop
其他直接调用方
服务定义 ctx.llm

Message · ContentBlock

StreamChunk · 适配器契约
DeepSeek 适配器

直连 fetch
pi-ai 适配器

库支撑,通用兼容
你自己的适配器

注册方式:

ts 复制代码
ctx.llm.registerAdapter(providers, adapter)

一个适配器实例可以拥有多条 provider 路由。GenerateOptions.provider 选路由,GenerateOptions.model 交给那个适配器解释------model 不需要在生命周期开始时就注册 。重复的 provider 路由原子失败

模型目录(listModels())是建议性的,不是白名单

"That catalog is advisory rather than a request whitelist: the adapter remains authoritative and may accept unlisted model ids."

这条很实用------你自建的 endpoint 上有一个目录里没有的模型名,照样能用。

三、适配器契约:八条,每条都对应一个真实事故

文档原话是 "Every adapter MUST obey these, and every consumer may rely on them"。逐条读,并说明它防的是什么:

1. usage 必须在 finish 之前,finish 之后什么都不许有

"Defer both to the provider's end-of-stream marker so a trailing usage-only chunk can't violate the ordering."

防的是:消费者以为流结束了,结果又来了一个只带 usage 的分片。

2. 工具调用的 arguments 全程保持原始 JSON 字符串

"Partial fragments stream via argumentsDelta; a provider that hands back parsed objects re-stringifies at block-end."

防的是 :解析再序列化导致的字节漂移(键顺序、数字格式、Unicode 转义)。第 6 篇说过 tool/call 事件里的 arguments 是"模型原样产出的、未解析的"------那条日志规则的上游保证就在这里。

3. 两条错误路径,一个失败类型

允许 throw (传输/协议错误),也允许finish {kind:'error'|'aborted', failure} 结束流 (厂商在流内报错,适配器没法在流中间抛)。两条路径携带的是同一个 LlmFailure

防的是:消费者要写两套错误处理。

失败之后的动作(第 7 篇讲过):循环关掉失败的 step,把错误、不可变事实、之前重试过的事实、serving 时捕获的重试策略 、turn 信号一起交给 agent/request-error

4. 一次适配器调用 = 一次厂商尝试

"Adapters disable library retries . Agent-level recovery opens another durable numbered turn; direct ctx.llm.stream() callers remain single-attempt."

防的是 :底层库悄悄重试三次,导致"日志里一次请求,账单上三次"、以及重试对上层不可见。要重试就开一个新编号的持久 turn------重试这件事本身也是可追溯的。

5. 厂商卡住由传输层兜底

两个正式适配器都暴露 streamIdleTimeoutMs默认五分钟 。看门狗只在 next() 挂起期间上膛 ,整个请求用一个稳定信号,自己到期映射成 TIMEOUT,而更早的调用方中止仍然记为 ABORTED

防的是:厂商流不结束也不报错,agent 永远挂着;以及"超时"覆盖掉"用户取消"这个更准确的原因。

6. 上下文溢出只有一个规范错误码

两个 DeepSeek 适配器都通过 isContextWindowExceededError() 归一成 CONTEXT_WINDOW_EXCEEDED,无论它是抛出来的 HTTP 错误还是流内 finish 错误。

"Consumers route on the code, never provider text."

防的是:压缩插件靠正则匹配厂商的错误文案来判断"是不是超长了"------厂商改一次文案就全崩。这也正是第 7 篇那条压缩恢复路径的前提。

7. 每个厂商 HTTP 请求都带应用归属头

attributionHeaders()User-Agent 基线),并且用一个 wire 级测试证明它确实加上了

8. 空回复是可重试错误,不是静默成功

"Both adapters map a terminal stop finish that carried no content blocks to finish {kind:'error'} with the canonical EMPTY_RESPONSE code, and dsh-llm-retry retries it by default."

防的是:模型返回空,循环以为"它没什么想说的",于是这个 turn 就这么结束了------用户看着一个空气泡。

四、llm/stream:为什么拦截点在适配器查找之前

这是一个容易忽略但很有价值的设计:
"Adapter lookup happens at the terminal continuation of the llm/stream waterfall, so a listener may short-circuit the call or route a mutable one-shot request before lookup."
厂商 适配器 终端续延 llm/stream 监听器 (缓存 / 录制 / 改路由) 驱动 厂商 适配器 终端续延 llm/stream 监听器 (缓存 / 录制 / 改路由) 驱动 #mermaid-svg-pyKBIKjRQrST9c2Q{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-pyKBIKjRQrST9c2Q .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-pyKBIKjRQrST9c2Q .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-pyKBIKjRQrST9c2Q .error-icon{fill:#552222;}#mermaid-svg-pyKBIKjRQrST9c2Q .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-pyKBIKjRQrST9c2Q .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-pyKBIKjRQrST9c2Q .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-pyKBIKjRQrST9c2Q .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-pyKBIKjRQrST9c2Q .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-pyKBIKjRQrST9c2Q .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-pyKBIKjRQrST9c2Q .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-pyKBIKjRQrST9c2Q .marker{fill:#333333;stroke:#333333;}#mermaid-svg-pyKBIKjRQrST9c2Q .marker.cross{stroke:#333333;}#mermaid-svg-pyKBIKjRQrST9c2Q svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-pyKBIKjRQrST9c2Q p{margin:0;}#mermaid-svg-pyKBIKjRQrST9c2Q .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-pyKBIKjRQrST9c2Q text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-pyKBIKjRQrST9c2Q .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-pyKBIKjRQrST9c2Q .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-pyKBIKjRQrST9c2Q .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-pyKBIKjRQrST9c2Q .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-pyKBIKjRQrST9c2Q #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-pyKBIKjRQrST9c2Q .sequenceNumber{fill:white;}#mermaid-svg-pyKBIKjRQrST9c2Q #sequencenumber{fill:#333;}#mermaid-svg-pyKBIKjRQrST9c2Q #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-pyKBIKjRQrST9c2Q .messageText{fill:#333;stroke:none;}#mermaid-svg-pyKBIKjRQrST9c2Q .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-pyKBIKjRQrST9c2Q .labelText,#mermaid-svg-pyKBIKjRQrST9c2Q .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-pyKBIKjRQrST9c2Q .loopText,#mermaid-svg-pyKBIKjRQrST9c2Q .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-pyKBIKjRQrST9c2Q .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-pyKBIKjRQrST9c2Q .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-pyKBIKjRQrST9c2Q .noteText,#mermaid-svg-pyKBIKjRQrST9c2Q .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-pyKBIKjRQrST9c2Q .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-pyKBIKjRQrST9c2Q .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-pyKBIKjRQrST9c2Q .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-pyKBIKjRQrST9c2Q .actorPopupMenu{position:absolute;}#mermaid-svg-pyKBIKjRQrST9c2Q .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-pyKBIKjRQrST9c2Q .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-pyKBIKjRQrST9c2Q .actor-man circle,#mermaid-svg-pyKBIKjRQrST9c2Q line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-pyKBIKjRQrST9c2Q :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 适配器根本没被查找 alt 监听器接管 委托下去 llm/stream(request) 直接返回一个流 (回放录像 / 命中缓存) next() 此时才查找适配器 HTTP 流 StreamChunk*

这张图回答的问题:为什么"录制/回放模型响应"这种功能不需要改任何核心代码。

有一条边界很诚实:驱动在外层 waterfall 返回流句柄时就认为"发生了一次请求尝试",但这个边界不证明惰性的终端适配器真的被构造了、或者真的开始了厂商 I/O

五、请求准备:prepareCall() 与它解决的问题

直接调用方用 resolveCallConfig()agent loop 用 prepareCall(),区别在于后者:

  • 模型解析、持久化 header 记录、派发 这三件事之间保持同一个注册
  • 保留那次精确查找得到的上下文元数据;
  • 报告哪些配置字段是适配器补的默认值(而不是调用方提出的)。

PreparedLlmCall.stream() 还有个防呆:请求的调用配置字段必须和准备时一致,重用或不匹配都会以 INVALID_PREPARED_CALL 失败

为什么要这么讲究?因为路由是可以被热替换的(第 4 篇:配置改一行,子树重组)。如果一次请求的"解析模型 → 写 header → 派发"三步跨越了一次路由替换,你会得到一条记录的 header 和实际发出的请求不一致的日志。而这直接违反第 6 篇那条不变量。

同理,重试策略也是在 serving 注册那一刻捕获的

"llmRetryPolicyOf(stream) returns the value captured from the serving registration after the call selects it, so later route disposal or replacement cannot change an in-flight failure's recovery policy."

六、Replay 状态:推理链复用的归属规则

新一代推理模型往往可以复用上一轮的内部推理状态。DSH 的处理方式很值得学:

"A successful finish may carry a ReplayEnvelope: opaque response-level metadata plus optional per-block entries aligned with the emitted block sequence."

三条规则:

  1. 对齐是 harness 的词汇。 装配丢掉某个内容块时,同一位置的 replay 条目也丢掉------所以存下来的元数据永远描述的是存下来的内容。
  2. 只在"完全同一个适配器实例"时才传回去。 历史 provider 和目标 provider 当前必须注册在同一个适配器实例上,才把这份私有状态交给它;其他适配器只拿到 provider 中立的内容。
  3. 持久内容永远权威。 读取方适配器用不了这份状态时,这一条消息降级 成 provider 中立转换 + 一条诊断,而不是让整个请求失败

第 3 条是这个仓库的典型风格:优化失效时降级,而不是报错。

七、失败行为小结

出什么事 怎么办
传输/协议错误 适配器 throwLlmError.failureLlmFailure
厂商在流内报错 finish {kind:'error', failure} 结束流,同一个失败类型
厂商流卡住 五分钟默认空闲超时 → TIMEOUT;更早的调用方中止仍记 ABORTED
上下文超了 归一成 CONTEXT_WINDOW_EXCEEDED → 交给压缩恢复路径(第 7 篇)
模型返回空 EMPTY_RESPONSE,默认重试
重试策略配置里既有 always 模式又留着 normal-only 字段 解析器忽略失效字段,捕获纯 always 策略
省略 provider 策略 用 normal 默认值:五次重试
存下来的 replay 状态读不懂 这一条消息降级为中立转换 + 诊断,不失败

⚓ 回到示例

第 6 步完整展开:

  1. 驱动调 prepareCall():选中 deepseek 路由 → 解析模型(拿到精确模型标识、上下文容量、适配器默认的 maxTokens)→ 捕获这条注册
  2. request/header 落日志(第 6 篇 seq 4);
  3. agent/request waterfall:没人拦,直接过;
  4. llm/stream waterfall:也没人拦 → 终端续延此时才查找适配器 → DeepSeek 适配器发出 HTTP 请求,头里带 attributionHeaders()
  5. 五分钟空闲看门狗上膛。

第 7 步 :分片流回来。适配器只需要吐出格式正确的 StreamChunk------block-start / block-endindex 相关性加上 BlockAssembler,让块重组不是每个适配器各自的问题 。驱动把每一个分片原样写进 assistant/chunk(第 6 篇的 seq 5--40),流结束后组装成 assistant/message 并带上 usage。

这一步里模型输出了一个 tool_use 块,arguments'{"path":"package.json"}' ------ 原样字符串,一路没被解析过 (契约 2)。它会以完全相同的字节落进第 8 步的 tool/call 事件。

如果这次请求失败了会怎样?假设厂商返回 503:

  1. 适配器 throwLlmFailure 里带错误码;
  2. step/end 关掉第 1 步;
  3. agent/request-error waterfall 拿到错误 + serving 时捕获的重试策略(默认五次);
  4. dsh-llm-retry 判定可重试 → 修复 → 返回 { kind: 'retry' }
  5. 另开一个新编号的 turn 重试------所以日志里你会看到 turn: 2,而不是"turn 1 的第二次尝试"。重试在日志里是显式的。

上一篇08 · 系统提示装配

下一篇 → 10 · 工具注册表与执行管线:一次工具调用要过五道关

回到 → 系列索引


📚 返回专栏目录

相关推荐
麻瓜code3 小时前
【Agent】从 0 手写智能体框架:ReAct + ToolCall 分层设计
人工智能·spring
来了就不要走3 小时前
亿数平台怎么样:亿象生态与数实融合路径观察
大数据·人工智能·区块链
知几蜗牛3 小时前
流式JSON为什么总报错?理解结构化输出就能接稳AI接口
人工智能
Zentceh3 小时前
AI-ISP vs 传统ISP全面对比
人工智能·深度学习·计算机视觉·车载系统·transformer·无人机·智能硬件
知几蜗牛3 小时前
AI代码评审开始跑测试,真正该升级的是团队证据链
人工智能
魔镜er3 小时前
11-循环神经网络
人工智能·pytorch·python·深度学习·神经网络
海宇AI3 小时前
零信任架构实战:基于海宇公安二要素认证即时版构建自动化灵活用工准入网关
运维·人工智能·架构·自动化
菜园前端3 小时前
CSS 基础入门教程
人工智能
AI多Agent协作实战派3 小时前
【AI探索历程14】AI也会“卡住“,而且卡住的方式五花八门
人工智能