第 9 节:使用 Agno 构建 AI Agent

0 阅读8分钟

第 9 节:使用 Agno 构建 AI Agent

阅读时间:约 8 分钟
难度级别:实战
前置知识:FastAPI 基础、CubeJS 集成、AI 基础概念

本节概要

通过本节学习,你将掌握:

  • Agno 框架的核心概念和工作原理
  • 创建专业的 CubeJS Agent
  • 设计有效的 Agent 指令(Instructions)
  • 加载和注入数据模型到 Agent
  • 测试和优化 Agent 的输出质量
  • Agent 的错误处理和调试技巧

引言

Agno 是一个强大的 AI Agent 框架,它让我们能够轻松构建专业的 AI 应用。本节我们将学习如何使用 Agno 创建一个专门理解 CubeJS 查询的 Agent,这是 Text-to-BI 系统的核心组件。

AI Agent 是 Text-to-BI 系统的核心。本文将介绍如何使用 Agno 框架构建一个专业的 CubeJS 查询 Agent。

🎯 本章目标

完成后,你将拥有:

  • ✅ 理解 Agno Agent 的工作原理
  • ✅ 创建专业的 CubeJS Agent
  • ✅ 掌握提示词工程技巧
  • ✅ 实现上下文注入
  • ✅ 测试 Agent 的输出

🤖 什么是 Agno Agent?

Agent 的概念

Agent 是一个具有特定能力和知识的 AI 实体:

from agno.agent import Agent
from agno.models.deepseek import DeepSeek

agent = Agent(
    name="DataAnalyst",           # Agent 名称
    model=DeepSeek(...),          # 使用的模型
    instructions="你是数据分析专家...",  # 指令
    markdown=True                 # 支持 Markdown 输出
)

# 使用 Agent
response = agent.run("分析这些数据")

Agent vs 普通 LLM 调用

普通 LLM 调用:

# 每次都要重复上下文
response = llm.chat("你是数据分析专家。分析这些数据...")

使用 Agent:

# 上下文已经内置
response = agent.run("分析这些数据")

优势:

  • ✅ 上下文复用
  • ✅ 专业化能力
  • ✅ 一致的行为
  • ✅ 易于测试

📝 设计 CubeJS Agent

Step 1: 理解需求

与 AI 对话:

我需要创建一个 CubeJS 查询 Agent,要求:

1. 理解 CubeJS 数据模型(YAML 格式)
2. 将自然语言转换为 CubeJS REST API 查询 JSON
3. 输出格式规范,包含查询分析和 JSON 代码块
4. 支持常见的查询类型:统计、分组、过滤、排序

请帮我设计这个 Agent 的指令。

Step 2: 加载数据模型

创建模型加载函数:

"""
CubeJS Agent - 专业的 CubeJS 查询生成器
"""
import os
import yaml
from agno.agent import Agent
from agno.models.deepseek import DeepSeek


def load_cubejs_schema(schema_path: str) -> dict:
    """
    加载单个 CubeJS YAML 模型文件
    
    Args:
        schema_path: YAML 文件路径
        
    Returns:
        解析后的模型字典
    """
    with open(schema_path, 'r', encoding='utf-8') as f:
        return yaml.safe_load(f)


def load_all_cubejs_schemas(model_dir: str) -> dict:
    """
    加载目录下所有的 CubeJS 模型文件
    
    Args:
        model_dir: 模型目录路径
        
    Returns:
        所有模型的字典,键为模型名称
    """
    schemas = {}
    
    try:
        for filename in os.listdir(model_dir):
            if filename.endswith('.cube.yml'):
                schema_path = os.path.join(model_dir, filename)
                cube_name = filename.replace('.cube.yml', '')
                schemas[cube_name] = load_cubejs_schema(schema_path)
    except FileNotFoundError:
        schemas = {"error": "Model directory not found"}
    
    return schemas

Step 3: 构建 Agent 指令

关键要素:

  1. 角色定义:明确 Agent 的身份
  2. 数据模型:注入可用的数据结构
  3. 输出格式:规范输出格式
  4. 示例:提供参考示例
  5. 规则:明确约束条件

完整的 Agent 构建函数:

