📝 模块4:Agent Prompt 工程适配(Claude 专属)

4.1 Claude Agent 专属 Prompt 设计逻辑

Claude 系列模型对结构化 Prompt 特别敏感。好的 Agent Prompt 需要具备以下四要素:

┌─────────────────────────────────────────────────────┐ │ Agent Prompt 四要素结构(Claude 专属) │ │ │ │ 🎭 角色定位 — "你是谁?你的身份和职责是什么?" │ │ ↓ │ │ 📏 任务约束 — "你可以做什么?绝对不能做什么?" │ │ ↓ │ │ 📤 输出规范 — "回答必须是什么格式?" │ │ ↓ │ │ 🧭 思维引导 — "遇到情况X时,应该如何思考/行动?" │ └─────────────────────────────────────────────────────┘

Claude vs 其他模型的 Prompt 差异

特性ClaudeGPT 系列开源模型
结构化指令遵循⭐⭐⭐⭐⭐ 极强⭐⭐⭐⭐ 强⭐⭐⭐ 中等
角色扮演稳定性⭐⭐⭐⭐⭐ 极强⭐⭐⭐⭐ 强⭐⭐ 较弱
工具调用格式偏好 JSON,容错好支持 Function Calling不稳定
对示例的依赖低(零样本强)高(需要 Few-shot)
Prompt 长度敏感适中适中长 Prompt 容易跑偏
💡 Claude Agent Prompt 黄金法则

Claude 最喜欢"说清楚 + 给自由"。即:把规则和约束说得很清楚,但在具体执行上给 Claude 自主发挥的空间。最忌讳的是"每一步都精确控制"(那样不如直接写脚本),和"什么都不说靠 AI 猜"(那样幻觉多)。

4.2 实战 Prompt 模板库

模板1:通用 Agent Prompt(开箱即用)

AGENT_PROMPT_BASE = """## 角色
你是 {agent_name},一个专业的 AI 智能助手。你的目标是帮助用户高效完成任务。

## 核心能力
- 理解用户的自然语言指令,提取关键信息
- 选择合适的工具来完成任务
- 在工具不可用时,诚实告知并尝试替代方案
- 记住对话中的重要信息(用户偏好、任务上下文)

## 行为规范
- 对于你确定的事情,自信而简洁地回答
- 对于你不确定的事情,明确说明"我不确定",不要猜测
- 当需要用户提供更多信息时,友好地追问,而非胡编
- 优先使用中文回复,除非用户要求其他语言

## 工具使用
{tools_description}

## 输出格式
- 普通回复:直接输出答案
- 工具调用:输出 JSON: {{"tool": "工具名", "args": {{"key": "value"}}}}

## 错误处理
- 工具失败时,分析原因并尝试1次修正
- 如果修正后仍失败,向用户说明情况并提供建议
"""

模板2:角色特化 Agent Prompt

ROLE_SPECIFIC_PROMPT = """## 🎭 角色:{role_name}

### 身份背景
{role_background}

### 专业领域
{expertise_domains}

### 说话风格
- 语气: {tone}(如专业严谨、轻松友好、学术正式)
- 用词: {vocabulary_level}(如通俗易懂、专业术语、混合)

### 核心原则
1. {principle_1}
2. {principle_2}
3. {principle_3}

### 当遇到以下情况时:
- {scenario_a}: {response_strategy_a}
- {scenario_b}: {response_strategy_b}
- 不确定的情况: 诚实说明并追问

### 禁止行为
- {forbidden_behavior_1}
- {forbidden_behavior_2}
"""

模板3:多步推理引导 Prompt

REASONING_PROMPT = """请在回答前先进行逐步推理,按以下格式:

<thinking>
步骤1: 分析用户意图...
步骤2: 确定需要的工具/信息...
步骤3: 制定执行策略...
步骤4: 考虑可能的异常情况...
</thinking>

然后给出最终回答。

注意:<thinking> 部分是对你思考过程的内部记录,
最终回答中不要包含这部分的内容。"""

4.3 减少幻觉、提升稳定性的 Prompt 技巧

技巧实现方式效果
引用约束 "只使用已提供的信息回答。如果没有相关信息,说'我无法确定',不要编造。" 大幅减少编造事实
不确定性表达 "对不确定的内容,使用'可能'、'据我了解'等限定词,并标注不确定程度。" 用户更容易分辨可信度
输出校验 "回答后,请自我检查:1) 所有数字是否正确 2) 所有'事实'是否有依据 3) 逻辑是否自洽" 自我纠错,减少低级错误
Few-shot 示例 在 Prompt 中给出2-3个正确格式的示例 输出格式更稳定
负向提示 "不要用'作为AI'这样的开头。不要说'我无法执行实际的操作',直接调用工具。" 减少模板化拒绝

