Agno 开发教程(十一):深入理解与实践 Skills(技能)

181 阅读29分钟

目录

  1. Skill 讲解

  2. Skill 使用

  3. 实践案例

  4. 最佳实践建议


一、Skill 讲解

1.1 什么是 Skill?

在 Agno 框架中,Skill(技能) 是一种为 AI Agent 提供结构化领域专业知识的机制。通过指令(Instructions)、脚本(Scripts)和参考文档(References),Skill 将特定领域的知识和能力打包成可重用的模块。

Agno Skills 基于 Anthropic 的 Agent Skills 规范,是一种标准化的技能定义方式,使 Agent 能够:

  • 逐步发现技能:在系统提示中看到技能摘要
  • 按需加载指令:当任务匹配时加载完整的技能指导
  • 访问参考文档:根据需要查阅详细的技术文档
  • 执行脚本:运行技能中包含的可执行代码

Skill 的核心组成:

my-skill/
├── SKILL.md           # 必需:带有 YAML frontmatter 的指令文件
├── scripts/           # 可选:可执行脚本
│   └── helper.py
└── references/        # 可选:参考文档
    └── guide.md

1.2 Skill 的作用和重要性

为什么 Skill 如此重要?

1. 按需提供领域专业知识(Domain Expertise on Demand)

传统方式需要在系统消息中填充所有可能的指令,这会导致:

  • 上下文窗口过载
  • Token 消耗过高
  • Agent 性能下降

使用 Skill 后,可以:

  • 将领域知识组织成专注的包
  • Agent 只加载所需内容
  • 节省 tokens 并最终降低成本

对比示例:

# ❌ 传统方式:在系统消息中硬编码所有指令
agent = Agent(
    model=OpenAIChat(id="gpt-4o"),
    instructions=[
        "When reviewing code, check for: 1) Style violations...",
        "When analyzing data, follow these steps...",
        "When writing documentation, use these templates...",
        # ... 数百行指令
    ]
)

# ✅ 使用 Skill:按需加载
agent = Agent(
    model=OpenAIChat(id="gpt-4o"),
    skills=Skills(
        loaders=[LocalSkills("/path/to/skills")]
    )
)
# Agent 只在需要时加载相关技能

2. 可重用的知识包(Reusable Knowledge Packages)

一次创建,跨多个 Agent 使用。例如:

  • 代码审查技能可以在调试 Agent、PR 审查 Agent 和代码生成 Agent 之间共享
  • 数据分析技能可以在报表生成、趋势预测等多个场景中复用

3. 渐进式发现(Progressive Discovery)

Skills 使用懒加载来保持上下文窗口高效:

┌─────────────────────────────────────────────────────────┐
│  1. Browse(浏览)                                       │
│     Agent 在系统提示中看到技能摘要                      │
│     例:code-review: 代码审查辅助,包括样式检查         │
├─────────────────────────────────────────────────────────┤
│  2. Load(加载)                                         │
│     当任务匹配时,Agent 调用 get_skill_instructions()   │
│     加载完整的代码审查指导                               │
├─────────────────────────────────────────────────────────┤
│  3. Reference(参考)                                    │
│     Agent 调用 get_skill_reference() 访问详细文档       │
│     例:加载 Python 样式指南                             │
├─────────────────────────────────────────────────────────┤
│  4. Execute(执行)                                      │
│     Agent 调用 get_skill_script() 运行脚本              │
│     例:执行代码风格检查脚本                             │
└─────────────────────────────────────────────────────────┘

1.3 Skill 的架构

技能目录结构

一个标准的 Skill 包含以下三个部分:

code-review/                    # 技能根目录
├── SKILL.md                    # 核心指令文件(必需)
├── scripts/                    # 脚本目录(可选)
│   ├── check_style.py         # Python 脚本
│   └── lint.sh                # Shell 脚本
└── references/                 # 参考文档目录(可选)
    ├── style-guide.md         # 样式指南
    └── best-practices.md      # 最佳实践

SKILL.md 文件结构

SKILL.md 是技能的核心,采用 YAML frontmatter + Markdown 内容 的格式:

---
name: code-review                              # 必需:技能名称
description: 代码审查辅助,包括样式检查和最佳实践  # 必需:简短描述
license: Apache-2.0                            # 可选:许可证
metadata:                                      # 可选:元数据
  version: "1.0.0"
  author: your-name
  tags: ["python", "code-quality"]
---

# Code Review Skill

在审查代码质量、样式和最佳实践时使用此技能。

## 何时使用
- 用户要求代码审查或反馈
- 用户想要提高代码质量
- 用户需要重构帮助

## 过程
1. **分析结构**:审查整体代码组织
2. **检查样式**:查找样式指南违规
3. **识别问题**:发现错误、安全问题、性能问题
4. **建议改进**:提供可操作的建议

## 最佳实践
- 首先关注最有影响力的问题
- 解释建议背后的"为什么"
- 为修复提供代码示例

字段说明

字段类型必需说明限制
namestring技能名称最多64字符,小写字母数字+连字符,与目录名匹配
descriptionstring技能描述最多1024字符,显示在Agent系统提示中
licensestring许可证SPDX 标识符(MIT、Apache-2.0等)
metadataobject自定义元数据可包含 version、author、tags 等

1.4 Skill 的工作原理

加载和发现机制

from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.skills import Skills, LocalSkills

# 1. 创建 Skills 协调器,指定加载器
skills = Skills(
    loaders=[
        LocalSkills("/path/to/shared-skills"),
        LocalSkills("/path/to/project-skills"),
    ]
)

# 2. Agent 初始化时自动注册技能工具
agent = Agent(
    model=OpenAIChat(id="gpt-4o"),
    skills=skills
)

加载过程:

  1. 扫描目录LocalSkills 扫描指定目录,查找包含 SKILL.md 的子目录
  2. 解析元数据:读取 YAML frontmatter,验证名称和描述
  3. 索引资源:记录 scripts 和 references 目录中的文件
  4. 注入系统提示:将技能名称和描述添加到 Agent 的系统消息中
  5. 注册工具:自动为 Agent 添加三个技能工具

