Arcadia本地智能体工作流基建:Rust+Python轻量编排框架

📅 2026/7/12 3:57:54 👁️ 阅读次数 📝 编程学习
Arcadia本地智能体工作流基建:Rust+Python轻量编排框架

1. 项目概述:这不是又一个LLM玩具,而是一套可落地的本地智能体工作流基建

“Arcadia: Put your LLMs to Work — Part I: Setup”这个标题一出来,我就在团队晨会上被好几个同事截住问:“是不是那个开源项目?真能跑起来?”——不是演示视频里的PPT式Demo,也不是调个API就完事的玩具,而是真正把大语言模型从“能回答问题”推进到“能执行任务、串联工具、持续运转”的工程化起点。Arcadia的核心定位非常清晰:它不试图替代你手头的Llama 3、Qwen或Phi-3,也不和Ollama、LM Studio抢本地模型托管的活;它专注解决一个被严重低估的痛点——模型有了,但怎么让它们稳定、可控、可调试、可组合地嵌入真实工作流里?比如,你用Qwen2-7B跑一个日报生成任务,需要自动拉取飞书多维表格数据、调用Python脚本清洗、再让模型写摘要、最后发到企业微信群——这中间每一步的错误捕获、上下文传递、重试逻辑、资源隔离,全得自己硬编码。Arcadia就是来填这张空白的。它本质上是一套轻量级、模块化、面向开发者的工作流编排框架,底层用Rust保证性能与内存安全,上层提供Python SDK和CLI,让你像搭乐高一样把“模型调用”“HTTP请求”“Shell执行”“条件判断”这些原子能力串成可靠流水线。Part I的Setup,绝不是点几下安装包就完事;它包含环境隔离策略选择、模型路由配置、工作流状态持久化机制初始化、以及最关键的——如何让本地运行的模型服务(无论你是用ollama run、llama.cpp server还是text-generation-inference)被Arcadia无感接入。我实测下来,整个Setup过程控制在15分钟内完成,但背后涉及的架构权衡,比如为什么默认用SQLite而不是PostgreSQL存执行日志、为什么工作流定义推荐用YAML而非JSON Schema、为什么CLI要内置一个轻量级HTTP代理层——这些才是真正决定你后续三个月开发体验的关键。适合谁?不是纯终端用户,而是已经能把模型跑起来、正被“每次改一行代码就要重启整个服务”折磨的工程师、自动化流程搭建者、或者想把AI能力嵌入现有内部系统的IT负责人。如果你还在用Python脚本+requests硬拼LLM调用,Arcadia的Setup就是你该按下暂停键、系统性重构的第一步。

2. 整体设计思路与核心架构选型解析

2.1 为什么是“工作流编排”而非“模型微调平台”或“聊天界面”?

很多人看到“Put your LLMs to Work”,第一反应是“又要教模型新技能”。但Arcadia的出发点恰恰相反:它默认你已经有能干活的模型,问题不在模型能力,而在能力调度的可靠性。我拿自己去年做的一个客户项目对比:他们用Llama 3做合同条款比对,原始方案是写一个Flask API,接收PDF上传→调PyPDF2提取文本→送进模型→返回JSON结果。上线两周后崩溃三次——第一次是PDF解析超时没设timeout,进程卡死;第二次是模型响应慢导致API队列积压,CPU飙到95%;第三次最典型:某份扫描件OCR识别率低,模型输出格式错乱,下游JSON解析直接抛异常,整个请求链路静默失败。根本原因?所有环节耦合在单一线程里,没有错误隔离、没有重试退避、没有状态快照。Arcadia的设计哲学就是“解耦一切可解耦的”。它的核心抽象只有三个:Node(节点)、Edge(边)、Workflow(工作流)。Node不是“一个模型”,而是“一次可重试的原子操作”,比如ollama_call节点封装了连接池、超时、重试、结果校验;http_get节点内置了JWT自动续期和429限流处理;python_script节点则强制要求脚本返回结构化字典,否则工作流直接标记为failed而非crashed。Edge定义的是数据流向和触发条件,支持on_successon_failureon_timeout三路分支,这意味着你可以轻松实现“模型调用失败→降级到规则引擎→再失败→发告警邮件”这种生产级容错。这种设计直接规避了传统方案里“一个函数里塞if-else处理所有异常”的反模式。我刻意没选DAG(有向无环图)作为底层模型,因为实际业务中循环依赖太常见——比如“生成初稿→人工审核→反馈修改意见→模型重写”,Arcadia用loop节点配合状态变量实现,比强行拆成多个DAG再用外部协调器管理要轻量得多。

