驱动 AI 干活

跟管理一个聪明但粗心的实习生很像——你得把需求说清楚,还得检查他的输出

Prompt Engineering 的迷思

网上到处都是"万能 Prompt 模板",好像写好 prompt 就能解决一切。我的经验是:prompt 只占 20%,剩下 80% 是工程问题。

真正的难点不在"怎么写 prompt",而在三个地方:

  1. 怎么拆解任务 — 用户说"帮我分析茅台",你要拆成:查行情→查财报→查行业→综合分析。拆错了,后面全白费。
  2. 怎么定义工具接口 — LLM 调工具的参数必须简单明确。参数太复杂,LLM 经常填错;参数太简单,工具能力受限。
  3. 怎么处理不确定性 — LLM 的输出不可靠。JSON 格式偶尔出错,工具参数偶尔填错,结论偶尔胡说。必须有校验和兜底。

坑一:LLM 的 JSON 输出不可靠

让 LLM 输出 JSON 格式的结构化数据,偶尔会多出注释、少个逗号、或者突然换成 Markdown 代码块。这个问题我踩了不下 10 次。

# 解决方案:Pydantic 严格校验 + 重试
from pydantic import BaseModel, Field
from tenacity import retry, stop_after_attempt

class IntentResult(BaseModel):
    mode: ExecutionMode
    tools: list[str] = Field(default_factory=list)
    confidence: float = Field(ge=0, le=1)

@retry(stop=stop_after_attempt(3))
async def parse_intent(query: str) -> IntentResult:
    raw = await llm_call(query)
    return IntentResult.model_validate_json(raw)
    # 不要信任任何 LLM 的格式输出

核心原则:永远不要信任 LLM 的格式输出。用 Pydantic 严格校验,校验失败就重试,重试 3 次还失败就降级到规则引擎。

坑二:工具接口设计

工具接口的设计比 prompt 难多了。我的经验是:

  • 参数不超过 5 个,每个参数都有明确的类型和描述
  • 返回值必须是结构化的 JSON,不要返回自然语言
  • 工具描述要写清楚"什么时候用"和"什么时候不用"
# 好的工具定义
tools = [{
    "name": "get_stock_data",
    "description": "获取股票行情数据。当用户问股价、涨跌、成交量时使用。",
    "parameters": {
        "type": "object",
        "properties": {
            "symbol": {
                "type": "string",
                "description": "股票代码,如 600519"
            },
            "period": {
                "type": "string",
                "enum": ["1d", "1w", "1m", "3m", "1y"],
                "description": "时间周期"
            }
        },
        "required": ["symbol"]
    }
}]

坑三:ReAct 循环的空转问题

FinBuddy 里有个 ReAct 循环,核心原则是"没有真实工具数据就不允许输出分析结论"。但 LLM 有时候会绕过这个限制——假装调了工具,其实没调,直接编造结论。

# ReAct 强制数据优先
class ReactLoop:
    """第1次迭代必须调用数据获取工具"""

    async def run(self, query: str):
        for i in range(self.max_iterations):
            action = await self._think(query, self.history)
            if i == 0 and not action.is_tool_call:
                # 第1次迭代必须调工具,不允许直接出结论
                action = self._force_tool_call(query)
            result = await self._execute(action)
            self.history.append((action, result))
        return self._synthesize()

心态:把 AI 当实习生

驱动 AI 干活最好的心态是:把它当成一个聪明但粗心的实习生。它学东西很快,但执行时经常出错。你需要:

  1. 把需求说清楚 — 模糊的需求 = 离谱的输出
  2. 检查它的输出 — Pydantic 校验、规则引擎兜底、人工抽检
  3. 给它合适的工具 — 不要让它从零开始,给它 API 和数据源
  4. 允许它犯错 — 重试机制、降级策略、错误容忍

这四条比任何 prompt 模板都管用。有意思的是,这种"用规则替代意志力"的思路跟我在投资中的做法一模一样——知道认知偏差 ≠ 能避免它,得靠止损线和冷却期来约束自己。本质上都是用确定性对抗不确定性。