技能工具

当向 Agent 添加技能时,它会自动获得以下工具:

工具功能参数
get_skill_instructions(skill_name)加载技能的完整指令(SKILL.md的内容)skill_name: 技能名称
get_skill_reference(skill_name, reference_path)加载参考文档skill_name: 技能名称
reference_path: 文档路径
get_skill_script(skill_name, script_path, execute, args, timeout)读取或执行脚本skill_name: 技能名称
script_path: 脚本路径
execute: 是否执行(布尔值)
args: 脚本参数(列表)
timeout: 超时时间(秒)

执行流程

用户请求
   ↓
Agent 分析任务
   ↓
在系统提示中浏览可用技能
   ↓
判断是否需要某个技能
   ↓
调用 get_skill_instructions() ──→ 加载完整指令
   ↓
按照指令处理任务
   ↓
(可选)调用 get_skill_reference() ──→ 查阅参考文档
   ↓
(可选)调用 get_skill_script() ──→ 执行脚本
   ↓
返回结果给用户

二、Skill 使用

2.1 创建自定义 Skill

步骤 1:创建目录结构

# 创建技能根目录
mkdir -p my-skills/code-review
cd my-skills/code-review

# 创建子目录
mkdir scripts references

步骤 2:编写 SKILL.md

创建 SKILL.md 文件:

---
name: code-review
description: 代码审查辅助,包括样式检查和最佳实践
license: Apache-2.0
metadata:
  version: "1.0.0"
  author: your-name
  tags: ["python", "code-quality"]
---

# Code Review Skill

在审查代码质量、样式和最佳实践时使用此技能。

## 何时使用

- 用户要求代码审查或反馈
- 用户想要提高代码质量
- 用户需要重构帮助

## 审查流程

### 1. 分析结构
- 检查代码组织和模块化
- 评估函数和类的职责分离
- 验证命名约定的一致性

### 2. 检查样式
- 遵循 PEP 8 样式指南
- 检查行长度(最多 100 字符)
- 验证导入顺序和分组

### 3. 识别问题
- **安全性**:SQL 注入、XSS 漏洞
- **性能**:不必要的循环、重复计算
- **错误处理**:缺失的异常处理
- **可维护性**:过于复杂的逻辑

### 4. 提供建议
- 解释问题的影响
- 提供具体的改进方案
- 附带代码示例

## 最佳实践

1. **优先级排序**:首先关注安全性和正确性问题
2. **建设性反馈**:解释"为什么"而不仅仅是"什么"
3. **代码示例**:为每个建议提供改进后的代码
4. **保持友好**:用积极的语气提出改进建议

步骤 3:添加脚本(可选)

创建 scripts/check_style.py

#!/usr/bin/env python3
"""检查代码样式并返回结果。"""
import sys
import json

def check_style(code: str) -> dict:
    """检查代码样式问题。
    
    Args:
        code: 要检查的 Python 代码
        
    Returns:
        包含问题列表的字典
    """
    issues = []
    lines = code.split('\n')
    
    for i, line in enumerate(lines, 1):
        # 检查行长度
        if len(line) > 100:
            issues.append({
                "line": i,
                "type": "line_length",
                "message": f"Line {i} exceeds 100 characters ({len(line)} chars)"
            })
        
        # 检查尾随空格
        if line.endswith(' ') and line.strip():
            issues.append({
                "line": i,
                "type": "trailing_whitespace",
                "message": f"Line {i} has trailing whitespace"
            })
        
        # 检查制表符
        if '\t' in line:
            issues.append({
                "line": i,
                "type": "tab_character",
                "message": f"Line {i} contains tab character (use spaces)"
            })
    
    return {
        "total_issues": len(issues),
        "issues": issues,
        "summary": f"Found {len(issues)} style issue(s)"
    }

if __name__ == "__main__":
    # 从 stdin 或参数读取代码
    if len(sys.argv) > 1:
        code = sys.argv[1]
    else:
        code = sys.stdin.read()
    
    result = check_style(code)
    print(json.dumps(result, indent=2))

确保脚本有执行权限:

chmod +x scripts/check_style.py

步骤 4:添加参考文档(可选)

创建 references/style-guide.md

# Python 样式指南

## 命名约定

### 变量和函数
- 使用 `snake_case`
- 使用描述性名称
- 避免单字母变量(除了循环计数器)

```python
# ✅ 好的命名
user_count = 10
def calculate_total_price(items):
    pass

# ❌ 不好的命名
uc = 10
def calc(x):
    pass
```

### 类
- 使用 PascalCase
- 名称应该是名词

```python
# ✅ 好的命名
class UserManager:
    pass

# ❌ 不好的命名
class user_manager:
    pass
```

### 常量
- 使用 `UPPER_SNAKE_CASE`

```python
# ✅ 好的命名
MAX_CONNECTIONS = 100
DEFAULT_TIMEOUT = 30

# ❌ 不好的命名
maxConnections = 100
```

## 代码布局

### 行长度
- 每行最多 100 个字符
- 在逻辑点处断开长行

### 导入
```python
# 标准库导入
import os
import sys

# 第三方导入
import numpy as np
import pandas as pd

# 本地导入
from myapp.models import User
from myapp.utils import helper
```

### 空行
- 顶层函数和类之间用 2 个空行
- 类方法之间用 1 个空行

## 注释

### 文档字符串
```python
def calculate_discount(price: float, discount_rate: float) -> float:
    """计算折扣后的价格。
    
    Args:
        price: 原价
        discount_rate: 折扣率(0-1之间)
        
    Returns:
        折扣后的价格
        
    Raises:
        ValueError: 如果 discount_rate 不在有效范围内
    """
    if not 0 <= discount_rate <= 1:
        raise ValueError("Discount rate must be between 0 and 1")
    return price * (1 - discount_rate)
```

### 内联注释
- 使用注释解释"为什么"而不是"什么"
- 与代码保持一个空格

