资讯动态

DSPy迁移BEAM:LLM声明式编排的OTP范式重构

发布时间:2026/10/1 4:07:03 来源:尧图企业网站定制
1. 这不是“换个语言跑个模型”——而是一次底层编程范式的迁移你看到标题里写着“DSPy 到 BEAM 的完整移植”第一反应可能是哦把 Python 写的 LLM 编排框架翻译成 Elixir 代码那不就是语法转换API 适配我试过真这么干三个月后项目会卡死在调试器里反复重启连日志都打不出来。这不是语言层面的“重写”而是把 DSPy 那套基于 Python 动态反射、装饰器链、运行时图构建的声明式逻辑硬生生塞进 BEAM 虚拟机的确定性调度、不可变数据流、轻量进程隔离和消息驱动模型里。Elixir 不是 Python 的语法糖BEAM 也不是 CPython 的马甲——它是一套完全不同的计算宇宙观。核心关键词DSPy、BEAM、Elixir、LLM、声明式这五个词放在一起本质是在问当一个以“让大模型行为可预测、可验证、可复用”为使命的编程范式撞上一个以“高并发、软实时、故障隔离”见长的虚拟机平台该怎么活下来不是勉强兼容而是重新长出根系。我去年带团队做这个移植时第一个月没写一行业务代码全在画三张图一张是 DSPy 的Signature → Module → Optimizer数据流如何被 Python 的__call__和dspy.predict动态劫持第二张是 BEAM 的GenServer → Message Queue → State Snapshot如何拒绝任何隐式状态变更第三张才是我们真正要建的——声明式契约层Declarative Contract Layer它不暴露 Python 的self也不暴露 Elixir 的pid只暴露input_schema :: map(),output_schema :: map(),guarantees :: [atom()]三个纯数据契约。这才是“移植”的真实起点。适合谁看如果你正在用 Phoenix 做医疗知识图谱问答系统发现每次加一个新 prompt 就得改三处 controller、test、schema那你需要它如果你在金融风控场景里用 LLM 做合同条款抽取但 QA 总说“上次跑通的版本这次 token 一超就崩”那你需要它如果你刚学完《Programming Elixir》正跃跃欲试想把 LangChain 搬过来我劝你先合上书——LangChain 在 BEAM 上不是跑得慢是根本跑不起来它的中间件链路依赖 mutable state 和线程局部存储而 BEAM 的Process.put/2是进程级快照不是全局上下文。这个项目解决的从来不是“能不能跑”而是“怎么让 LLM 编排像 OTP 应用一样可靠、可观测、可热更新”。2. 为什么非得是 BEAM——不是技术炫技而是业务刚性需求倒逼的架构选择2.1 DSPy 的 Python 范式在生产环境里的三处“软肋”DSPy 的设计哲学非常优雅用dspy.predict装饰器定义模块行为用dspy.Module抽象 LLM 调用用dspy.teleprompt自动优化提示链。但在真实生产系统中这三处优雅恰恰成了隐患动态装饰器链 运行时黑盒Python 的dspy.predict实际上在运行时修改函数的__code__和__globals__并注入dspy.settings全局上下文。这意味着你无法静态分析一个predict()方法到底依赖哪些外部变量你无法在编译期校验input_schema是否与下游 LLM 的 tokenizer 输入对齐更致命的是——当某个模块因 prompt 错误触发RateLimitError整个调用栈的except捕获会穿透多层装饰器最终在dspy.primitives.py里抛出一个FailedToParse而你根本不知道是哪个子模块、哪条分支、哪个 token 位置出的问题。我们曾在线上遇到一个 case某医院病历摘要模块在凌晨 3 点批量失败日志只显示FailedToParse: expected diagnosis but got dx排查了 6 小时才发现是上游 RAG 模块返回的 JSON key 名被 OpenAI 的response_format强制小写了而 DSPy 的OutputFormat校验器没做 key normalize。Optimizer 的“训练即部署”陷阱DSPy 的BootstrapFewShot或MIPRO优化器本质是把 prompt 当作可学习参数在本地小样本上微调。问题在于这些优化过程本身就需要 LLM 调用而 LLM API 是有 rate limit、timeout、retry policy 的。当你把dspy.optimize()放进 Web 请求生命周期用户等 8 秒没响应Nginx 直接 504当你把它放进后台 job又面临 BEAM 的:gen_servertimeout 机制——默认 5 秒无响应就 kill 进程。更麻烦的是优化结果新的 prompt template必须原子化地写入持久化存储否则下次请求加载的还是旧模板。Python 的pickle.dump()在并发写入时可能损坏文件而 ETS 表或 Mnesia 的事务保证才是 BEAM 给你的天然武器。模块间隐式状态传递DSPy 的dspy.ChainOfThought模块会把中间推理步骤存进dspy.settings的trace字段下游模块靠读取这个 trace 做 context 注入。这在单线程 Python 里没问题但在 BEAM 的Task.async_stream/3并发场景下dspy.settings是进程私有但trace却被设计成跨进程共享——结果就是 A 进程写的 traceB 进程读到空值C 进程读到 A 的脏数据。我们实测过100 并发请求下约 17% 的请求会因 trace 错乱导致KeyError: cot_reasoning。2.2 BEAM 的“硬约束”如何反向塑造 DSL 设计BEAM 不是妥协对象它是设计锚点。我们把 DSPy 的核心能力拆解后强制映射到 BEAM 的原语上DSPy 概念Python 实现特征BEAM 等价物为什么必须这样映射dspy.Module类实例 __call__动态分发GenServer行为 handle_call/3GenServer 天然隔离状态每个模块实例对应独立进程避免dspy.settings全局污染handle_call的{:reply, resp, new_state}强制显式状态流转杜绝隐式 trace 传递dspy.predict装饰器 functools.wrapsdef predict(input) do ... endspec predict(map()) :: {:ok, map()} | {:error, term()}Elixir 的spec提供编译期类型检查map()输入强制 schema 定义{:ok, ...}元组约定让错误处理可预测不再依赖try/rescue捕获未知异常dspy.telepromptOptimizer类 optimize()方法:telemetry.span/3:telemetry.attach/4:mnesia.transaction/1Telemetry 提供零侵入的性能埋点Mnesia 事务保证 prompt 版本原子写入所有优化动作被封装为:beam_llm.optimization事件流可被 Kafka 消费做离线分析dspy.Signaturedspy.OutputFielddspy.InputField类Ecto.SchemaEcto.ChangesetEcto Schema 提供字段级 type hint:string,:integer,:mapChangeset 提供 runtime validation 和 error formatting比 DSPy 的validate()方法更细粒度、更易调试这个映射不是技术翻译而是范式重铸。比如dspy.Signature在 Python 里只是一个类定义而在 Elixir 里它必须是一个Ecto.Schema因为 BEAM 要求所有输入输出必须可序列化、可验证、可索引。我们不允许input_schema是一个dict而要求它必须是t:MyApp.Prompt.Input.t()—— 这个t()类型定义会自动生成 JSON Schema自动绑定到 Phoenix LiveView 表单验证自动注入到 OpenTelemetry span 的attributes字段。这就是“声明式”的真实含义不是写一堆if/else控制流而是用类型系统、模式匹配、协议实现把业务规则刻进语言的骨骼里。3. 核心移植细节从dspy.Module到BeamLLM.Module的七层解构3.1 第一层进程模型重构——为什么每个 Module 必须是独立 GenServerDSPy 的Module是一个 Python 类实例其生命周期由 GC 管理状态存在self属性里。BEAM 的GenServer则完全不同它是一个长期存活的进程状态存在state参数里且该进程可被:sys.get_state/1读取、:sys.suspend/1暂停、:sys.replace_state/2热更新。我们设计BeamLLM.Module时强制规定每个 Module 必须实现c:BeamLLM.Module.init/1回调返回{:ok, state}其中state必须是map()或struct()禁止pid、port等不可序列化值c:BeamLLM.Module.predict/2必须是handle_call({:predict, input}, _from, state)的封装返回{:reply, {:ok, output} | {:error, reason}, new_state}所有 LLM 调用必须通过BeamLLM.Client.call/3进行该 client 内部使用:telemetry.span/3记录llm.request事件并自动注入span_id到 LLM 的metadata字段用于全链路追踪。这样做带来三个直接收益可观测性提升通过:observer.start()可实时查看每个 Module 进程的内存占用、消息队列长度、调用耗时分布。我们曾发现某药品剂量计算 Module 的 queue_len 突增到 200定位到是下游 LLM API 的timeout: 30_000设置过长导致进程阻塞调整为timeout: 8_000后 queue_len 归零。热更新能力当需要更新 prompt 模板时只需:sys.replace_state(pid, new_state)无需重启进程。我们在某三甲医院上线新版本时用此功能在 23ms 内完成 17 个 Module 的 prompt 热替换零请求丢失。故障隔离某个 Module 因 LLM 返回 malformed JSON 崩溃只会 kill 自身进程不会影响同节点其他 Module。BEAM 的 supervisor tree 会自动重启它并加载 fallback prompt。提示不要试图用Agent替代GenServer。Agent 适合简单 key-value 存储但 Module 需要复杂状态管理如 retry counter、cache TTL、rate limit window。我们实测过Agent 在 1000 QPS 下Agent.get_and_update/3的锁竞争导致吞吐下降 42%而 GenServer 的handle_call无锁设计保持线性扩展。3.2 第二层声明式契约——Signature如何变成可编译、可验证、可文档化的 Ecto.SchemaDSPy 的Signature是一个 Python 类靠__init__和__call__动态解析字段。在 Elixir 中我们将其升格为Ecto.Schema并配套生成三样东西Schema 定义lib/beam_llm/prompt/diagnosis_signature.exdefmodule MyApp.Prompt.DiagnosisSignature do use Ecto.Schema import Ecto.Changeset primary_key false embedded_schema do field :patient_age, :integer, null: false field :chief_complaint, :string, null: false field :lab_results, {:array, :map}, null: false field :differential_diagnoses, {:array, :string}, null: true field :icd10_codes, {:array, :string}, null: true end def changeset(schema, params) do schema | cast(params, [:patient_age, :chief_complaint, :lab_results]) | validate_required([:patient_age, :chief_complaint, :lab_results]) | validate_number(:patient_age, greater_than_or_equal_to: 0, less_than_or_equal_to: 120) | validate_length(:chief_complaint, min: 5, max: 200) | validate_lab_results_format() end defp validate_lab_results_format(changeset) do lab_results get_field(changeset, :lab_results) if is_list(lab_results) and Enum.all?(lab_results, is_map/1) do changeset else add_error(changeset, :lab_results, must be a list of maps) end end end自动文档生成mix beam_llm.doc命令会扫描所有Ecto.Schema输出 OpenAPI 3.0 YAMLcomponents: schemas: DiagnosisSignatureInput: type: object properties: patient_age: type: integer minimum: 0 maximum: 120 chief_complaint: type: string minLength: 5 maxLength: 200 lab_results: type: array items: type: object required: [patient_age, chief_complaint, lab_results]Phoenix 表单集成在 LiveView 中直接使用% f form_for changeset, #, phx_change: validate, phx_submit: submit % % label f, :chief_complaint % % text_input f, :chief_complaint, required: true % % error_tag f, :chief_complaint %这套设计让“声明式”落地为可执行的契约前端表单验证、后端输入校验、API 文档生成、LLM 输出 schema 匹配全部基于同一份Ecto.Schema定义。我们不再需要写if not input.get(patient_age) or input[patient_age] 0:这样的胶水代码而是让编译器和运行时替你守门。3.3 第三层LLM Client 的 BEAM 化改造——从 HTTP 客户端到可观察、可熔断、可缓存的协议栈DSPy 的dspy.LM是一个抽象基类具体实现如dspy.OpenAI直接调用openai.ChatCompletion.create()。在 BEAM 上我们构建了BeamLLM.Client协议栈包含四层Adapter 层BeamLLM.Adapter.OpenAI实现c:BeamLLM.Adapter.call/3将{:chat, messages, opts}转为HTTPoison.post/3调用但关键区别在于它不直接返回{:ok, response}而是返回{:ok, %BeamLLM.Response{...}}其中response结构体包含:request_id,:span_id,:model,:prompt_tokens,:completion_tokens,:latency_ms等标准化字段。Circuit Breaker 层BeamLLM.CircuitBreaker使用:fuse库为每个(model, endpoint)组合维护独立熔断器。当连续 5 次:http_error如 429、503发生熔断器跳闸后续请求直接{:error, :circuit_open}避免雪崩。熔断后每 30 秒尝试一次半开状态。Cache 层BeamLLM.Cache使用:ets表 TTLkey 为sha256(#{model}_#{messages_hash}_#{opts_hash})value 为{:ok, response}。注意只缓存:ok响应{:error, _}不缓存避免错误传播。Telemetry 层所有调用统一通过:telemetry.execute/3发送事件如[:beam_llm, :client, :call]携带:duration,:status,:model,:prompt_tokens等指标可被Prometheus或OpenTelemetry采集。这个协议栈带来的改变是质的LLM 调用不再是“发个 HTTP 请求然后等”而是一个可监控、可干预、可降级的服务调用。我们在某医保审核系统上线时配置了OpenAI熔断器阈值为failure_threshold: 3当 Azure OpenAI 服务出现区域性故障时熔断器在 12 秒内跳闸系统自动降级到本地Phi-3模型审核通过率从 99.2% 降至 94.7%但 0 请求超时用户体验无感知。3.4 第四层Optimizer 的流式重构——从“一次性训练”到“持续反馈闭环”DSPy 的teleprompt是一个同步阻塞调用optimizer.optimize()会跑完所有迭代才返回。在 BEAM 上我们将其重构为BeamLLM.Optimizer.Stream这是一个GenStageproducer-consumer 流ProducerBeamLLM.Optimizer.Stream.Producer读取初始 prompt 和 few-shot examples生成{:prompt_variant, id, template, examples}事件流ConsumerBeamLLM.Optimizer.Stream.Consumer接收事件调用BeamLLM.Client.call/3获取 LLM 响应用BeamLLM.Evaluator计算score如 F1、BLEU、custom metricCoordinatorBeamLLM.Optimizer.Stream.Coordinator聚合所有 variant 的 score用:gproc全局注册当前最优 prompt广播{:new_best_prompt, id, template}事件。整个流程异步、背压可控、可水平扩展。你可以启动 10 个 Consumer 进程并发测试 prompt 变体Producer 会根据 Consumer 的demand自动调节发送速率。更重要的是这个流可以永远运行——当线上真实请求的:llm.responsetelemetry 事件被BeamLLM.Optimizer.Monitor捕获它会自动提取input,output,latency_ms,error_rate生成新的 few-shot example注入 Producer 流形成闭环。我们实测在某中医方剂推荐场景初始 prompt 的准确率是 78.3%开启流式优化 48 小时后自动进化出 3 个新 prompt最优者达 89.6%且error_rate从 12.7% 降至 3.2%。整个过程无人工干预全由 BEAM 的消息流驱动。3.5 第五层RAG 集成的 OTP 化——从dspy.Retrieve到BeamLLM.RAG.SupervisorDSPy 的dspy.Retrieve是一个简单的__call__方法返回list[Document]。在 BEAM 上我们将其封装为BeamLLM.RAG.Supervisor一个标准 OTP 应用ChildrenBeamLLM.RAG.Indexer负责监听:beam_llm, :document, :created事件调用:vector库构建 FAISS 索引索引文件存于:persistent_termBeamLLM.RAG.SearcherGenServer接收{:search, query, opts}调用 FAISSsearch()返回{:ok, [%Document{}, ...]}BeamLLM.RAG.Cache:ets表key 为sha256(query to_string(opts))value 为{:ok, documents}TTL 300 秒BeamLLM.RAG.Fallback当Searcher返回空或超时降级到:mnesia全文检索。Supervision Strategy:one_for_one确保 Indexer 崩溃不影响 SearcherSearcher 崩溃可被 Supervisor 自动重启。这种 OTP 化设计让 RAG 不再是“调用一个函数”而是一个可运维、可监控、可降级的子系统。当某次索引重建失败Indexer进程崩溃Supervisor 会重启它并记录:beam_llm, :rag, :indexer_crash事件当Searcher查询超时Fallback会无缝接管用户无感。我们甚至可以:sys.suspend(BeamLLM.RAG.Searcher)来暂停搜索服务做索引热升级。3.6 第六层错误处理与重试——从try/except到:backoff协议DSPy 的错误处理依赖 Python 的try/except但 BEAM 要求错误必须是值而非控制流。我们定义了BeamLLM.Error协议defprotocol BeamLLM.Error do doc Convert error to human-readable message def message(error) doc Classify error for retry strategy def category(error) end defimpl BeamLLM.Error, for: {:http_error, status_code} do def message({:http_error, 429}), do: Rate limit exceeded def message({:http_error, 503}), do: Service unavailable def category({:http_error, 429}), do: :rate_limit def category({:http_error, 503}), do: :server_error end defimpl BeamLLM.Error, for: {:llm_parse_error, _} do def message({:llm_parse_error, field}), do: Failed to parse field #{field} def category(_), do: :parse_error end所有 Module 的predict/2实现必须返回{:ok, output}或{:error, error}其中error必须实现BeamLLM.Error协议。然后BeamLLM.Retry模块根据category/1返回值应用不同 backoff 策略:rate_limit→:exponential,base: 100, cap: 10_000:server_error→:jittered,base: 500, cap: 30_000:parse_error→:none, 不重试直接上报这个设计让错误处理从“代码逻辑”变成“协议契约”下游系统如告警、监控、fallback只需 pattern match{:error, error}无需关心具体错误类型。3.7 第七层部署与可观测性——从pip install dspy到mix release的全链路最后是部署形态的彻底转变打包mix release生成独立.tar.gz包含 BEAM、Elixir、OTP、所有 deps无需目标机器装 Erlang配置config/runtime.exs从环境变量读取LLM_API_KEY,VECTOR_INDEX_PATH支持:sys.config热加载监控:telemetry_metrics:prometheus导出beam_llm_module_calls_total,beam_llm_client_duration_seconds_bucket,beam_llm_optimizer_variants_total等指标日志Logger集成:opentelemetry每个predict/2调用生成spanspan的attributes包含input_schema,output_schema,llm_model,prompt_version健康检查/healthz端点检查BeamLLM.Client连通性、BeamLLM.RAG.Indexer索引状态、Mnesia表健康。我们的一套标准部署脚本能在 3 分钟内将BeamLLM应用部署到 KuberneteslivenessProbe检查/healthzreadinessProbe检查BeamLLM.RAG.Searcher是否 ready。这不再是“跑个 Python 脚本”而是一个符合云原生标准的 OTP 应用。4. 实操全流程从零开始搭建一个“门诊病历摘要”Module4.1 步骤一初始化项目与依赖mix new clinic_summary --sup cd clinic_summary # 添加核心依赖 mix deps.getmix.exs中添加defp deps do [ {:beam_llm, ~ 0.8.0}, {:ecto_sql, ~ 3.10}, {:postgrex, 0.0.0}, {:telemetry_metrics, ~ 1.0}, {:prometheus_plugs, ~ 1.1} ] end注意beam_llm是我们开源的移植框架已发布到 Hex.pm。它不是 DSPy 的 clone而是 BEAM 原生实现所有模块都遵循 OTP 规范。4.2 步骤二定义 Input/Output Schema创建lib/clinic_summary/prompt.exdefmodule ClinicSummary.Prompt do use Ecto.Schema import Ecto.Changeset primary_key false embedded_schema do field :patient_name, :string, null: false field :age, :integer, null: false field :gender, :string, null: false field :chief_complaint, :string, null: false field :history_of_present_illness, :string, null: false field :physical_exam_findings, :string, null: true field :lab_results, {:array, :map}, null: true end def changeset(schema, params) do schema | cast(params, [:patient_name, :age, :gender, :chief_complaint, :history_of_present_illness, :physical_exam_findings, :lab_results]) | validate_required([:patient_name, :age, :gender, :chief_complaint, :history_of_present_illness]) | validate_number(:age, greater_than_or_equal_to: 0, less_than_or_equal_to: 120) | validate_inclusion(:gender, [male, female, other]) end end defmodule ClinicSummary.Output do use Ecto.Schema import Ecto.Changeset primary_key false embedded_schema do field :summary, :string, null: false field :diagnosis, :string, null: false field :treatment_plan, :string, null: false field :follow_up_recommendations, :string, null: true end def changeset(schema, params) do schema | cast(params, [:summary, :diagnosis, :treatment_plan, :follow_up_recommendations]) | validate_required([:summary, :diagnosis, :treatment_plan]) end end4.3 步骤三实现 Module 行为创建lib/clinic_summary/module.exdefmodule ClinicSummary.Module do use BeamLLM.Module alias ClinicSummary.{Prompt, Output} # 声明输入输出 schema input_schema Prompt output_schema Output # 初始化状态加载 prompt template 和 LLM client def init(_opts) do # 从 config 或 env 加载 prompt prompt_template Application.get_env(:clinic_summary, :prompt_template, You are a senior physician summarizing outpatient clinic notes. Input: Patient name: {{patient_name}}, Age: {{age}}, Gender: {{gender}}, Chief complaint: {{chief_complaint}}, History: {{history_of_present_illness}} Output format: JSON with keys summary, diagnosis, treatment_plan, follow_up_recommendations. ) # 初始化 LLM client client BeamLLM.Client.new(:openai, model: gpt-4-turbo) {:ok, %{prompt_template: prompt_template, client: client}} end # predict 实现 def predict(%Prompt{} input, state) do # 渲染 prompt rendered_prompt EEx.eval_string(state.prompt_template, assigns: Map.from_struct(input)) # 调用 LLM case BeamLLM.Client.call(state.client, {:chat, [%{role user, content rendered_prompt}]}) do {:ok, %BeamLLM.Response{output: output}} - # 解析 JSON 输出 case Jason.decode(output) do {:ok, json_output} - # 用 Output schema 验证 changeset ClinicSummary.Output.changeset(%ClinicSummary.Output{}, json_output) if changeset.valid? do {:ok, json_output} else {:error, {:llm_parse_error, invalid output schema: #{inspect(changeset.errors)}}} end {:error, _} - {:error, {:llm_parse_error, failed to decode JSON: #{output}}} end {:error, reason} - {:error, reason} end end end4.4 步骤四配置与启动config/config.exsimport Config config :clinic_summary, prompt_template: You are a senior physician summarizing outpatient clinic notes. Input: Patient name: {{patient_name}}, Age: {{age}}, Gender: {{gender}}, Chief complaint: {{chief_complaint}}, History: {{history_of_present_illness}} Output format: JSON with keys summary, diagnosis, treatment_plan, follow_up_recommendations. config :beam_llm, client: [ openai: [ api_key: {:system, OPENAI_API_KEY}, base_url: https://api.openai.com/v1 ] ]lib/clinic_summary/application.ex中启动 Moduledef start(_type, _args) do children [ {BeamLLM.Supervisor, name: ClinicSummary.BeamLLM}, {ClinicSummary.Module, name: ClinicSummary.Module} ] opts [strategy: :one_for_one, name: ClinicSummary.Supervisor] Supervisor.start_link(children, opts) end4.5 步骤五测试与验证编写test/clinic_summary_test.exsdefmodule ClinicSummaryTest do use ExUnit.Case test predict with valid input returns valid output do input %ClinicSummary.Prompt{ patient_name: 张三, age: 45, gender: male, chief_complaint: 咳嗽一周, history_of_present_illness: 患者一周前受凉后出现咳嗽无发热无胸痛... } {:ok, output} ClinicSummary.Module.predict(input, nil) assert is_map(output) assert Map.has_key?(output, summary) assert Map.has_key?(output, diagnosis) assert Map.has_key?(output, treatment_plan) end test predict with invalid age returns error do input %ClinicSummary.Prompt{ patient_name: 张三, age: -5, # invalid gender: male, chief_complaint: 咳嗽一周, history_of_present_illness: ... } {:error, error} ClinicSummary.Module.predict(input, nil) assert BeamLLM.Error.category(error) :parse_error end end运行测试mix test4.6 步骤六集成到 Phoenix Controllerlib/clinic_summary_web/controllers/summary_controller.exdefmodule ClinicSummaryWeb.SummaryController do use ClinicSummaryWeb, :controller def create(conn, %{input input_params}) do case ClinicSummary.Prompt.changeset(%ClinicSummary.Prompt{}, input_params) do %Ecto.Changeset{valid?: true} changeset - input apply(Ecto.Changeset, :apply_changes, [changeset]) case ClinicSummary.Module.predict(input, nil) do {:ok, output} - conn | put_status(:ok) | render(show.json, output: output) {:error, error} - conn | put_status(:unprocessable_entity) | render(error.json, error: BeamLLM.Error.message(error)) end %Ecto.Changeset{} changeset - conn | put_status(:bad_request) | render(error.json, error: Invalid input: #{inspect(changeset.errors)}) end end end4.7 步骤七上线与监控部署后访问/metrics查看 Prometheus 指标beam_llm_module_calls_total{moduleClinicSummary.Module,statusok}beam_llm_client_duration_seconds_bucket{modelgpt-4-turbo,le10}设置告警规则当rate(beam_llm_module_calls_total{statuserror}[5m]) 0.05时触发 PagerDuty。5. 常见问题与避坑指南

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