2.2 Rust + Python混合栈:性能、生态与开发效率的三角平衡

Arcadia的Runtime用Rust重写,SDK和CLI用Python,这个组合不是跟风,而是基于三年内五个不同规模AI项目的血泪教训。先说Rust部分:我们做过压测,当工作流并发数超过50时,Python主线程的GIL会让HTTP节点和Shell节点的吞吐量断崖式下跌,尤其在调用本地llama.cpp server时,频繁的socket读写阻塞会拖垮整个调度器。Rust的异步运行时(Tokio)天然支持百万级连接,更重要的是它的内存安全特性直接消灭了“模型服务OOM后调度器跟着core dump”这类线上噩梦。但完全用Rust写SDK?那等于自断双臂。Python生态里,pandas处理表格、openpyxl操作Excel、weaviate-client对接向量库——这些是工程师每天都在用的轮子,用Rust重写一遍成本太高,且社区更新滞后。所以Arcadia采用“Rust Core + Python Glue”架构:所有核心调度逻辑、状态机、网络IO、日志聚合都在Rust层完成;Python SDK只做两件事——把YAML工作流定义序列化成Protobuf发给Rust Runtime,以及把执行结果反序列化成Python对象供你写测试用例。CLI工具更是如此,arcadia run --workflow daily-report.yaml这条命令背后,Python CLI启动一个Unix Domain Socket客户端,和Rust Runtime进程通信,全程零JSON解析开销。这种分层让我们的CI构建时间缩短40%,因为Rust二进制可以静态链接,发布时只有一个可执行文件;而Python部分只需pip install arcadia-sdk,依赖干净。有个细节值得提:Rust Runtime默认监听/tmp/arcadia.sock,但如果你在Docker里跑,它会自动fallback到TCP127.0.0.1:8080——这个检测逻辑写在Rust里,Python SDK完全无感,这就是混合栈的优势:底层复杂逻辑由Rust兜底,上层接口由Python保持简洁。

2.3 模型接入层设计:不绑定任何模型服务,但提供开箱即用的适配器

Arcadia最反直觉的设计是:它不提供自己的模型推理服务。你不会看到arcadia serve --model qwen2这种命令。它的模型接入层(Model Adapter Layer)本质是个协议转换器,目前支持三类后端:

  • Ollama兼容协议:直接复用ollama list的模型列表,调用/api/chat/api/generate端点,自动处理streaming响应和token计数;
  • llama.cpp server协议:适配其/completion/chat/completions端点,特别处理了n_predict参数映射和stop字符串截断;
  • OpenAI兼容协议:支持任意符合OpenAI API规范的服务(包括本地部署的text-generation-inference),自动注入api_key头和base_url

为什么这么设计?因为我们在客户现场发现,模型服务选型永远是政治问题而非技术问题。A部门坚持用Ollama(运维简单),B部门必须用llama.cpp(显存占用低),C部门已采购商业API(合规要求)。Arcadia的Adapter Layer用Rust trait object实现,新增一个后端只需实现ModelAdaptertrait的5个方法(health_checkchat_streamgenerateembedlist_models),我们内部测试过,给vLLM写适配器只花了2小时。更关键的是,它支持模型路由(Model Routing):你可以在工作流里写model: "qwen2:7b|priority=high",Arcadia会根据priority标签把高优任务路由到GPU节点,普通任务打到CPU节点——这个能力在Part I的Setup里就要配置好,否则后续无法发挥集群优势。我建议新手从Ollama开始,因为它的ollama run qwen2:7b命令能自动下载模型,省去手动管理GGUF文件的麻烦,但务必记住:Setup阶段要验证curl http://localhost:11434/api/tags能返回JSON,这是Arcadia健康检查的第一道关卡。