```python
# ✅ 好的注释
# 使用缓存避免重复的数据库查询
cached_user = cache.get(user_id)

# ❌ 不好的注释
# 从缓存获取用户
cached_user = cache.get(user_id)

验证规则

创建技能时,确保遵循以下规则:

名称要求:

  • code-review
  • git-workflow
  • data-analysis-v2
  • Code-Review(不能有大写)
  • -code-review(不能以连字符开头)
  • code--review(不能有连续连字符)
  • my_skill(不能使用下划线)

字段限制:

  • name:最多 64 个字符
  • description:最多 1024 个字符

目录名称匹配: 技能目录名必须与 SKILL.md 中的 name 字段完全一致。

2.2 加载和使用 Skill

基本用法

from agno.agent import Agent
from agno.models.deepseek import DeepSeek
from agno.skills import Skills, LocalSkills
from dotenv import load_dotenv

load_dotenv()
# 从目录加载 skills
agent = Agent(
    model=DeepSeek(),
    skills=Skills(
        loaders=[LocalSkills("path/skills/code-review")]
    ),
    instructions=[
        "你可以获得专业技能。",
        "在需要时使用get_skill_instructions来加载完整的指导",
    ],
)

# Agent 将在相关时自动使用技能
agent.print_response(
    "查看以下代码以获得最佳实践: def foo(): pass"
)

从技能目录加载

如果在子目录中有多个技能:

skills/
├── code-review/
│   └── SKILL.md
├── git-workflow/
│   └── SKILL.md
└── testing/
    └── SKILL.md
from agno.skills import Skills, LocalSkills

# 从目录加载所有技能
skills = Skills(
    loaders=[LocalSkills("/path/to/skills")]
)

加载单个技能

如果只想加载一个技能:

from agno.skills import Skills, LocalSkills

# 加载单个技能目录
skills = Skills(
    loaders=[LocalSkills("/path/to/skills/code-review")]
)

多个加载器

可以组合多个加载器从不同位置加载技能:

from agno.skills import Skills, LocalSkills

skills = Skills(
    loaders=[
        LocalSkills("/path/to/shared-skills"),      # 共享技能
        LocalSkills("/path/to/project-skills"),     # 项目特定技能
    ]
)

注意:如果来自不同加载器的技能具有相同名称,后面加载器的技能将覆盖前面的技能。

重新加载技能

如果技能在运行时发生变化,可以重新加载:

from agno.skills import Skills, LocalSkills

skills = Skills(
    loaders=[LocalSkills("/path/to/skills")]
)

# ... 技能在磁盘上被修改 ...

# 重新加载以获取更改
skills.reload()

错误处理

技能在加载时会被验证。如果验证失败,会引发 SkillValidationError

from agno.skills import Skills, LocalSkills, SkillValidationError

try:
    skills = Skills(
        loaders=[LocalSkills("/path/to/skills")]
    )
except SkillValidationError as e:
    print(f"Skill validation failed: {e}")
    print(f"Errors: {e.errors}")

2.3 内置 Skill(Claude Agent Skills)

Agno 集成了 Claude Agent Skills,这是 Anthropic 提供的官方技能,允许 Claude Agent 访问文件系统资源并创建各类文档。

可用的 Claude Agent Skills

Skill ID描述功能
pptxPowerPoint 技能创建专业演示文稿,包括幻灯片、布局和格式
xlsxExcel 技能生成带有公式、图表和数据分析功能的电子表格
docxWord 技能创建和编辑格式丰富的文档
pdfPDF 技能分析和提取 PDF 文档中的信息

启用 Claude Agent Skills

from agno.agent import Agent
from agno.models.anthropic import Claude

# 启用单个技能
agent = Agent(
    model=Claude(
        id="claude-sonnet-4-5-20250929",
        skills=[
            {"type": "anthropic", "skill_id": "pptx", "version": "latest"}
        ]
    ),
    instructions=["你是一个演讲专家。"],
    markdown=True
)

agent.print_response("制作一份关于人工智能趋势的3张幻灯片")

同时启用多个技能

from agno.models.anthropic import Claude

model = Claude(
    id="claude-sonnet-4-5-20250929",
    skills=[
        {"type": "anthropic", "skill_id": "pptx", "version": "latest"},
        {"type": "anthropic", "skill_id": "xlsx", "version": "latest"},
        {"type": "anthropic", "skill_id": "docx", "version": "latest"},
    ]
)

文件下载

由 Claude Skills 创建的文件存储在沙盒环境中,需要使用 Anthropic Files API 进行下载。

创建 file_download_helper.py

"""辅助脚本:下载 Claude Agent Skills 生成的文件。"""
import os
from pathlib import Path
from typing import List
from anthropic import Anthropic

def download_skill_files(
    provider_data: dict,
    client: Anthropic,
    output_dir: str = "downloads",
    default_filename: str = "output"
) -> List[str]:
    """下载技能生成的文件。
    
    Args:
        provider_data: Agent 响应中的 provider_data
        client: Anthropic 客户端实例
        output_dir: 输出目录
        default_filename: 默认文件名
        
    Returns:
        下载的文件路径列表
    """
    # 创建输出目录
    Path(output_dir).mkdir(parents=True, exist_ok=True)
    
    downloaded_files = []
    
    # 从 provider_data 提取文件信息
    if "skill_results" in provider_data:
        for result in provider_data["skill_results"]:
            if "files" in result:
                for file_info in result["files"]:
                    file_id = file_info.get("file_id")
                    filename = file_info.get("filename", default_filename)
                    
                    if file_id:
                        # 使用 Anthropic API 下载文件
                        file_content = client.files.content(file_id)
                        
                        # 保存到本地
                        output_path = os.path.join(output_dir, filename)
                        with open(output_path, "wb") as f:
                            f.write(file_content.read())
                        
                        downloaded_files.append(output_path)
    
    return downloaded_files

使用示例:

import os
from agno.agent import Agent
from agno.models.anthropic import Claude
from anthropic import Anthropic
from file_download_helper import download_skill_files

# 创建 Agent
agent = Agent(
    model=Claude(
        id="claude-sonnet-4-5-20250929",
        skills=[{"type": "anthropic", "skill_id": "pptx", "version": "latest"}]
    ),
    markdown=True
)

# 生成演示文稿
response = agent.run("Create a presentation about Python best practices")

# 下载生成的文件
client = Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))
if response.messages:
    for msg in response.messages:
        if hasattr(msg, "provider_data") and msg.provider_data:
            files = download_skill_files(
                msg.provider_data, 
                client, 
                default_filename="python_best_practices.pptx"
            )
            if files:
                print(f"Downloaded {len(files)} file(s):")
                for file in files:
                    print(f"  - {file}")

2.4 Skill 参数配置

Skills 类参数

from agno.skills import Skills, LocalSkills

skills = Skills(
    loaders=[
        LocalSkills(path="/path/to/skills"),
        # 可以添加多个加载器
    ],
    # 未来可能支持的配置选项
    # cache_enabled=True,         # 是否缓存技能内容
    # reload_on_change=False,     # 是否监控文件变化自动重载
)

Agent 与 Skills 集成

from agno.agent import Agent
from agno.models.deepseek import DeepSeek
from agno.skills import Skills, LocalSkills
from dotenv import load_dotenv

load_dotenv()
# 获取相对于此文件的技能目录
agent = Agent(
    name="Code Assistant",
    model=DeepSeek(),
    skills=Skills(
        loaders=[LocalSkills("path/skills/code-review")]
    ),
    instructions=[
        "你是一个有用的编码助手,拥有专门的技能。",
        "当用户要求代码审查时,加载代码审查技能。",
        "总是在你的建议中提供清晰的解释。",
    ],
    read_tool_call_history=True,          # 显示工具调用(调试用)
    markdown=True,                  # 启用 Markdown 格式输出
)

# 使用 Agent
if __name__ == "__main__":
    agent.print_response(
        "审查这个Python函数:\n\n"
        "def calc(x,y): return x+y"
    )

三、实践案例

案例一:代码审查技能

场景:构建一个代码审查助手,能够自动检查代码质量、样式问题并提供改进建议。

步骤 1:创建技能结构

mkdir -p skills/code-review/scripts
mkdir -p skills/code-review/references

步骤 2:编写 SKILL.md

skills/code-review/SKILL.md

---
name: code-review
description: 全面的代码审查助手,检查样式、安全性、性能和最佳实践
license: MIT
metadata:
  version: "1.0.0"
  author: agno-tutorial
  tags: ["python", "code-quality", "linting"]
---

# Code Review Skill

专业的代码审查助手,提供全面的代码质量分析。

## 何时使用

- 用户提交代码需要审查
- 用户询问代码改进建议
- 需要检查代码是否符合最佳实践

## 审查维度

### 1. 代码风格(Style)
- PEP 8 合规性检查
- 命名约定一致性
- 代码格式和缩进

### 2. 安全性(Security)
- SQL 注入风险
- XSS 漏洞
- 敏感信息泄露

### 3. 性能(Performance)
- 算法复杂度
- 不必要的重复计算
- 内存使用优化

### 4. 可维护性(Maintainability)
- 函数长度和复杂度
- 代码重复(DRY 原则)
- 注释和文档

## 审查流程

1. **快速扫描**:识别明显问题
2. **使用脚本**:运行 `check_style.py` 进行自动检查
3. **深入分析**:手动审查逻辑和架构
4. **生成报告**:按优先级列出问题和建议

## 输出格式

```markdown
## 代码审查报告