4.4 综合实战:个人智能文档整理 Agent

整合本阶段四大能力(记忆+工具+规划+Prompt),开发一个个人智能文档整理 Agent —— DocOrganizer

需求分析

完整代码

#!/usr/bin/env python3
"""
DocOrganizer — 个人智能文档整理 Agent
========================================
整合:记忆机制 + 工具调用 + 任务规划 + Prompt工程

运行:python3 doc_organizer.py
"""

import json
import os
import shutil
from datetime import datetime
from pathlib import Path
from collections import Counter

from dotenv import load_dotenv
from anthropic import Anthropic

load_dotenv()

# ================================================================
# Prompt 模板(整合四大能力的系统提示词)
# ================================================================

DOC_ORGANIZER_PROMPT = """## 🎭 角色
你是 DocOrganizer,一个专业的个人文档整理助手。你帮助用户整理、分类、归档文件。

## 📏 核心能力与约束
- 分析文件内容和类型,给出分类建议
- 为文档生成简洁摘要
- 记住用户的整理习惯和偏好
- 仅处理支持的文件类型:.txt .md .py .json .csv .log

## 🔧 可用工具
1. list_files — 列出目录下的文件,参数: directory_path
2. read_file — 读取文件内容,参数: filepath
3. classify_document — 分类文档,参数: filename|content
4. summarize_document — 摘要文档,参数: filename|content
5. move_file — 移动文件到分类目录,参数: filepath|category
6. save_preference — 保存用户偏好,参数: key=value
7. generate_report — 生成整理报告,参数: directory_path

## 📤 输出规范
- 工具调用:严格的 JSON 格式: {"tool": "工具名", "args": {"参数名": "值"}}
- 普通回复:简洁明了,一次不要超过3句话
- 报告:Markdown 格式

## 🧭 思考引导
1. 先理解用户意图(单文件整理?批量整理?查看报告?)
2. 如需批量处理,先列计划再逐步执行
3. 每步执行后验证结果
4. 自动记录用户的整理偏好(如"代码文件归到 projects/ 下")
"""