3. 核心Setup步骤详解与实操避坑指南

3.1 环境准备:操作系统、依赖与权限的硬性要求

Arcadia对环境的要求看似宽松,实则暗藏玄机。官方文档写“支持Linux/macOS/Windows WSL2”,但我在Windows原生CMD下踩过两次大坑:第一次是路径分隔符问题,YAML里写的script: ./scripts/clean_data.py在Windows上会被解析成.\scripts\clean_data.py,Rust Runtime找不到文件;第二次更致命:Windows的CreateProcessAPI对长命令行有8192字符限制,当工作流里嵌入大量环境变量时直接报错ERROR_INVALID_PARAMETER。所以我的实操建议是:Windows用户必须用WSL2,且发行版选Ubuntu 22.04 LTS以上。macOS用户注意M系列芯片,Arcadia默认编译x86_64二进制,你得加--target aarch64-apple-darwin参数重新编译,或者直接用Homebrew安装(brew tap arcadia-org/tap && brew install arcadia)。Linux用户重点看glibc版本:CentOS 7的glibc 2.17太老,会报version 'GLIBC_2.28' not found,必须升级到CentOS 8或换AlmaLinux 9。依赖方面,除了基础的curljq必须预装libsqlite3-dev(Ubuntu/Debian)或sqlite3-devel(RHEL/CentOS),因为Arcadia的默认状态存储后端是SQLite,编译时会链接libsqlite3.so。我见过最惨的案例是某客户在Docker里用alpine:latest镜像,里面只有sqlite3命令行工具,没有dev包,cargo build直接失败,折腾了三小时才发现缺头文件。权限方面,Arcadia Runtime进程需要读写~/.arcadia/目录(默认数据目录),如果你用systemd托管,记得在service文件里加ReadWritePaths=/home/user/.arcadia,否则journalctl里全是Permission denied。最后提醒:禁用SELinux或AppArmor,Arcadia的Unix Domain Socket通信会被拦截,错误日志里只会显示Connection refused,排查起来极其痛苦。

3.2 安装与初始化:二进制下载、配置生成与首次运行验证

Arcadia提供三种安装方式,我按推荐度排序:

  1. 官方二进制(首选):访问GitHub Releases页面,下载对应平台的arcadia-vX.X.X-<platform>.tar.gz,解压后把arcadia可执行文件放到/usr/local/bin。验证命令arcadia --version应输出类似arcadia 0.4.2 (rustc 1.78.0)。注意:不要用sudo curl | sh这种一键脚本,它会绕过包管理器,升级时容易冲突。
  2. Cargo安装(开发者向)cargo install --git https://github.com/arcadia-org/arcadia.git --branch main。好处是能随时cargo update拉最新commit,坏处是编译耗时长(Rust依赖多),且需要完整Rust toolchain。
  3. Docker(生产环境)docker run -v ~/.arcadia:/root/.arcadia -p 8080:8080 arcadiaorg/arcadia:latest。但注意:Docker容器内默认没有Ollama或llama.cpp,你得用--network host模式让容器直接访问宿主机的11434端口。

初始化命令是arcadia init,它会做三件事:

  • ~/.arcadia/创建目录结构:config/(放settings.yaml)、workflows/(放YAML定义)、logs/(执行日志)、models/(缓存模型元数据);
  • 生成默认settings.yaml,关键字段包括:
    model_adapters: ollama: base_url: "http://localhost:11434" # 必须和你的Ollama服务一致 timeout: 300 # 单次调用超时秒数,别设太短!Qwen2-7B在CPU上首token可能要15秒 llama_cpp: base_url: "http://localhost:8080" storage: backend: "sqlite" # 可选 "postgres",但PostgreSQL需额外配置 path: "~/.arcadia/arcadia.db" # SQLite文件路径
  • 创建一个示例工作流~/.arcadia/workflows/hello-world.yaml,内容是标准的“调用模型说hello”。