### ⚠️ 关键问题(Critical)
- [问题描述]
  - 位置:第 X 
  - 建议:[具体改进方案]
  - 示例:[改进后的代码]

### ⚡ 性能问题(Performance)
- ...

### 📝 样式建议(Style)
- ...

### ✨ 最佳实践(Best Practice)
- ...

相关资源

  • 参考:style-guide.md - Python 样式指南
  • 脚本:check_style.py - 自动样式检查

步骤 3:创建检查脚本

skills/code-review/scripts/check_style.py

#!/usr/bin/env python3
"""
代码样式检查脚本
检查常见的 Python 代码样式问题
"""
import sys
import json
import re
from typing import List, Dict

def check_style(code: str) -> Dict:
    """执行代码样式检查。
    
    Args:
        code: 要检查的 Python 代码
        
    Returns:
        包含问题分类的检查结果
    """
    issues = {
        "critical": [],
        "style": [],
        "best_practice": []
    }
    
    lines = code.split('\n')
    
    for i, line in enumerate(lines, 1):
        # 检查行长度
        if len(line) > 100:
            issues["style"].append({
                "line": i,
                "message": f"Line exceeds 100 characters ({len(line)} chars)",
                "suggestion": "Break long lines at logical points"
            })
        
        # 检查尾随空格
        if line.endswith(' ') and line.strip():
            issues["style"].append({
                "line": i,
                "message": "Trailing whitespace",
                "suggestion": "Remove trailing spaces"
            })
        
        # 检查制表符
        if '\t' in line:
            issues["style"].append({
                "line": i,
                "message": "Tab character found",
                "suggestion": "Use 4 spaces instead of tabs"
            })
        
        # 检查 print 语句(可能是调试代码)
        if re.search(r'\bprint\s*(', line):
            issues["best_practice"].append({
                "line": i,
                "message": "print() statement found",
                "suggestion": "Consider using logging instead of print for production code"
            })
        
        # 检查裸 except
        if re.search(r'except\s*:', line):
            issues["critical"].append({
                "line": i,
                "message": "Bare except clause",
                "suggestion": "Specify exception types: except ValueError:"
            })
        
        # 检查 TODO 注释
        if 'TODO' in line or 'FIXME' in line:
            issues["best_practice"].append({
                "line": i,
                "message": "TODO/FIXME comment found",
                "suggestion": "Address pending tasks before code review"
            })
    
    # 统计信息
    total = sum(len(v) for v in issues.values())
    
    return {
        "total_issues": total,
        "issues": issues,
        "summary": {
            "critical": len(issues["critical"]),
            "style": len(issues["style"]),
            "best_practice": len(issues["best_practice"])
        }
    }