class DocOrganizer:
    """文档整理 Agent"""

    def __init__(self, workspace: str = "./documents"):
        self.client = Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))
        self.model = "claude-sonnet-4-6"
        self.workspace = Path(workspace)
        self.workspace.mkdir(parents=True, exist_ok=True)

        # 记忆:用户偏好
        self.preferences_file = self.workspace / ".organizer_prefs.json"
        self.preferences = self._load_preferences()

        # 分类目录
        self.categories = ["notes", "code", "logs", "reports", "data", "archived"]
        for cat in self.categories:
            (self.workspace / cat).mkdir(exist_ok=True)

        # 操作日志
        self.operation_log: list[dict] = []

        print(f"📂 工作目录: {self.workspace.absolute()}")
        print(f"📁 分类目录: {', '.join(self.categories)}")

    def _load_preferences(self) -> dict:
        if self.preferences_file.exists():
            return json.loads(self.preferences_file.read_text("utf-8"))
        return {"rules": {}, "favorite_categories": [], "stats": {}}

    def _save_preferences(self):
        self.preferences_file.write_text(
            json.dumps(self.preferences, ensure_ascii=False, indent=2),
            encoding="utf-8"
        )

    # --- 工具:列出文件 ---
    def tool_list_files(self, directory: str = ".") -> str:
        target = self.workspace / directory if directory != "." else self.workspace
        if not target.exists():
            return f"❌ 目录不存在: {target}"
        files = []
        for f in target.iterdir():
            if f.is_file() and not f.name.startswith("."):
                size_kb = f.stat().st_size / 1024
                files.append(f"  📄 {f.name} ({size_kb:.1f}KB)")
        if not files:
            return "📂 目录为空(无可整理文件)"
        return "📂 文件列表:\n" + "\n".join(sorted(files))

    # --- 工具:分类文档 ---
    def tool_classify(self, filepath: str) -> str:
        """用简单规则 + Claude 辅助分类"""
        path = self.workspace / filepath
        if not path.exists():
            return f"❌ 文件不存在: {filepath}"

        ext = path.suffix.lower()
        # 先按扩展名快速分类
        ext_map = {".py": "code", ".js": "code", ".json": "data",
                   ".csv": "data", ".log": "logs", ".md": "notes", ".txt": "notes"}
        if ext in ext_map:
            category = ext_map[ext]
            self.preferences.setdefault("ext_rules", {})[ext] = category
            return f"🏷️ {path.name} → [自动] {category} (按扩展名 {ext})"

        # 扩展名未知,读取内容让 Claude 判断
        try:
            content = path.read_text("utf-8")[:500]
            response = self.client.messages.create(
                model=self.model,
                max_tokens=50,
                messages=[{"role": "user", "content":
                    f"将以下文件内容分类为: notes/code/logs/reports/data/archived。只输出类别名。\n\n{content}"}],
                temperature=0.1,
            )
            category = response.content[0].text.strip().lower()
            if category in self.categories:
                return f"🏷️ {path.name} → [AI判断] {category}"
        except Exception:
            pass
        return f"🏷️ {path.name} → [默认] archived"

    # --- 工具:生成摘要 ---
    def tool_summarize(self, filepath: str) -> str:
        path = self.workspace / filepath
        if not path.exists():
            return f"❌ 文件不存在: {filepath}"
        try:
            content = path.read_text("utf-8")[:3000]
            response = self.client.messages.create(
                model=self.model,
                max_tokens=150,
                messages=[{"role": "user", "content":
                    f"请用1-2句话总结以下文档的核心内容:\n\n{content}"}],
                temperature=0.2,
            )
            summary = response.content[0].text.strip()
            return f"📝 {path.name} 摘要: {summary}"
        except Exception as e:
            return f"❌ 摘要生成失败: {e}"

    # --- 工具:移动文件 ---
    def tool_move_file(self, filepath: str, category: str) -> str:
        if category not in self.categories:
            return f"❌ 无效分类: {category}。可选: {self.categories}"
        path = self.workspace / filepath
        if not path.exists():
            return f"❌ 文件不存在: {filepath}"
        dest_dir = self.workspace / category
        dest = dest_dir / path.name
        # 处理重名
        if dest.exists():
            dest = dest_dir / f"{path.stem}_{datetime.now().strftime('%H%M%S')}{path.suffix}"
        shutil.move(str(path), str(dest))
        self.operation_log.append({"action": "move", "file": filepath, "to": str(dest)})
        return f"✅ {path.name} → {category}/"

    # --- 工具:生成报告 ---
    def tool_generate_report(self, directory: str = ".") -> str:
        target = self.workspace / directory if directory != "." else self.workspace
        report_lines = [
            f"# 📊 文档整理报告",
            f"生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M')}",
            f"扫描目录: {target.absolute()}",
            f"",
        ]
        # 按分类统计
        category_counts = Counter()
        total_size = 0
        for cat in self.categories:
            cat_dir = self.workspace / cat
            if cat_dir.exists():
                files = list(cat_dir.iterdir())
                category_counts[cat] = len([f for f in files if not f.name.startswith(".")])
                total_size += sum(f.stat().st_size for f in files if f.is_file())

        report_lines.append("## 分类统计")
        for cat in self.categories:
            count = category_counts.get(cat, 0)
            bar = "█" * min(count, 20)
            report_lines.append(f"- **{cat}**: {count} 个文件 {bar}")
        report_lines.append(f"\n**总计**: {sum(category_counts.values())} 个文件, {total_size/1024:.1f}KB")

        report_lines.append("\n## 最近操作")
        for op in self.operation_log[-10:]:
            report_lines.append(f"- {op['action']}: {op.get('file', '')} → {op.get('to', '')}")

        if self.preferences.get("rules"):
            report_lines.append("\n## 用户偏好规则")
            for rule, value in self.preferences["rules"].items():
                report_lines.append(f"- {rule}: {value}")

        report_text = "\n".join(report_lines)
        report_path = self.workspace / f"report_{datetime.now().strftime('%Y%m%d')}.md"
        report_path.write_text(report_text, encoding="utf-8")
        return f"📊 报告已生成: {report_path.name}\n\n{report_text[:500]}"

    # --- 核心运行逻辑 ---
    def _call_claude(self, user_input: str, context: str = "") -> str:
        system = DOC_ORGANIZER_PROMPT + f"\n\n当前工作目录: {self.workspace.absolute()}"
        if context:
            system += f"\n\n上次操作结果: {context}"
        try:
            response = self.client.messages.create(
                model=self.model,
                max_tokens=1024,
                system=system,
                messages=[{"role": "user", "content": user_input}],
                temperature=0.2,
            )
            return response.content[0].text.strip()
        except Exception as e:
            return f'{{"tool": "error", "args": {{"message": "{str(e)}"}}}}'

    def _execute_tool(self, tool_name: str, args: dict) -> str:
        tools = {
            "list_files": lambda a: self.tool_list_files(a.get("directory", ".")),
            "read_file": lambda a: self.tool_read_file(a.get("filepath", "")),
            "classify_document": lambda a: self.tool_classify(a.get("filepath", "")),
            "summarize_document": lambda a: self.tool_summarize(a.get("filepath", "")),
            "move_file": lambda a: self.tool_move_file(a.get("filepath", ""), a.get("category", "archived")),
            "save_preference": lambda a: self._save_pref(a),
            "generate_report": lambda a: self.tool_generate_report(a.get("directory", ".")),
        }
        func = tools.get(tool_name)
        if func:
            return func(args)
        return f"❌ 未知工具: {tool_name}"

    def _save_pref(self, args: dict) -> str:
        for k, v in args.items():
            self.preferences["rules"][k] = v
        self._save_preferences()
        return f"✅ 偏好已保存: {args}"

    def tool_read_file(self, filepath: str) -> str:
        path = self.workspace / filepath
        if not path.exists():
            return f"❌ 文件不存在: {filepath}"
        try:
            return path.read_text("utf-8")[:2000]
        except Exception as e:
            return f"❌ {e}"

    def run(self, user_input: str):
        """运行一轮文档整理操作"""
        print(f"\n👤 用户: {user_input}")

        claude_reply = self._call_claude(user_input)

        # 尝试解析工具调用
        try:
            json_str = claude_reply
            for marker in ["```json", "```"]:
                if marker in claude_reply:
                    json_str = claude_reply.split(marker)[1].split("```")[0]
                    break
            parsed = json.loads(json_str.strip())
            if "tool" in parsed:
                print(f"🔧 调用: {parsed['tool']}({parsed.get('args', {})})")
                result = self._execute_tool(parsed["tool"], parsed.get("args", {}))
                print(f"🤖 DocOrganizer: {result}")
                # 多步任务的循环处理
                if "继续" in user_input.lower() or "批量" in user_input.lower():
                    print("  📋 批量任务可能需要多步处理...")
                return result
        except (json.JSONDecodeError, KeyError):
            pass

        print(f"🤖 DocOrganizer: {claude_reply}")
        return claude_reply