提示:arcadia init不会自动启动Runtime!它只是生成配置。真正的启动命令是arcadia serve,它会在后台启动Rust Runtime进程,并监听/tmp/arcadia.sock。验证是否成功:执行arcadia status,应返回Runtime: running, PID: 12345;如果显示not running,检查journalctl -u arcadia(systemd)或~/.arcadia/logs/arcadia.log

3.3 模型服务对接实战:Ollama、llama.cpp与OpenAI兼容服务的三步验证法

Setup成败的关键,在于模型服务能否被Arcadia稳定调用。我总结出一套三步验证法,跳过任何一步都可能导致后续工作流静默失败:

第一步:独立服务健康检查
在终端执行:

# 检查Ollama curl -s http://localhost:11434/api/tags | jq '.models[0].name' # 应返回"qwen2:7b" # 检查llama.cpp server(假设运行在8080) curl -s http://localhost:8080/health | jq '.status' # 应返回"ok" # 检查OpenAI兼容服务(如TGI) curl -s http://localhost:8081/v1/models | jq '.data[0].id' # 应返回模型ID

如果这一步失败,Arcadia连握手都做不到。常见问题:Ollama服务没启动(systemctl --user start ollama)、防火墙阻止端口(sudo ufw allow 11434)、或模型没拉取(ollama pull qwen2:7b)。

第二步:Arcadia Adapter层连通性测试
arcadia test-adapter命令:

arcadia test-adapter --adapter ollama --model qwen2:7b --prompt "Hello"