def main():
    # 从命令行参数或 stdin 读取代码
    if len(sys.argv) > 1:
        code = sys.argv[1]
    else:
        code = sys.stdin.read()
    
    # 执行检查
    result = check_style(code)
    
    # 输出 JSON 格式结果
    print(json.dumps(result, indent=2, ensure_ascii=False))

if __name__ == "__main__":
    main()
chmod +x skills/code-review/scripts/check_style.py

步骤 4:创建参考文档

skills/code-review/references/style-guide.md

# Python 代码审查样式指南

## 命名约定

### 变量和函数:snake_case
```python
# ✅ 正确
user_count = 10
def calculate_total():
    pass

# ❌ 错误
userCount = 10
def CalculateTotal():
    pass
```

### 类:PascalCase
```python
# ✅ 正确
class UserManager:
    pass

# ❌ 错误
class user_manager:
    pass
```

### 常量:UPPER_SNAKE_CASE
```python
# ✅ 正确
MAX_SIZE = 100

# ❌ 错误
maxSize = 100
```

## 代码布局

### 行长度
- 最大 100 字符
- 在逻辑断点处换行

### 空行
- 顶层定义之间:2 个空行
- 方法之间:1 个空行

### 导入顺序
```python
# 1. 标准库
import os
import sys

# 2. 第三方库
import numpy as np

# 3. 本地模块
from myapp import models
```

## 最佳实践

### 异常处理
```python
# ✅ 正确:指定异常类型
try:
    value = int(user_input)
except ValueError:
    print("Invalid input")

# ❌ 错误:裸 except
try:
    value = int(user_input)
except:
    print("Error")
```

### 使用 logging 而非 print
```python
# ✅ 正确
import logging
logging.info("Processing started")

# ❌ 错误(生产代码中)
print("Processing started")
```

### 文档字符串
```python
def calculate_discount(price: float, rate: float) -> float:
    """计算折扣后的价格。
    
    Args:
        price: 原始价格
        rate: 折扣率(0-1)
        
    Returns:
        折扣后的价格
        
    Raises:
        ValueError: 如果 rate 不在有效范围内
    """
    if not 0 <= rate <= 1:
        raise ValueError("Rate must be between 0 and 1")
    return price * (1 - rate)
```

步骤 5:创建 Agent

code_review_agent.py

"""代码审查助手 Agent"""
from pathlib import Path
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.skills import Skills, LocalSkills

# 技能目录
skills_dir = Path(__file__).parent / "skills"

# 创建代码审查 Agent
code_reviewer = Agent(
    name="Code Reviewer",
    model=OpenAIChat(id="gpt-4o"),
    skills=Skills(
        loaders=[LocalSkills(str(skills_dir))]
    ),
    instructions=[
        "You are an expert code reviewer with access to the code-review skill.",
        "When reviewing code:",
        "1. First, use get_skill_script to run check_style.py for automated checks",
        "2. Then, use get_skill_instructions to load the full review guidelines",
        "3. Perform manual analysis for logic, security, and performance issues",
        "4. Refer to style-guide.md when needed",
        "5. Present findings in a clear, prioritized format",
        "Always be constructive and provide specific, actionable suggestions."
    ],
    show_tool_calls=True,
    markdown=True,
)

def main():
    print("=== Code Review Assistant ===\n")
    
    # 示例代码
    sample_code = '''
def calc(x,y):
    print("Calculating...")
    try:
        result=x/y
    except:
        print("Error")
        return None
    return result

userInput = input("Enter number: ")
answer = calc(10, int(userInput))
print(answer)
'''
    
    print("📝 Code to review:")
    print(sample_code)
    print("\n" + "="*50 + "\n")
    
    # 执行代码审查
    code_reviewer.print_response(
        f"Please review the following Python code and provide detailed feedback:\n\n```python\n{sample_code}\n```",
        stream=True
    )

if __name__ == "__main__":
    main()

步骤 6:运行和测试

# 安装依赖
pip install agno openai

# 设置 API 密钥
export OPENAI_API_KEY="your-api-key"

# 运行
python code_review_agent.py

预期输出:

=== Code Review Assistant ===

📝 Code to review:
def calc(x,y):
    print("Calculating...")
    ...

==================================================

🔧 Running tool: get_skill_script
  - skill_name: code-review
  - script_path: scripts/check_style.py
  - execute: True

📊 Automated Check Results:
{
  "total_issues": 8,
  "issues": {
    "critical": [
      {
        "line": 5,
        "message": "Bare except clause",
        "suggestion": "Specify exception types: except ZeroDivisionError:"
      }
    ],
    "style": [...],
    "best_practice": [...]
  }
}

## 代码审查报告

### ⚠️ 关键问题(Critical)

1. **裸 except 子句**(第 5 行)
   - 问题:捕获所有异常会隐藏潜在错误
   - 建议:指定具体的异常类型
   ```python
   try:
       result = x / y
   except ZeroDivisionError:
       print("Cannot divide by zero")
       return None
```

### 📝 样式问题(Style)

1. **函数定义缺少空格**(第 1 行)
   - 当前:`def calc(x,y):`
   - 建议:`def calc(x, y):`
   
2. **变量命名不符合规范**(第 10 行)
   - 当前:`userInput`
   - 建议:`user_input`(使用 snake_case)

### ⚡ 性能问题(Performance)

无明显性能问题。

### ✨ 最佳实践建议

1. **使用 logging 替代 print**
   ```python
   import logging
   logging.info("Calculating...")
   ```

2. **添加类型提示**
   ```python
   def calc(x: float, y: float) -> float | None:
       ...
   ```

3. **添加文档字符串**
   ```python
   def calc(x: float, y: float) -> float | None:
       """计算 x 除以 y 的结果。
       
       Args:
           x: 被除数
           y: 除数
           
       Returns:
           计算结果,如果除法失败则返回 None
       """
   ```