def main():
    print("=" * 60)
    print("📂 DocOrganizer — 个人智能文档整理 Agent")
    print("=" * 60)
    print("试试这些命令:")
    print("  • 列出当前目录所有文件")
    print("  • 整理所有文件(自动分类+归档)")
    print("  • 分析 test.py 并归类")
    print("  • 生成整理报告")
    print("  • 偏好: 把所有 .md 文件归到 notes")
    print("  • quit → 退出")
    print("=" * 60)

    if not os.getenv("ANTHROPIC_API_KEY"):
        print("❌ 请先配置 ANTHROPIC_API_KEY!")
        return

    # 创建示例文档
    workspace = Path("./documents")
    workspace.mkdir(exist_ok=True)
    (workspace / "学习笔记.md").write_text("# AI Agent 学习笔记\n\n1. Agent 有记忆、工具、规划能力\n2. Claude Code 是优秀的 Agent 开发平台\n", encoding="utf-8")
    (workspace / "app.py").write_text("# My App\n\ndef main():\n    print('Hello Agent')\n\nif __name__ == '__main__':\n    main()\n", encoding="utf-8")
    (workspace / "server.log").write_text("2024-01-15 10:00 INFO Server started\n2024-01-15 10:05 ERROR Connection refused\n", encoding="utf-8")
    print("📝 已创建示例文档用于学习测试")

    agent = DocOrganizer()

    while True:
        try:
            user_input = input("\n👤 你: ").strip()
            if not user_input:
                continue
            if user_input.lower() in ("quit", "exit", "q"):
                break
            agent.run(user_input)
        except KeyboardInterrupt:
            break

    print(f"\n📊 本次操作数: {len(agent.operation_log)}")
    print(f"👋 DocOrganizer 已退出。")


if __name__ == "__main__":
    main()

📝 第二阶段总结

能力核心技术实现效果
记忆机制ShortTermMemory + LongTermMemory + SessionMemory 三层结构Agent 能记住对话上下文和用户偏好,跨会话持久化
工具调用ToolRegistry 注册中心 + 自动发现 + 失败重试Agent 自主选择工具、自动纠错、多工具协同
任务规划TaskPlanner + 链式思考 + 迭代调整复杂任务自动拆分→分步执行→结果汇总
Prompt 工程四要素模板 + 推理引导 + 幻觉抑制Agent 输出稳定、格式规范、可预期

✏️ 课后练习

  1. 完善 DocOrganizer:增加"批量整理"功能——一次扫描整个目录,自动分类、摘要、生成报告。
  2. 偏好学习:让 Agent 在每次整理后自动学习用户的纠正(如用户把 Agent 分类的"archived"改为"code",下次自动记住)。
  3. Prompt 优化实验:分别用不同的 system prompt 运行同一个任务3次,对比结果质量。记录哪种 Prompt 结构效果最好。
  4. 综合复盘:回顾第二阶段的4个模块,画一张自己的"Agent 核心能力地图",标注每个能力的核心函数和关键逻辑。
← 任务规划 进入第三阶段 →