它会绕过工作流引擎,直接调用Adapter的chat_stream方法,输出原始响应流。成功标志是看到{"message": {"role": "assistant", "content": "Hello!"}}这样的JSON块。如果报错Connection refused,检查settings.yaml里的base_url是否多写了斜杠(http://localhost:11434/末尾的/会导致404);如果报错Model not found,确认Ollama里模型名是qwen2:7b而非qwen2(Ollama的tag必须精确匹配)。

第三步:端到端工作流执行验证
编辑~/.arcadia/workflows/hello-world.yaml,确保model字段和你的服务匹配:

nodes: - id: "greet" type: "ollama_call" # 或 "llama_cpp_call", "openai_chat" config: model: "qwen2:7b" prompt: "Say hello in Chinese" temperature: 0.1 edges: - from: "greet" to: "end"

然后执行:

arcadia run --workflow ~/.arcadia/workflows/hello-world.yaml --verbose

--verbose会打印每一步的输入输出。成功时你会看到:

[INFO] Executing node 'greet' with model 'qwen2:7b' [DEBUG] Sending request to Ollama: {"model":"qwen2:7b","prompt":"Say hello in Chinese","temperature":0.1} [INFO] Node 'greet' completed successfully. Output: "你好!"

如果卡在[DEBUG] Sending request...,说明网络不通;如果返回{"error":"context length exceeded"},说明模型上下文长度不够,需在config里加num_ctx: 4096参数。

3.4 高级配置:SQLite优化、日志分级与环境变量注入技巧

默认的SQLite配置在小规模使用时没问题,但一旦工作流日志量上来,就会遇到锁表问题。我在一个日均2000次执行的客户环境里,把~/.arcadia/arcadia.db的写入延迟从平均120ms降到8ms,关键就三步:

  1. settings.yamlstorage段加journal_mode: WAL(Write-Ahead Logging),开启并发写入;
  2. synchronous: NORMAL,牺牲一点持久性换取速度(Arcadia本身有重试机制,磁盘掉电风险极低);
  3. 手动执行SQLite优化:
    sqlite3 ~/.arcadia/arcadia.db "PRAGMA journal_mode = WAL;" sqlite3 ~/.arcadia/arcadia.db "PRAGMA synchronous = NORMAL;" sqlite3 ~/.arcadia/arcadia.db "VACUUM;" # 清理碎片

日志分级是另一个被忽视的点。Arcadia默认日志级别是INFO,但调试工作流时你需要DEBUG。别直接改settings.yaml,因为arcadia serve启动后会覆盖它。正确做法是:

  • 启动时加环境变量:RUST_LOG=arcadia_runtime=debug arcadia serve
  • 或在~/.arcadia/config/settings.yaml里加:
    logging: level: "debug" file: "~/.arcadia/logs/arcadia-debug.log"

这样arcadia.log保留INFO,arcadia-debug.log记录DEBUG,互不干扰。

环境变量注入是工作流安全的关键。Arcadia支持两种方式:

  • 全局注入:在settings.yamlenvironment段写:
    environment: API_KEY: "${SECRET_API_KEY}" # 从系统环境变量读取 DB_URL: "postgresql://user:pass@localhost:5432/arcadia"
  • 节点级注入:在YAML工作流的node.config里加env
    - id: "fetch_data" type: "http_get" config: url: "https://api.example.com/data" env: AUTH_TOKEN: "${USER_TOKEN}" # 仅此节点可见

注意:${VAR_NAME}语法只支持系统环境变量,不支持.env文件。如果要用.env,得在启动arcadia serve前用export $(grep -v '^#' .env | xargs)加载。

4. 常见问题排查与独家避坑经验实录

4.1 “Connection refused”错误的七种可能及精准定位法

Connection refused是Setup阶段最高频的报错,但背后原因千差万别。我整理了一份速查表,按发生概率排序:

现象定位命令解决方案
arcadia test-adapter报错,但curl http://localhost:11434/api/tags成功ss -tuln | grep :11434检查Ollama是否监听127.0.0.1:11434而非0.0.0.0:11434,Arcadia默认连127.0.0.1
arcadia run卡住,arcadia status显示runninglsof -i :11434确认Ollama进程存在,有时systemctl --user restart ollama后端口未释放
Docker容器内报错,宿主机curl正常docker exec -it <container> curl -v http://host.docker.internal:11434/api/tagsWindows/macOS用host.docker.internal,Linux用--add-host=host.docker.internal:host-gateway
arcadia serve启动失败,日志无信息`strace -e trace=connect,socket arcadia serve 2>&1 | grep -E "(114348080)"`
WSL2内报错,Windows上Ollama正常cat /etc/resolv.conf | grep nameserverWSL2的DNS可能指向Windows,改/etc/wsl.conf[network] generateHosts = true
首次运行后所有调用都失败ls -la ~/.arcadia/|find ~/.arcadia -name "*.lock"删除arcadia.lock文件,可能是上次异常退出未清理
日志里出现connection reset by peerarcadia test-adapter --adapter ollama --model qwen2:7b --prompt "a"测试极短prompt,排除模型OOM导致服务崩溃

最隐蔽的案例:某客户在Kubernetes里部署Ollama,Service的targetPort写成11434,但Pod里Ollama实际监听8080curl能通是因为Service做了端口映射,但Arcadia的Adapter直连Pod IP,自然Connection refused。解决方案是把settings.yamlbase_url改成http://<pod-ip>:8080,或在Service里暴露8080端口。

4.2 模型响应“卡住”或“格式错乱”的底层原因与修复

工作流里模型节点长时间无响应,或返回{"error":"invalid json"},表面看是模型问题,实则是Arcadia的流式解析机制在作祟。Arcadia默认启用stream: true,逐块接收SSE(Server-Sent Events)响应,每块必须是合法JSON。但很多模型服务(尤其是llama.cpp)在流式响应时,会把data:前缀和JSON混在一起发送,比如:

data: {"model":"qwen2:7b","created_at":"2024-05-20T10:00:00Z","message":{"role":"assistant","content":"Hello"}}

Arcadia的Rust解析器期望纯JSON,遇到data:就报错。解决方案有两个:

  • 临时修复:在settings.yaml里为llama.cpp adapter加stream: false,强制用非流式调用;
  • 永久修复:升级llama.cpp到v0.2.30+,它修复了SSE格式问题;或在Arcadia的llama_cpp_call节点配置里加parse_sse: true(0.4.2版本已支持)。

另一个高频问题是模型返回非预期格式。比如你期望{"summary":"xxx"},但模型返回了Here is the summary: xxx。Arcadia提供了output_schema参数强制校验:

- id: "summarize" type: "ollama_call" config: model: "qwen2:7b" prompt: "Summarize this text in JSON: {{input.text}}" output_schema: type: "object" properties: summary: type: "string" required: ["summary"]

Arcadia会在收到响应后,用jsonschema库验证,不匹配则自动重试(最多3次),并记录validation_error到日志。这个功能在Part I Setup时就要启用,否则后续调试成本极高。

4.3 工作流定义YAML的语法陷阱与调试技巧

YAML看着简单,实则坑多。我列出三个必踩的坑:

  1. 缩进空格数不一致:YAML要求严格2空格缩进,但很多人用Tab或4空格。Arcadia报错是YAML parse error: did not find expected key,极其难定位。解决方案:用VS Code装YAML插件,开启editor.detectIndentation: true,并设置editor.insertSpaces: true
  2. 字符串中的冒号prompt: "The answer is: 42"会被YAML解析器误认为键值对。必须加引号:prompt: "The answer is: 42"prompt: 'The answer is: 42'
  3. 布尔值大小写enabled: true正确,enabled: Trueenabled: YES会被解析成字符串,导致条件分支失效。

调试技巧:Arcadia提供arcadia validate命令,能静态检查YAML语法和Schema:

arcadia validate --workflow ~/.arcadia/workflows/daily-report.yaml

它会报告:

  • 行号12:model字段缺失(required)
  • 行号25:output_schema不是合法JSON Schema
  • 行号33:edge.from引用的节点process_csv不存在

比肉眼找快十倍。更狠的是arcadia dry-run:它不执行任何真实操作,只模拟工作流执行路径,输出每个节点的输入输出模板。比如:

arcadia dry-run --workflow ~/.arcadia/workflows/daily-report.yaml

输出:

Node 'fetch_sales': Input = {"date": "2024-05-20"} Node 'clean_data': Input = {"raw_data": "[{...}]"} # 模拟上一步输出 Node 'generate_report': Input = {"cleaned_data": "...", "template": "sales.j2"}

这让你在写真实代码前,就能确认数据流向是否符合预期,避免“写完跑不通再改”的返工。

4.4 性能瓶颈诊断:从CPU飙升到内存泄漏的全流程分析

Setup完成后,你可能会发现arcadia serve进程CPU长期90%+,或内存缓慢增长最终OOM。这不是Arcadia的Bug,而是配置不当的信号。诊断流程如下:

  1. 确认是否真瓶颈top -p $(pgrep arcadia),看是arcadia进程本身CPU高,还是它fork的子进程(如ollama)高。如果是后者,问题在模型服务,和Arcadia无关。
  2. 检查工作流循环:用arcadia list-runs --limit 10看最近执行记录,如果同一工作流status: running持续超过5分钟,大概率是某个节点没设timeout,卡死了。
  3. 内存分析arcadia serve启动时加--memory-profiling参数,它会每30秒采样一次内存分配,生成~/.arcadia/logs/memory-profile-<timestamp>.svg。打开SVG文件,能看到tokio::tasksqlite3的内存占比。如果sqlite3占80%,说明日志表没清理,执行VACUUM;如果tokio::task占高,说明并发数设太大,调低settings.yaml里的concurrency_limit
  4. 网络延迟arcadia run --workflow test.yaml --verbose,看[DEBUG] Sending request...[INFO] Node completed的时间差。如果单次调用>10秒,检查模型服务是否在CPU上跑(Qwen2-7B在i7-11800H上首token约8秒,合理;如果>30秒,可能是显存不足触发swap)。

我遇到过最诡异的案例:客户在ARM服务器上跑Arcadia,arcadia serve内存每小时涨50MB,查了三天发现是openssl库的TLS握手缓存泄漏,解决方案是升级系统openssl到3.0.12+,并在settings.yaml里加tls: { disable_cache: true }

5. 实战工作流搭建:从零构建一个自动日报生成系统

5.1 需求拆解与节点设计:把模糊需求翻译成原子操作

客户提的需求很朴素:“每天早上9点,自动汇总销售数据,生成带图表的PDF日报,发到钉钉群”。这句话里藏着五个必须拆解的原子任务:

  • 数据获取:从MySQL数据库拉取昨日销售数据;
  • 数据清洗:处理NULL值、统一货币单位、计算环比;
  • 内容生成:用Qwen2-7B写日报摘要,强调增长点和风险项;
  • 图表渲染:用Python的matplotlib画销售额趋势图;
  • PDF合成与分发:把文字和图表合并成PDF,用钉钉机器人Webhook发送。

Arcadia的节点设计原则是:每个节点只做一件事,且这件事必须可独立测试。所以我不写一个generate_daily_report大函数,而是拆成:

  • mysql_query节点:执行SQL,输出JSON数组;
  • python_script节点:调用clean_data.py,输入是上一步JSON,输出是清洗后JSON;
  • ollama_call节点:喂清洗后数据给模型,输出Markdown摘要;
  • python_script节点:调用render_chart.py,输入是JSON,输出PNG文件路径;
  • pdf_merge节点:调用weasyprint,合并Markdown和PNG;
  • dingtalk_post节点:HTTP POST到钉钉Webhook。

注意:python_script节点要求脚本必须以#!/usr/bin/env python3开头,且返回{"output": {...}}格式字典,否则Arcadia无法解析。我专门写了个script_template.py放在~/.arcadia/scripts/里,新脚本都继承它,省去重复造轮子。

5.2 YAML工作流编写:参数化、错误处理与重试策略

以下是daily-report.yaml的核心片段,展示如何用YAML表达复杂逻辑:

# 参数化:让工作流可复用 parameters: - name: "report_date" type: "date" default: "yesterday" - name: "dingtalk_webhook" type: "string" required: true nodes: # 1. 数据获取,带重试 - id: "fetch_data" type: "mysql_query" config: host: "${DB_HOST}" port: 3306 database: "sales_db" user: "${DB_USER}" password: "${DB_PASSWORD}" query: | SELECT date, product, revenue FROM sales WHERE date = '{{ parameters.report_date }}' timeout: 60 retry: max_attempts: 3 backoff: "exponential" # 指数退避:1s, 2s, 4s # 2. 数据清洗,失败则降级 - id: "clean_data" type: "python_script" config: script: "./scripts/clean_data.py" input: "{{ nodes.fetch_data.output }}" timeout: 30 on_failure: - id: "fallback_to_empty" type: "set_variable" config: variable: "cleaned_data" value: "[]" # 3. 内容生成,强制JSON Schema校验 - id: "generate_summary" type: "ollama_call" config: model: "qwen2:7b" prompt: | 你是一个销售分析师。请基于以下数据生成日报摘要: {{ nodes.clean_data.output }} 要求:输出JSON,包含字段'summary'(字符串)、'key_insights'(字符串数组)、'risks'(字符串数组) output_schema: type: "object" properties: summary: {type: "string"} key_insights: {type: "array", items: {type: "string"}} risks: {type: "array", items: {type: "string"}} required: ["summary", "key_insights", "risks"] # 4. 图表渲染,输出文件路径 - id: "render_chart" type: "python_script" config: script: "./scripts/render_chart.py" input: "{{ nodes.clean_data.output }}" # 输出路径自动注入到环境变量 env: OUTPUT_DIR: "/tmp/arcadia-charts" # 5. PDF合成,合并所有资产 - id: "merge_pdf" type: "pdf_merge" config: markdown: "{{ nodes.generate_summary.output.summary }}" chart_path: "{{ nodes.render_chart.output.chart_path }}" template: "report.j2" # 6. 发送到钉钉 - id: "send_to_dingtalk" type: "http_post" config: url: "{{ parameters.dingtalk_webhook }}" headers: Content-Type: "application/json" body: | { "msgtype": "markdown", "markdown": { "title": "日报", "text": "{{ nodes.merge_pdf.output.pdf_url }}" } } edges: - from: "fetch_data" to: "clean_data" - from: "clean_data" to: "generate_summary" - from: "