### 📋 改进后的完整代码

```python
import logging

def calculate_division(dividend: float, divisor: float) -> float | None:
    """计算两个数的除法。
    
    Args:
        dividend: 被除数
        divisor: 除数
        
    Returns:
        计算结果,如果除数为零则返回 None
    """
    logging.info("Performing division calculation")
    
    try:
        result = dividend / divisor
    except ZeroDivisionError:
        logging.error("Cannot divide by zero")
        return None
    
    return result

# 主程序
if __name__ == "__main__":
    user_input = input("Enter a number: ")
    try:
        divisor = float(user_input)
        answer = calculate_division(10, divisor)
        if answer is not None:
            print(f"Result: {answer}")
    except ValueError:
        print("Invalid input: please enter a valid number")

案例二:智能文档助手

场景:构建一个文档助手,能够理解 Agno 框架文档并回答用户问题。

步骤 1:创建技能

skills/agno-docs/SKILL.md

---
name: agno-docs
description: Agno 框架文档专家,提供关于 Agent、Skills、Tools 的帮助
license: MIT
metadata:
  version: "1.0.0"
  tags: ["documentation", "agno", "help"]
---

# Agno Documentation Skill

提供 Agno 框架的专业文档支持和技术指导。

## 何时使用

- 用户询问 Agno 框架的使用方法
- 需要查找特定 API 或功能的文档
- 用户需要代码示例或最佳实践

## 知识范围

### Agent
- Agent 的创建和配置
- 模型选择和参数设置
- 指令编写技巧

### Skills
- Skills 的定义和结构
- 加载和使用 Skills
- 自定义 Skills 开发

### Tools
- 内置工具使用
- 自定义工具开发
- 工具组合和编排

## 回答原则

1. **准确性**:基于官方文档提供准确信息
2. **完整性**:提供完整的代码示例
3. **实用性**:关注实际应用场景
4. **渐进性**:从简单到复杂逐步讲解

步骤 2:添加参考文档

skills/agno-docs/references/agent-guide.md

# Agent 开发指南

## 创建基础 Agent

```python
from agno.agent import Agent
from agno.models.openai import OpenAIChat

agent = Agent(
    name="My Agent",
    model=OpenAIChat(id="gpt-4o"),
    instructions=["You are a helpful assistant."],
    markdown=True
)

agent.print_response("Hello!")

Agent 配置参数

核心参数

参数类型说明
namestrAgent 名称
modelModel使用的 LLM 模型
instructionsList[str]指令列表
toolsList[Tool]可用工具列表
skillsSkills技能配置

记忆和存储

from agno.storage.agent.sqlite import SqliteAgentStorage

agent = Agent(
    model=OpenAIChat(id="gpt-4o"),
    storage=SqliteAgentStorage(
        table_name="sessions",
        db_file="agent_storage.db"
    ),
    add_history_to_messages=True,
    num_history_responses=3,
    session_id="user_123"
)

知识库集成

from agno.knowledge.pdf_url import PDFUrlKnowledgeBase
from agno.vectordb.lancedb import LanceDb

knowledge = PDFUrlKnowledgeBase(
    urls=["https://example.com/docs.pdf"],
    vector_db=LanceDb(
        table_name="docs",
        uri="tmp/lancedb"
    )
)

agent = Agent(
    model=OpenAIChat(id="gpt-4o"),
    knowledge=knowledge
)

步骤 3:创建文档助手 Agent

docs_assistant.py

"""Agno 文档助手"""
from pathlib import Path
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.skills import Skills, LocalSkills
from agno.tools.duckduckgo import DuckDuckGoTools
from agno.storage.agent.sqlite import SqliteAgentStorage

# 技能目录
skills_dir = Path(__file__).parent / "skills"

# 创建文档助手
docs_assistant = Agent(
    name="Agno Docs Assistant",
    model=OpenAIChat(id="gpt-4o"),
    
    # 加载文档技能
    skills=Skills(
        loaders=[LocalSkills(str(skills_dir / "agno-docs"))]
    ),
    
    # 添加网页搜索能力(用于查找最新信息)
    tools=[DuckDuckGoTools()],
    
    # 配置记忆
    storage=SqliteAgentStorage(
        table_name="docs_sessions",
        db_file="tmp/docs_assistant.db"
    ),
    add_history_to_messages=True,
    num_history_responses=5,
    
    instructions=[
        "You are an expert on the Agno framework.",
        "Use the agno-docs skill to provide accurate documentation.",
        "When answering questions:",
        "1. Load the relevant reference document using get_skill_reference",
        "2. Provide complete, runnable code examples",
        "3. Explain concepts clearly with step-by-step instructions",
        "4. If information is not in the docs, use DuckDuckGo to search",
        "Always cite your sources and indicate when using external information."
    ],
    
    show_tool_calls=True,
    markdown=True,
)

def interactive_mode():
    """交互式问答模式"""
    print("=== Agno Documentation Assistant ===")
    print("Ask me anything about the Agno framework!")
    print("Type 'exit' to quit.\n")
    
    session_id = "docs_session_001"
    docs_assistant.session_id = session_id
    
    while True:
        try:
            question = input("\n🤔 You: ").strip()
            
            if question.lower() in ['exit', 'quit', 'bye']:
                print("\n👋 Goodbye!")
                break
            
            if not question:
                continue
            
            print(f"\n🤖 Assistant:")
            docs_assistant.print_response(question, stream=True)
            
        except KeyboardInterrupt:
            print("\n\n👋 Goodbye!")
            break
        except Exception as e:
            print(f"\n❌ Error: {e}")

def main():
    # 示例问题
    examples = [
        "How do I create an Agent with memory?",
        "What is the difference between Skills and Tools?",
        "Show me how to integrate a knowledge base with an Agent"
    ]
    
    print("=== Agno Documentation Assistant ===\n")
    print("Example questions:")
    for i, q in enumerate(examples, 1):
        print(f"{i}. {q}")
    
    print("\n" + "="*60 + "\n")
    
    # 回答第一个问题
    print("📚 Answering: How do I create an Agent with memory?\n")
    docs_assistant.print_response(
        "How do I create an Agent with memory? Show me a complete example.",
        stream=True
    )
    
    print("\n" + "="*60 + "\n")
    
    # 启动交互模式
    choice = input("Start interactive mode? (y/n): ").strip().lower()
    if choice == 'y':
        interactive_mode()