def build_cubejs_agent():
    """
    构建 CubeJS 专家 Agent
    
    Returns:
        配置好的 Agno Agent
    """
    
    # 加载所有数据模型
    model_dir = os.path.join(
        os.path.dirname(__file__), 
        '../cubejs/model'
    )
    
    try:
        all_schemas = load_all_cubejs_schemas(model_dir)
    except Exception as e:
        all_schemas = {"error": f"Failed to load schemas: {str(e)}"}
    
    # 将模型转换为 YAML 字符串
    schema_yaml = yaml.dump(all_schemas, default_flow_style=False, allow_unicode=True)
    
    # 构建详细的指令
    instructions = f"""你是 CubeJS 查询生成器。将自然语言转换为 CubeJS JSON 查询。

**可用的数据模型:**

{schema_yaml}

**响应格式(严格遵守):**

## 🔍 查询分析

[一句话说明查询目的]

```json
[JSON query here - properly formatted]

示例:

用户:"统计员工总数" 响应:

🔍 查询分析

统计数据库中的员工总数

{{
  "measures": ["employees.total_employees"]
}}

用户:"按性别统计员工数量" 响应:

🔍 查询分析

按性别分组统计员工数量

{{
  "measures": ["employees.total_employees"],
  "dimensions": ["employees.gender"]
}}

用户:"各部门的员工数量" 响应:

🔍 查询分析

按部门分组统计员工数量

{{
  "measures": ["employees.total_employees"],
  "dimensions": ["employees.dept_name"]
}}

规则:

  • 查询分析只用一句话
  • JSON 代码块前后必须有空行
  • 确保 JSON 格式正确,有正确的缩进
  • 使用正确的 cube 前缀(如 "employees.measure_name")
  • 只包含必要的查询属性
  • JSON 代码块后不要添加额外的说明

现在生成查询:"""

# 创建 Agent
agent = Agent(
    model=DeepSeek(id="deepseek-chat"),
    instructions=instructions,
    markdown=True,
    debug_mode=False,
)

return agent

## 🎨 提示词工程技巧

### 1. 结构化指令

使用清晰的结构组织指令:

角色定义 你是...

可用资源 数据模型:...

任务描述 将...转换为...

输出格式 格式:...

示例 示例1:... 示例2:...

规则

  • 规则1
  • 规则2

### 2. 提供具体示例

```python
instructions = """
**示例:**

输入:"统计员工总数"
输出:
```json
{
  "measures": ["employees.total_employees"]
}

输入:"按性别统计" 输出:

{
  "measures": ["employees.total_employees"],
  "dimensions": ["employees.gender"]
}

"""


### 3. 明确约束条件

```python
instructions = """
**规则:**
- 必须使用 JSON 格式
- 必须包含 measures 或 dimensions
- cube 名称必须加前缀
- 不要添加注释
- 不要使用未定义的字段
"""

4. 格式控制

instructions = """
**输出格式(严格遵守):**

## 🔍 查询分析
[一句话]

```json
{JSON内容}

注意:

  • JSON 前后必须有空行
  • 不要添加额外内容 """

## 🧪 测试 Agent

### Step 1: 创建测试函数

```python
def query_cubejs(user_question: str, stream: bool = False):
    """
    使用 CubeJS Agent 处理查询
    
    Args:
        user_question: 用户的自然语言问题
        stream: 是否使用流式输出
        
    Returns:
        Agent 的响应
    """
    agent = build_cubejs_agent()
    
    if stream:
        return agent.run(user_question, stream=True)
    else:
        response = agent.run(user_question, stream=False)
        return response.content

Step 2: 测试不同场景

创建测试脚本:

if __name__ == "__main__":
    from dotenv import load_dotenv
    load_dotenv()
    
    # 测试用例
    test_cases = [
        "统计员工总数",
        "按性别统计员工数量",
        "显示各部门的员工分布",
        "查询女性员工数量",
    ]
    
    print("=== CubeJS Agent 测试 ===\n")
    
    for question in test_cases:
        print(f"问题: {question}")
        print("-" * 60)
        
        response = query_cubejs(question)
        print(response)
        print("\n" + "=" * 60 + "\n")

运行测试:

cd backend
python agents/cubejs_agent.py

预期输出:

=== CubeJS Agent 测试 ===

问题: 统计员工总数
------------------------------------------------------------
## 🔍 查询分析

统计数据库中的员工总数

```json
{
  "measures": ["employees.total_employees"]
}

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

问题: 按性别统计员工数量

🔍 查询分析

按性别分组统计员工数量

{
  "measures": ["employees.total_employees"],
  "dimensions": ["employees.gender"]
}

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


## 🔍 调试 Agent

### 启用调试模式

```python
agent = Agent(
    model=DeepSeek(id="deepseek-chat"),
    instructions=instructions,
    markdown=True,
    debug_mode=True,  # 启用调试
)

调试输出:

