LLayer 实战教程:用 bash、curl 和 jq 构建 Unix 哲学驱动的 AI Agent
AI Agent 框架层出不穷,从 LangChain 到 CrewAI,每个都有大量抽象概念、复杂的类继承关系和繁重的依赖管理。但有没有一种更轻量、更透明的方式?
LLayer 给出了一个反直觉的答案:用 bash、curl 和 jq 三个最基础的 Unix 工具,就能构建一个完整的 AI Agent。这个 MIT 开源项目只有 6041 字符的 README 文档,却完整覆盖了状态管理、上下文构建、工具调度和流式输出等 Agent 核心能力。
为什么需要 Unix 风格的 AI Agent?
现有的 Agent 框架有几个共性问题:
- 状态沉重:内部维护复杂的类继承链和回调机制,调试困难
- 抽象过厚:要理解完整的数据流,需要阅读数百行框架源码
- 难以组合:很难将 Agent 的中间输出管道到其他工具做分析或过滤
LLayer 将 Agent 生命周期解构为四个独立的 shell 命令——ll-read、ll-context、ll-eval、ll-print——通过标准 Unix 管道组合成完整的 REPL 循环。每个命令只做一件事,且做好。
快速上手
需要 Docker 和 Ollama,启动只需两行:
git clone https://github.com/cloudkj/llayer.git cd llayer docker compose up --detach ./agent
等待 Ollama 启动后,./agent 就会打开一个 REPL 终端:
> 你好,今天有什么新鲜事? 今天天气不错!有什么我可以帮你的吗?
这个 REPL 的背后,实际上是以下管道链在运行:
用户输入 → ll-read(写入历史) → ll-context(构建上下文) → ll-eval(调用模型) → ll-print(展示输出)
四步管道架构拆解
LLayer 的架构极其透明。你可以独立调用每一个组件:
无状态单次调用(跳过历史记录):
echo "Hello, world" | ./ll-read | ./ll-context | ./ll-eval | ./ll-print
这条命令等价于一次完整的 Agent 交互,但没有状态持久化。每个组件的作用:
| 组件 | 功能 | 输出 |
|---|---|---|
| ll-read | 将输入写入 .jsonl 历史文件 | 触发事件 |
| ll-context | 从历史文件构建模型上下文 | JSON 格式的上下文 |
| ll-eval | 调用 Ollama API 获取响应 | 令牌流 |
| ll-print | 格式化输出到终端 | 人类可读的消息 |
有状态的完整 Agent:
./agent
agent 脚本将上述四个组件组合成一个 while 循环,并加入工具调度(ll-dispatch)步骤:
ll-read读取用户输入,追加为事件到.llayer_history文件ll-context从历史文件中压缩出当前轮次的模型上下文ll-eval调用本地 Ollama 模型,获取回复- 如果需要工具调用,
ll-dispatch分发到对应工具,将结果追加到历史,回到步骤 2 - 模型输出完成后,
ll-print展示给用户
亮点功能
1. 时光回溯式调试
因为所有状态都是只追加的 .jsonl 文件,调试变得异常简单:
head -n 20 .llayer_history cp .llayer_history .llayer_history.bak head -n 10 .llayer_history > .llayer_history.tmp mv .llayer_history.tmp .llayer_history ./agent
这在排查 Agent 的「幻觉循环」时极其有效——你可以在历史中逐条检查每个工具调用的输入输出,找到问题点后切片回退。
2. 管道威力:拦截和改造 Agent 流
因为所有组件都是标准 Unix 管道,你可以做很多框架中难以实现的事情:
用 pv 监控 Token 流速度:
echo "写一篇关于 AI Agent 的文章" | ./ll-read | ./ll-context | ./ll-eval | pv --line-mode | sponge | ./ll-print
pv --line-mode 实时显示行数、速率和总数据量,sponge 缓冲全部输出后才交给 ll-print——这在基准测试 Token 流速度时非常有用。
用 grep 过滤 PII 数据:
echo "我的邮箱是 test@example.com" | ./ll-read | ./ll-context | ./ll-eval | grep -v -E '\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b' | ./ll-print
在模型输出到达用户之前,通过 grep 过滤掉潜在的个人信息——不需要框架级别的插件机制。
3. 通过管道注入系统提示词
(echo "你是一个名叫 Mario 的意大利水管工,用 Brooklyn 口音回答" | ./ll-read --system > .llayer_history) && ./agent
--system 标志将输入标记为系统消息,ll-context 会将其作为首条上下文传递给模型。
与框架化 Agent 的对比
| 维度 | LLayer | 传统框架(LangChain/CrewAI) |
|---|---|---|
| 依赖 | bash + curl + jq + Ollama | 数百 MB 的 Python 包 |
| 状态模型 | 追加 JSONL 文件 | 内存对象 + 序列化 |
| 调试 | cat 历史文件即可 | 需框架专门的调试 UI |
| 组合性 | 标准 Unix 管道 | 框架内部 API |
| 学习曲线 | 熟悉 Unix 即可 | 需学习整套抽象概念 |
最佳实践
- 在隔离环境中运行:LLayer 通过 docker-compose 启动 Ollama,模型运行在容器中,但 Agent 逻辑在宿主机。建议在开发容器或 CI runner 中使用
- 用
sponge缓冲流式输出:直接管道的ll-eval输出是逐 Token 的,用sponge(moreutils包)缓冲后再处理可避免半截 Token 的解析错误
- 历史文件管理:长期运行的 Agent 会产生很大的
.llayer_history。定期用ll-context的压缩能力重建上下文,或设置 cron 定时清理旧事件
- 工具扩展:添加自定义工具只需写一个 bash 函数,标准输出直接变为 Agent 的输入——没有框架接口需要实现
weather() {
curl -s "https://wttr.in/$1?format=%C+%t" | head -c 200
}
然后在 ll-dispatch 的 tool 列表中注册函数名即可。
总结
LLayer 不是要取代 LangChain 或 CrewAI——在生产级应用中,框架的抽象层是必要的。但对于理解 Agent 本质、快速原型验证、以及在资源受限环境中运行 Agent 来说,Unix 管道的方法提供了一种极有价值的补充视角。
当你下次遇到 Agent 行为不符合预期时,不妨想想 LLayer 的哲学:拆解到最小单元,用管道连接,用文本文件做状态——一切都可以 cat 和 grep,没有什么不可见。
相关链接