if __name__ == "__main__":
    main()

步骤 4:运行和测试

python docs_assistant.py

示例对话:

=== Agno Documentation Assistant ===

📚 Answering: How do I create an Agent with memory?

🔧 Running tool: get_skill_reference
  - skill_name: agno-docs
  - reference_path: references/agent-guide.md

🤖 Assistant:

要创建具有记忆功能的 Agent,您需要配置存储后端和会话管理。以下是完整示例:

## 方法一:使用 SQLite 存储

```python
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.storage.agent.sqlite import SqliteAgentStorage

agent = Agent(
    name="Memory Agent",
    model=OpenAIChat(id="gpt-4o"),
    
    # 配置 SQLite 存储
    storage=SqliteAgentStorage(
        table_name="agent_sessions",      # 表名
        db_file="tmp/agent_storage.db"    # 数据库文件路径
    ),
    
    # 启用历史消息
    add_history_to_messages=True,         # 将历史添加到上下文
    num_history_responses=3,              # 保留最近 3 轮对话
    
    # 设置会话 ID
    session_id="user_session_001",        # 唯一会话标识符
    
    markdown=True
)

# 第一轮对话
agent.print_response("I love Python programming.")

# 第二轮对话(Agent 会记住之前的内容)
agent.print_response("What did I just say I love?")
# Agent 回复:You mentioned that you love Python programming.

方法二:使用 PostgreSQL 存储

from agno.storage.agent.postgres import PostgresAgentStorage

agent = Agent(
    model=OpenAIChat(id="gpt-4o"),
    storage=PostgresAgentStorage(
        table_name="agent_sessions",
        db_url="postgresql://user:password@localhost:5432/dbname"
    ),
    add_history_to_messages=True,
    session_id="user_session_001"
)

关键参数说明

  • storage: 存储后端,用于持久化对话历史
  • add_history_to_messages: 是否将历史消息添加到上下文
  • num_history_responses: 保留多少轮历史对话(控制上下文长度)
  • session_id: 会话唯一标识符,用于区分不同用户或对话

最佳实践

  1. 为每个用户使用唯一的 session_id

    session_id = f"user_{user_id}_{datetime.now().isoformat()}"
    
  2. 控制历史消息数量以避免上下文过载

    num_history_responses=5  # 通常 3-10 轮比较合适
    
  3. 定期清理旧会话数据

    # 删除 30 天前的会话
    storage.delete_old_sessions(days=30)
    

这样,您的 Agent 就具备了跨对话的记忆能力!


Skills vs Tools

特性SkillsTools
用途提供领域专业知识和指导执行具体的操作
内容指令、参考文档、脚本可调用的函数
加载按需加载(懒加载)总是可用
例子代码审查指南、写作风格网页搜索、数据库查询

组合使用示例

from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.skills import Skills, LocalSkills
from agno.tools.duckduckgo import DuckDuckGoTools
from agno.tools.file import FileTools

agent = Agent(
    model=OpenAIChat(id="gpt-4o"),
    
    # 添加 Skills(知识和指导)
    skills=Skills(
        loaders=[
            LocalSkills("skills/code-review"),
            LocalSkills("skills/documentation")
        ]
    ),
    
    # 添加 Tools(操作能力)
    tools=[
        DuckDuckGoTools(),      # 网页搜索
        FileTools(),            # 文件操作
    ],
    
    instructions=[
        "Use skills for guidance on how to approach tasks.",
        "Use tools to perform actual operations.",
        "Combine both for comprehensive solutions."
    ]
)

实际应用场景

场景:代码审查助手

# Agent 的工作流程:
# 1. 用户提交代码
# 2. 使用 code-review skill 加载审查指南
# 3. 使用 file tool 读取代码文件
# 4. 使用 skill 中的脚本执行自动检查
# 5. 使用 web search tool 查找最佳实践
# 6. 生成综合报告

agent.print_response("Review the file main.py")

# Agent 会:
# - get_skill_instructions("code-review") → 获取审查指南
# - FileTools.read_file("main.py") → 读取文件
# - get_skill_script("code-review", "check_style.py", execute=True) → 运行检查
# - DuckDuckGoTools.search("Python best practices 2024") → 搜索最新实践

Skills 提供"知道如何做"的知识,Tools 提供"能够做"的能力。


四、最佳实践建议

1. Skill 设计原则

✅ DO(推荐做法)

单一职责原则

✅ 好的设计:
skills/
├── code-review/          # 专注于代码审查
├── git-workflow/         # 专注于 Git 工作流
└── testing/              # 专注于测试策略

❌ 不好的设计:
skills/
└── programming/          # 太宽泛,包含所有编程相关内容

清晰的描述

# ✅ 好的描述
description: 代码审查辅助,检查 Python 代码的样式、安全性和性能问题

# ❌ 不好的描述
description: 帮助编程

结构化的指令

✅ 好的指令:
## 何时使用
- 明确的使用场景

## 过程
1. 步骤一
2. 步骤二
3. 步骤三

## 最佳实践
- 具体的建议

❌ 不好的指令:
随便写一些关于代码审查的内容...

❌ DON'T(避免的做法)

  1. 不要创建过于宽泛的技能

    • ❌ "programming-helper"(太宽泛)
    • ✅ "python-code-review"、"git-workflow"(具体明确)
  2. 不要在 SKILL.md 中放置大量代码

    • ❌ 在 SKILL.md 中包含数百行代码示例
    • ✅ 使用 scripts/ 目录存放脚本,references/ 存放详细文档
  3. 不要忽略验证规则

    • ❌ 技能名称使用大写字母或下划线
    • ✅ 使用小写字母、数字和连字符

2. 开发工作流最佳实践

本地开发和测试

# 开发模式配置
agent = Agent(
    model=OpenAIChat(id="gpt-4o"),
    skills=Skills(loaders=[LocalSkills("./skills")]),
    show_tool_calls=True,          # ✅ 显示工具调用
    debug_mode=True,                # ✅ 启用调试模式
    markdown=True,
)

# 测试技能加载
try:
    skills = Skills(loaders=[LocalSkills("./skills")])
    print("✅ Skills loaded successfully")
    print(f"Available skills: {skills.list()}")
except SkillValidationError as e:
    print(f"❌ Validation failed: {e}")

版本控制

skills/
├── code-review/
│   ├── SKILL.md                 # ✅ 在 YAML 中包含版本号
│   ├── CHANGELOG.md            # ✅ 记录变更历史
│   └── README.md               # ✅ 使用说明
---
name: code-review
description: 代码审查辅助
metadata:
  version: "1.2.0"              # ✅ 语义化版本
  changelog:
    - "1.2.0: Added security checks"
    - "1.1.0: Improved performance analysis"
    - "1.0.0: Initial release"
---

3. 性能优化

控制技能数量

# ❌ 加载所有技能(如果有很多技能会影响性能)
skills = Skills(loaders=[
    LocalSkills("/path/to/100-skills")
])

# ✅ 只加载需要的技能
skills = Skills(loaders=[
    LocalSkills("/path/to/skills/code-review"),
    LocalSkills("/path/to/skills/git-workflow"),
])

优化脚本执行

# scripts/check_style.py

# ✅ 设置合理的超时
agent.run("""
Use get_skill_script with:
- execute: True
- timeout: 30  # 30 秒超时
""")

# ✅ 缓存重复计算的结果
cache = {}

def expensive_check(code):
    code_hash = hash(code)
    if code_hash in cache:
        return cache[code_hash]
    
    result = perform_check(code)
    cache[code_hash] = result
    return result

按需加载参考文档

## Skill 指令设计

✅ 好的设计:
在 SKILL.md 中提供概要,在 references/ 中提供详细内容

❌ 不好的设计:
在 SKILL.md 中放置所有详细文档(会增加上下文长度)

4. 安全性考虑

脚本执行安全

# ✅ 验证脚本来源
def validate_skill_script(skill_path):
    """验证技能脚本的完整性"""
    import hashlib
    
    # 计算脚本哈希
    with open(skill_path, 'rb') as f:
        script_hash = hashlib.sha256(f.read()).hexdigest()
    
    # 与已知安全哈希比对
    known_hashes = {
        "check_style.py": "abc123...",
    }
    
    return script_hash in known_hashes.values()

# ✅ 限制脚本权限
# 在 Docker 容器或沙盒环境中运行脚本

输入验证

# scripts/process_data.py

def process_data(user_input: str):
    # ✅ 验证输入
    if not user_input or len(user_input) > 10000:
        raise ValueError("Invalid input length")
    
    # ✅ 清理输入
    clean_input = sanitize(user_input)
    
    # ✅ 使用参数化查询(如果涉及数据库)
    # 避免 SQL 注入

5. 文档和维护

编写清晰的文档

# ✅ 好的 Skill 文档结构

# Skill 名称

简短描述(1-2 句话)

## 何时使用
明确的使用场景列表

## 功能
- 功能 1
- 功能 2

## 使用示例
具体的使用例子

## 限制和注意事项
- 限制 1
- 限制 2

## 相关资源
- 脚本:xxx
- 参考:yyy

持续更新

# ✅ 提供 reload 机制
skills = Skills(loaders=[LocalSkills("./skills")])

# 技能更新后
skills.reload()

# ✅ 监控技能使用情况
from agno.monitoring import track_skill_usage

@track_skill_usage
def use_skill(agent, skill_name):
    return agent.get_skill_instructions(skill_name)

6. 团队协作

Skill 共享

company-skills/                # 公司级共享技能
├── code-review/
├── documentation/
└── security-check/

project-skills/                # 项目特定技能
├── api-design/
└── database-migration/

# 加载时合并
skills = Skills(loaders=[
    LocalSkills("company-skills"),
    LocalSkills("project-skills"),
])

贡献指南

创建 CONTRIBUTING.md

# Skill 贡献指南

## 提交新 Skill

1. 确保遵循命名规范
2. 包含完整的 SKILL.md
3. 添加测试用例
4. 更新 README

## 代码审查清单

- [ ] 名称符合规范
- [ ] 描述清晰准确
- [ ] 包含使用示例
- [ ] 脚本有执行权限
- [ ] 通过验证测试

7. 监控和调试

日志记录

import logging

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)

# 在 Agent 中启用调试
agent = Agent(
    model=OpenAIChat(id="gpt-4o"),
    skills=Skills(loaders=[LocalSkills("./skills")]),
    debug_mode=True,              # ✅ 启用调试模式
    show_tool_calls=True,         # ✅ 显示工具调用
)

# 记录技能使用
logger = logging.getLogger("skill_usage")
logger.info(f"Loaded skill: {skill_name}")

错误处理

from agno.skills import SkillValidationError

try:
    skills = Skills(loaders=[LocalSkills("./skills")])
except SkillValidationError as e:
    logger.error(f"Skill validation failed: {e}")
    logger.error(f"Errors: {e.errors}")
    # 降级处理:使用默认配置
    skills = None
except Exception as e:
    logger.exception(f"Unexpected error: {e}")
    raise

总结

通过本教程,我们深入学习了 Agno Skills 的核心概念、使用方法和最佳实践:

关键要点

  1. Skill 是知识包:将领域专业知识打包成可重用的模块
  2. 渐进式发现:Browse → Load → Reference → Execute
  3. 三个核心组件:SKILL.md(指令)、scripts/(脚本)、references/(参考文档)
  4. 与 Tools 互补:Skills 提供"如何做"的知识,Tools 提供"能够做"的能力
  5. Claude Agent Skills:内置的文档处理能力(PPTX、XLSX、DOCX、PDF)