[DEBUG] Agent: CubeJSExpert
[DEBUG] Input: 统计员工总数
[DEBUG] Model: deepseek-chat
[DEBUG] Instructions length: 1234 chars
[DEBUG] Response time: 1.23s
[DEBUG] Output length: 156 chars

验证输出格式

import json
import re

def validate_agent_output(output: str) -> bool:
    """
    验证 Agent 输出格式
    
    Args:
        output: Agent 的输出
        
    Returns:
        是否符合格式要求
    """
    # 检查是否包含查询分析
    if "## 🔍 查询分析" not in output:
        print("❌ 缺少查询分析")
        return False
    
    # 提取 JSON 代码块
    json_pattern = r'```json\s*([\s\S]*?)\s*```'
    matches = re.findall(json_pattern, output)
    
    if not matches:
        print("❌ 缺少 JSON 代码块")
        return False
    
    # 验证 JSON 格式
    try:
        query = json.loads(matches[0])
        print("✅ JSON 格式正确")
        print(f"   Measures: {query.get('measures', [])}")
        print(f"   Dimensions: {query.get('dimensions', [])}")
        return True
    except json.JSONDecodeError as e:
        print(f"❌ JSON 解析失败: {e}")
        return False

💡 优化 Agent 性能

1. 缓存数据模型

from functools import lru_cache

@lru_cache(maxsize=1)
def load_all_cubejs_schemas_cached(model_dir: str) -> dict:
    """缓存数据模型加载"""
    return load_all_cubejs_schemas(model_dir)

2. 减少指令长度

# ❌ 冗长的指令
instructions = """
你是一个非常专业的 CubeJS 查询生成器...
你需要理解用户的自然语言问题...
然后将其转换为 CubeJS 的 REST API 查询格式...
...(大量重复说明)
"""

# ✅ 简洁的指令
instructions = """
你是 CubeJS 查询生成器。将自然语言转换为 CubeJS JSON 查询。

数据模型:{schema}

输出格式:
## 🔍 查询分析
[说明]

```json
{查询}

"""


### 3. 使用更快的模型

```python
# 对于简单查询,使用更快的模型
agent = Agent(
    model=DeepSeek(id="deepseek-chat"),  # 快速模型
    # model=DeepSeek(id="deepseek-coder"),  # 代码专用
)

🎯 Vibe Coding 要点

1. 迭代优化提示词

1版:"生成 CubeJS 查询"
测试 → 输出不规范

第2版:"生成 JSON 格式的 CubeJS 查询"
测试 → 缺少说明

第3版:"生成查询分析 + JSON 查询"
测试 → 格式不一致

第4版:"严格按照格式输出"
测试 → ✅ 符合要求

2. 使用示例引导

提供 3-5 个典型示例,覆盖主要场景:

  • 简单统计
  • 分组查询
  • 过滤条件
  • 排序

3. 及时验证

每次修改指令后,立即测试:

python agents/cubejs_agent.py

4. 保存成功的提示词

将有效的提示词模板保存下来,用于其他 Agent。

本节小结

本节我们完成了 AI Agent 的构建:

  1. Agno 框架:理解了 Agent、Model、Instructions 等核心概念
  2. Agent 创建:使用 DeepSeek 模型创建了专业的 CubeJS Agent
  3. 指令设计:编写了清晰、具体的 Agent 指令,包含角色、任务、输出格式
  4. 模型注入:将 CubeJS 数据模型动态注入到 Agent 指令中
  5. 测试优化:通过多轮测试优化 Agent 的输出质量
  6. 最佳实践:掌握了提示词工程的关键技巧

现在我们有了一个能够理解自然语言并生成 CubeJS 查询的 Agent。

思考与练习

思考题

  1. 为什么要创建专业的 Agent 而不是使用通用的 ChatGPT?
  2. Agent 指令中哪些部分最重要?如何平衡详细程度和简洁性?
  3. 如何评估 Agent 的输出质量?有哪些量化指标?
  4. 如果 Agent 经常生成错误的查询,应该如何调试和优化?

实践练习

  1. 创建新 Agent

    • 创建一个结果分析 Agent
    • 输入查询结果,输出数据分析和建议
    • 测试不同类型的数据
  2. 优化指令

    • 尝试不同的指令写法
    • 对比输出质量
    • 找出最佳的指令模板
  3. 错误处理

    • 故意输入模糊的查询
    • 观察 Agent 如何处理
    • 改进指令以提高容错性
  4. 多轮对话

    • 实现 Agent 的对话历史记录
    • 支持上下文相关的查询
    • 测试多轮对话效果

上一节第 8 节:集成 CubeJS 数据模型
下一节第 10 节:实现 Workflow 流程编排