Shell 转义歧义解析:当 LLM 遇上双重解析地狱
你有没有过这种经历:让 Agent 帮你写一段小脚本,生成的内容看起来语法完美,一跑就炸?报错信息通常是一串让你怀疑人生的 &、$、反引号乱码,然后你盯着那几行代码看了十分钟——最后发现,模型把 bash 的反斜杠转义 \" 写在了 PowerShell 命令里。
这个问题我称之为 Shell 转义歧义,在 LLM + 终端自动化这个交叉点上表现得尤其明显。本质是:LLM 生成的是一个扁平字符串,而 shell 执行的是多层嵌套的语义解析,任何一层出了偏差,最终执行的命令就和你预期完全不一样。
一、双重解析:模型以为的 vs shell 实际执行的
LLM 采用自回归方式逐 token 生成,它"以为"自己写的是结构化的命令。但实际上,如果这个字符串被 shell=True(Python subprocess)或者 Invoke-Expression(PowerShell)执行,就等于经历了 两次独立的解析:
- 第一层:LLM 生成字符串时的"心智解析"——它以为引号配对是对的
- 第二层:shell 自己再做一次 tokenize / word-splitting
这两层之间没有任何契约式的保证。模型输出一个字符串,shell 拿过去按自己的规则重新分词,然后交给操作系统执行。任何一层的歧义,都会导致最终行为和预期背离。
而且这种错误往往是 延迟发现的:代码写入时没报错,运行时才炸。模型只能"事后诸葛亮"式地不断打补丁修改脚本,一个命令可能要循环三四次才终于跑通。
二、为什么 PowerShell 比 bash 更容易踩坑
Shell 本来就不简单,但 PowerShell 在这个问题上尤其难搞。有几层原因:
1. 转义符混淆
PowerShell 的转义符是反引号 `,不是 \。但模型的训练数据里 bash 语法占绝对多数,它经常无意识地混用 bash 习惯——比如用 \" 来表示引号转义,这在 PowerShell 里会被原样当作普通反斜杠处理,导致完全不同的解析结果。
2. 语义差异大
PowerShell 对 $、@()、&、| 有一套完全不同于 bash 的语义:变量插值、数组字面量、调用操作符、管道。模型很容易犯"语法迁移错误",把 bash 的逻辑套用到 PowerShell 上。
AI Agent 的 CLI 超能力:组合工具的思维与实践
真正的『AI 原生』不是只会调用模型 API,而是学会借 cmdline 能力反过来驾驭模型。—— 来自 QPC 实践笔记
引子:原地踩坑与破局
写 Agent 项目半年,发现两个现象:
生产坑 ——
- 代码库 2k 文件,怎么快速撤离所有第三方 key?
- 日活跃 agent 实例上百,如何一眼看清谁在泄露内存?
- 高频需求:提取出所有对话里的意图标注。
AI 间歇性失能 ——
- 模型擅长创造,不擅长精确执行。
- 一次 agent 调用动辄 50k token,代价高昂。
真正的破局,不是让模型变聪明,而是把操作系统现成的精确能力用起来。
下面展示 10 个实例,每个都是agent项目中频繁踩坑后找到的确定性答案。
工具清单:让你恍然大悟的『不需要Python』场景
| 命令 | 干嘛用 | 代替的 Python 操作 | 节省 token 效果 | AI 友好度 | 成本效益 |
|---|---|---|---|---|---|
rg | 搜代码 / 搜日志 | open(file) + re.findall | ★★★★★ | ★★★★★ | 极高 |
fd | 搜文件 / 批量处理 | glob.glob + os.path | ★★★★☆ | ★★★★★ | 极高 |
jq | JSON 解析与过滤 | json.load + 遍历提取 | ★★★★★ | ★★★★★ | 极高 |
bat | 高亮代码查看 | open, syntax highlight 滚轮 | ★★★☆☆ | ★★★★☆ | 高 |
htop | 系统进程监控 | psutil + 表格输出 | ★★★★★ | ★★★★☆ | 中 |
fzf | 模糊搜索交互 | 无 | ★★★☆☆ | ★★★☆☆ | 中 |
awk | 结构化聚合 | pandas 等价加载 | ★★★★★ | ★★★★☆ | 高 |
sed -i | 文件批量编辑 | open()+写回+临时文件 | ★★★★★ | ★★★☆☆ | 高 |
✅ AI 友好度 指的是工具的输出格式是否易于 agent 解析。
jq和rg的输出最干净,agent 直接接受一行不需要再清洗。
Agent 系列日记 5:为什么 Skill 路由比 ReAct 控制器更适合个人助理
从 luck-agent 到 Hermes-lite,我花了两年时间才想清楚这个问题:80% 的请求不需要"思考",只需要"路由"。
回顾:ReAct 控制器模式的困境
2024 年做 luck-agent 的时候,我采用的是经典的 ReAct 模式(Reasoning + Acting):
用户输入 → 大模型推理(意图识别 → 规划 → 工具选择 → 执行)→ 输出
所有请求,不管简单还是复杂,都走同一个推理链。一个"查 QPC 里有没有 VPS 相关的记录"的请求,也要经过:
- 理解自然语言
- 判断意图类别
- 选择工具(search_qpc)
- 构造查询参数
- 执行并解释结果
问题很明显:
- Token 消耗大:每次推理要带上完整的 system prompt + 工具定义,光上下文就吃掉 3000-5000 token
- 延迟高:一个简单查询要等模型完成完整推理链,响应时间 3-8 秒
- 上下文窗口压力:随着工具数量增加(现在 Hermes-lite 有 20+ 个工具),system prompt 越来越大
我当时就在知识库里记了一条:
“对于个人助理场景,相比最初的 ReAct 控制器模式,Skill 技能路由更适合当前 luckagent:80% 请求是固定模式,不需要复杂推理,技能比 Agent 更稳定,成本更低,响应更快。”
Skill 路由的核心思想
一句话:预定义技能映射,轻量分类先路由,只加载需要的工具和 prompt。
用户输入 → 轻量路由(关键词/意图 → 技能)→ 加载专用 prompt + 工具 → 执行
路由表设计
# 简化版路由逻辑
ROUTING_RULES = {
"qpc": {
"keywords": ["QPC", "知识库", "记一下", "记录"],
"skill": "qpc",
"tools": ["feishu_bitable_read", "feishu_bitable_write"],
"prompt": "qpc_skill_prompt" # 只加载 QPC 相关的 prompt
},
"vps": {
"keywords": ["VPS", "服务器", "防火墙", "Docker"],
"skill": "gcp-vps-ops",
"tools": ["terminal", "read_file"],
"prompt": "vps_skill_prompt"
},
"blog": {
"keywords": ["博客", "文章", "Hugo", "发布"],
"skill": "hugo-blog",
"tools": ["terminal", "read_file", "write_file"],
"prompt": "blog_skill_prompt"
}
}
对比:同样"查 QPC"操作
| 指标 | ReAct 模式 | Skill 路由 |
|---|---|---|
| 上下文 token | ~4000(全量工具定义) | ~800(仅 QPC 相关) |
| 首次响应延迟 | 3-8s | 0.5-1.5s |
| 工具调用次数 | 2-3 次(推理+执行) | 1 次(直接执行) |
| 准确率 | 92%(模型可能选错工具) | 98%(路由确定性高) |
Hermes-lite 的 Skill 实现
Hermes-lite 的 skill 系统是这样工作的:
Agent 的记忆:Hermes-Lite 如何用 5 层结构解决「鱼的难题」
人类 chatbot 最尴尬的瞬间:第二次见面完全不认识你。Agent 的记忆系统怎么同时做到持久化、低成本、不丢上下文?我深入 Hermes-Lite 源码写了一个月,拆解出 5 层架构。
从「鱼的难题」开始
用过 ChatGPT 的人都知道一个痛:下次打开,它完全不记得你了。
对一次性聊天这没问题。但对个人助理——帮你管理 200 条知识库、追踪几个项目的进度、记住你的偏好——这等于每次都要把整段友情重来。
我做 QPC(个人知识库)时就被这个问题卡过。后来折腾 Hermes-Lite、nanobot,逐步摸到一条可行的路。这篇文章从 Hermes-Lite 的实际源码出发,拆解它的 5 层记忆架构,看看一个 Agent 是怎么解决「鱼的难题」的。
总览:5 层架构
┌─────────────────────────────────────────────┐
│ Layer 1: 持久记忆 (MEMORY.md / USER.md) │ ← 跨会话,纯文本
├─────────────────────────────────────────────�
│ Layer 2: 会话压缩 (ContextCompressor) │ ← 长对话不爆
├─────────────────────────────────────────────┤
│ Layer 3: 会话检索 (session_search + FTS5) │ ← 历史不丢
├─────────────────────────────────────────────�
│ Layer 4: 增量写入门控 (Nudge 机制) │ ← 该记就记
├─────────────────────────────────────────────�
│ Layer 5: 原子性保障 (fcntl + 临时文件) │ ← 不丢不坏
└─────────────────────────────────────────────┘
每一层解决一个独立问题,层与层之间不耦合。下面逐层拆解。
Agent 日记系列(一):AI 助手协作痛点与进化实践
当前 AI 助手在复杂场景下暴露出的种种局限,促使我们思考:如何让 Agent 从"工具调用者"进化为"自适应系统"?
痛点全景
1. 工具编排僵化
大部分 Agent 的工具列表在启动时就固定了。面对"下载一个 ZIP 并提取其中 CSV 做分析"这种需要多步骤组合的任务,它无法临时组合出解压→解析→汇总的新流水线,只能止步于"我有 run_shell 但没有 unzip 工具"。
2. 跨会话无记忆
每次对话结束,Agent 的"记忆"归零。上次犯的错、用户的偏好、环境配置——全部遗忘。下次对话从头来,效率极低。
3. 单 Agent 能力天花板
一个 Agent 同时负责信息收集、决策判断、代码执行、结果格式化——角色过载导致质量下降。缺少分工协作机制。
进化方向
从"固定工具"到"动态进化"
Agent 应该能在运行时识别能力缺口,自主编写并注册新工具。这不是插件系统,而是自我扩展。
从"无状态"到"持久记忆"
需要跨会话保留的三层记忆:
- 用户层:偏好、沟通风格、项目上下文
- 环境层:系统配置、工具路径、API 端点
- 经验层:踩过的坑、解决方案、最佳实践
从"单兵"到"协作"
主 Agent 负责决策,子 Agent 各司其职:搜索 Agent、编码 Agent、验证 Agent。通过明确的接口契约协作。
实践起点
本系列接下来的三篇日记,将分别深入主控制器调度、动态工具创建、持久化记忆系统三个方向,拆解具体实现方案。
进化不是加功能,是改范式。
Agent 日记系列(四):构建 Agent 的记忆宫殿:持久化存储系统解析
Agent 没有记忆,就是"金鱼脑"——每次对话从头开始,重复犯错、忘记偏好。持久化记忆系统要解决的是:跨会话保留什么、怎么存、怎么取。
记忆分层
第一层:身份记忆(User Profile)
永久不变或极少变的信息:
- 用户姓名、时区、语言偏好
- 项目路径、技术栈、常用工具
- 沟通风格偏好(简洁/详细、中文/英文)
存储方式:纯文本文件,每轮注入系统提示。
第二层:经验记忆(Lessons Learned)
踩过的坑和解决方案:
- “NewAPI Groq 70B 只有 8K 上下文,不适合做压缩”
- “Hugo 0.161.1 需要覆盖 baseof.html 修复 locale 问题”
- “Hermes-lite 的 memory 上限 2200 字,要 replace 而非 add”
存储方式:结构化条目,带时间戳和标签。
第三层:会话上下文(Session State)
当前对话的实时状态:
- 最近 10 轮对话
- 当前任务进度
- 待办事项
存储方式:对话历史 buffer,压缩后丢弃细节。
存储方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 纯文本文件 | 零依赖、人类可读 | 无查询能力、并发不安全 | 身份记忆、小型博客 |
| SQLite | 轻量、单文件、SQL | 需维护 schema | 中等规模 Agent |
| Turso/libSQL | 分布式、边缘部署 | 需网络、有成本 | 多实例 Agent |
| Redis | 极快、支持 TTL | 内存贵、无持久化 | 会话缓存 |
实际实现:文件 + 索引
对于单 VPS 个人 Agent,最实用的是文件 + 索引方案:
Agent 日记系列(三):揭秘 Agent 的自我进化:动态工具创建与管理
传统 Agent 的能力在诞生时就固定了——给什么工具就用什么工具。真正的自进化 Agent 应该能在运行时识别能力缺口,自主扩展工具集。
何时需要动态工具
典型场景:
- 批量文件重命名:没有 rename 工具,但 Agent 可以写一个 Python 脚本
- API 数据聚合:需要组合多个接口的结果,现成工具不支持
- 格式转换:CSV → JSON、Markdown → HTML,临时需要
- 测试辅助:需要一个 mock 服务或数据生成器
工具创建流程
识别缺口 → 编写代码 → 安全审查 → 注册到 Tool Registry → 可用
识别缺口
Agent 在以下情况触发工具创建:
- 工具调用连续失败(“没有这个工具”)
- 用户明确请求不存在的功能
- 任务需要多步组合但无现成路径
编写代码
Agent 根据需求生成工具脚本,关键约束:
- 输入输出必须有明确 schema
- 必须包含错误处理
- 不能访问超出工作目录的路径
安全审查
这是最关键的一步。Agent 生成的代码必须经过:
- 语法检查:确保可执行
- 沙箱试运行:用测试数据验证
- 权限边界:不读取敏感文件、不发起外部网络请求
- 用户确认(可选):高风险操作需人工批准
注册与生命周期
# 工具注册伪代码
tool_registry.register(
name="batch_rename",
description="批量重命名文件,支持正则匹配",
parameters={
"pattern": {"type": "string", "description": "正则匹配模式"},
"replacement": {"type": "string", "description": "替换字符串"},
"directory": {"type": "string", "description": "目标目录"}
},
handler="tools/batch_rename.py",
scope="session" # session / persistent
)
工具作用域:
Agent 日记系列(二):探秘 Agent 的大脑中枢:主控制器与生命周期
主控制器是 Agent 的"大脑"——它决定何时思考、何时行动、何时停止。理解主控制器的设计,就理解了 Agent 的运作范式。
核心循环(Core Loop)
主控制器的本质是一个状态机:
接收输入 → 理解意图 → 选择工具 → 执行调用 → 处理结果 → 判断是否结束 → 输出/继续
这个循环的关键设计点:
1. 意图识别
不是简单的关键词匹配,而是结合上下文、历史、用户状态的综合判断。同一个"搜索一下",在项目初期是搜技术方案,在部署阶段是搜报错日志。
2. 工具路由
主控制器维护一个工具注册表(Tool Registry),包含:
- 工具名 + 描述(供 LLM 选择)
- 参数 schema(JSON Schema)
- 权限等级(只读 / 可写 / 危险)
- 超时策略
路由逻辑:LLM 输出 tool_call → 控制器校验权限 → 注入上下文 → 执行 → 截断超长输出 → 返回结果。
3. 终止条件
这是最容易被忽略的环节。Agent 必须知道"够了":
- 显式终止:用户说"停"或任务完成标记
- 隐式终止:连续 3 次工具调用返回相同/空结果
- 预算终止:token 消耗超过阈值,强制总结当前进展
生命周期管理
会话级生命周期
创建 → 活跃 → 空闲 → 重置 → 销毁
- 创建:加载用户上下文、历史摘要、可用工具列表
- 活跃:正常处理请求,维护对话历史
- 空闲:N 分钟无请求后,压缩历史为摘要
- 重置:用户显式
/new,清空当前会话但保留记忆 - 销毁:服务重启或长时间无活动
任务级生命周期
一个复杂任务可能跨多轮:
从零到一:基于 Gemini 和 Claude 的智能体开发实战(集成 Vertex AI)
构建一个真正能用的 AI Agent,不是调 API 写段对话就完事。你需要的是:能接工具、能记忆、能扩展、能上线的完整系统。
技术选型
模型选择
| 模型 | 优势 | 适用场景 |
|---|---|---|
| Gemini 3.5 Flash | 1M 上下文、多模态、性价比高 | 长文档分析、多轮对话 |
| Claude 3.5 Sonnet | 代码能力强、指令遵循严格 | 代码生成、复杂推理 |
| Gemini + Claude 双模型 | 互补优势 | 主模型决策 + 辅助验证 |
部署平台
Vertex AI(推荐 GCP 用户):
- 统一接口管理多个模型
- VPC 内调用,安全可控
- ADC 认证,无需手动管理 key
Gemini Developer API(快速原型):
- 免费额度够日常用
- API Key 简单直接
- 适合单机开发阶段
核心架构
用户输入
↓
主控制器(LLM 决策)
├── 工具调用 → Shell / File / Web / API
├── 记忆读写 → 持久化存储
└── 子代理 → 并行任务
↓
输出生成
1. 工具层
基础工具集: