Skip to content

第10章 实战项目一:智能数据分析Agent

理论到实践的距离,只有动手才能缩短。从本章开始,我们将用三个完整的实战项目,把前面学到的知识融会贯通。第一个项目是智能数据分析Agent——用户用自然语言提问,Agent自动查询数据、执行分析、生成图表和报告。

10.1 需求分析:从自然语言到SQL与图表生成

项目目标

构建一个数据分析Agent,核心能力:

能力用户输入示例Agent输出
数据查询"上个月的销售额是多少"SQL查询 + 结果表格
趋势分析"销售额的增长趋势如何"趋势图 + 文字分析
异常检测"有没有异常的订单"异常数据列表 + 原因推测
报告生成"生成本月销售报告"完整分析报告(含图表)

架构设计

用户自然语言问题
       |
  [意图识别] ──> 查询类 / 分析类 / 报告类
       |
  [Schema理解] ──> 读取数据库元数据
       |
  [SQL生成] ──> 生成并执行SQL
       |
  [结果处理] ──> 格式化 / 生成图表 / 文字解读
       |
  [安全检查] ──> 确认无敏感数据泄露
       |
  返回结果

技术选型

组件选型理由
LLMGPT-4oSQL生成和数据分析需要强推理能力
数据库SQLite(演示)/ PostgreSQL(生产)轻量且兼容
图表库matplotlib + Plotly静态图 + 交互图
框架LangChainAgent编排 + 工具管理
前端Streamlit快速构建交互界面

10.2 数据预处理与Schema映射策略

示例数据准备

python
import sqlite3
import pandas as pd

def create_sample_database(db_path: str = "sales.db"):
    """创建示例销售数据库"""
    conn = sqlite3.connect(db_path)

    # 订单表
    orders_data = {
        "order_id": [f"ORD{i:06d}" for i in range(1, 1001)],
        "customer_id": [f"C{np.random.randint(1, 200):04d}" for _ in range(1000)],
        "product_id": [f"P{np.random.randint(1, 50):04d}" for _ in range(1000)],
        "order_date": pd.date_range("2024-01-01", periods=1000, freq="8H"),
        "quantity": [np.random.randint(1, 20) for _ in range(1000)],
        "unit_price": [round(np.random.uniform(10, 500), 2) for _ in range(1000)],
    }
    df_orders = pd.DataFrame(orders_data)
    df_orders["total_amount"] = df_orders["quantity"] * df_orders["unit_price"]
    df_orders.to_sql("orders", conn, if_exists="replace", index=False)

    # 产品表
    products_data = {
        "product_id": [f"P{i:04d}" for i in range(1, 51)],
        "product_name": [f"产品{i}" for i in range(1, 51)],
        "category": [np.random.choice(["电子", "服装", "食品", "家居"]) for _ in range(50)],
    }
    pd.DataFrame(products_data).to_sql("products", conn, if_exists="replace", index=False)

    conn.close()

create_sample_database()

Schema映射:让LLM理解数据库结构

LLM需要知道数据库的表结构才能生成正确的SQL。关键策略是精简但完整地描述Schema

python
def get_db_schema(db_path: str) -> str:
    """提取数据库Schema描述"""
    conn = sqlite3.connect(db_path)
    cursor = conn.cursor()

    cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
    tables = cursor.fetchall()

    schema_description = ""
    for (table_name,) in tables:
        cursor.execute(f"PRAGMA table_info({table_name})")
        columns = cursor.fetchall()

        schema_description += f"\n{table_name}:\n"
        for col in columns:
            col_name, col_type = col[1], col[2]
            schema_description += f"  - {col_name} ({col_type})"

        # 添加示例数据(3行)
        cursor.execute(f"SELECT * FROM {table_name} LIMIT 3")
        rows = cursor.fetchall()
        col_names = [desc[0] for desc in cursor.description]
        sample_df = pd.DataFrame(rows, columns=col_names)
        schema_description += f"\n  示例数据:\n{sample_df.to_string()}\n"

    conn.close()
    return schema_description

Schema描述优化

直接把完整Schema塞给LLM太浪费Token。优化策略:

python
def get_condensed_schema(db_path: str, relevant_tables: list[str] = None) -> str:
    """精简Schema描述,只包含关键信息"""
    conn = sqlite3.connect(db_path)
    cursor = conn.cursor()

    cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
    all_tables = [t[0] for t in cursor.fetchall()]
    target_tables = relevant_tables or all_tables

    schema_lines = []
    for table_name in target_tables:
        if table_name not in all_tables:
            continue
        cursor.execute(f"PRAGMA table_info({table_name})")
        columns = cursor.fetchall()
        col_descriptions = ", ".join([f"{c[1]} {c[2]}" for c in columns])
        schema_lines.append(f"{table_name}({col_descriptions})")

    conn.close()
    return "\n".join(schema_lines)

# 输出示例:
# orders(order_id TEXT, customer_id TEXT, product_id TEXT, order_date DATETIME, quantity INTEGER, unit_price REAL, total_amount REAL)
# products(product_id TEXT, product_name TEXT, category TEXT)

10.3 代码生成与执行:PandasAI的核心实现逻辑

SQL生成与执行工具

python
from langchain_core.tools import tool
import sqlite3

@tool
def execute_sql(query: str, db_path: str = "sales.db") -> str:
    """执行SQL查询并返回结果。

    Args:
        query: SQL查询语句(仅支持SELECT)
        db_path: 数据库文件路径
    """
    # 安全检查
    forbidden_keywords = ["DROP", "DELETE", "UPDATE", "INSERT", "ALTER", "CREATE"]
    if any(kw in query.upper() for kw in forbidden_keywords):
        return "安全限制:只允许执行SELECT查询"

    try:
        conn = sqlite3.connect(db_path)
        cursor = conn.cursor()
        cursor.execute(query)
        rows = cursor.fetchall()
        col_names = [desc[0] for desc in cursor.description]

        df = pd.DataFrame(rows, columns=col_names)
        conn.close()

        if len(df) > 50:
            return f"查询返回{len(df)}行数据,前50行:\n{df.head(50).to_string()}"
        return df.to_string()
    except Exception as e:
        return f"SQL执行错误:{e}"

@tool
def generate_chart(data_description: str, chart_type: str = "bar") -> str:
    """根据数据描述生成图表。

    Args:
        data_description: 数据描述,格式为"列1,列2,...\\n值1,值2,...\\n..."
        chart_type: 图表类型,可选bar/line/pie/scatter
    """
    import matplotlib.pyplot as plt

    lines = data_description.strip().split("\n")
    headers = lines[0].split(",")
    data = [line.split(",") for line in lines[1:]]

    df = pd.DataFrame(data, columns=headers)
    for col in df.columns[1:]:
        df[col] = pd.to_numeric(df[col], errors="coerce")

    fig, ax = plt.subplots(figsize=(10, 6))
    if chart_type == "bar":
        df.plot(x=headers[0], y=headers[1:], kind="bar", ax=ax)
    elif chart_type == "line":
        df.plot(x=headers[0], y=headers[1:], kind="line", ax=ax)
    elif chart_type == "pie":
        df.plot(y=headers[1], labels=df[headers[0]], kind="pie", ax=ax)

    chart_path = f"/tmp/chart_{int(time.time())}.png"
    plt.savefig(chart_path, dpi=150, bbox_inches="tight")
    plt.close()

    return f"图表已生成:{chart_path}"

构建数据分析Agent

python
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain_core.prompts import ChatPromptTemplate

db_schema = get_condensed_schema("sales.db")

system_prompt = f"""你是一个专业的数据分析助手。

## 数据库结构
{db_schema}

## 工作流程
1. 分析用户的问题,理解数据需求
2. 生成正确的SQL查询语句
3. 使用execute_sql工具执行查询
4. 分析查询结果
5. 如需图表,使用generate_chart工具生成
6. 用自然语言解读分析结果

## 注意事项
- 只生成SELECT语句,不修改数据
- 日期格式:YYYY-MM-DD
- 金额字段使用total_amount
- 分析时要给出具体数字,不要笼统描述
"""

prompt = ChatPromptTemplate.from_messages([
    ("system", system_prompt),
    ("human", "{input}"),
    ("placeholder", "{agent_scratchpad}"),
])

llm = ChatOpenAI(model="gpt-4o", temperature=0)
tools = [execute_sql, generate_chart]

agent = create_openai_tools_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True, max_iterations=5)

使用示例

python
result = agent_executor.invoke({
    "input": "各产品类别的销售额排名如何?请生成柱状图"
})
print(result["output"])

10.4 安全围栏:防止恶意代码执行与数据泄露

三层安全防护

Layer 1: 提示词层 - 告知Agent安全规则
Layer 2: 工具层 - 工具内部做安全检查
Layer 3: 输出层 - 检查返回内容是否包含敏感数据

输出脱敏

python
class OutputSanitizer:
    """输出脱敏处理器"""

    SENSITIVE_PATTERNS = {
        "phone": (r"1[3-9]\d{9}", lambda m: m.group()[:3] + "****" + m.group()[-4:]),
        "email": (r"[\w.-]+@[\w.-]+\.\w+", lambda m: m.group()[0] + "***@" + m.group().split("@")[1]),
        "id_card": (r"\d{17}[\dXx]", lambda m: m.group()[:6] + "********" + m.group()[-4:]),
    }

    def sanitize(self, text: str) -> str:
        for pattern_name, (pattern, replace_fn) in self.SENSITIVE_PATTERNS.items():
            text = re.sub(pattern, replace_fn, text)
        return text

    def check_sensitive_columns(self, df: pd.DataFrame) -> pd.DataFrame:
        """检查并脱敏DataFrame中的敏感列"""
        sensitive_keywords = ["phone", "email", "id_card", "password", "secret"]
        for col in df.columns:
            if any(kw in col.lower() for kw in sensitive_keywords):
                df[col] = "[REDACTED]"
        return df

查询审计

python
class QueryAuditor:
    """查询审计日志"""

    def __init__(self, log_path: str = "query_audit.jsonl"):
        self.log_path = log_path

    def audit(self, user: str, question: str, sql: str, result_summary: str):
        record = {
            "timestamp": datetime.now().isoformat(),
            "user": user,
            "question": question,
            "sql": sql,
            "result_summary": result_summary[:200],
            "risk_level": self._assess_risk(sql),
        }
        with open(self.log_path, "a") as f:
            f.write(json.dumps(record, ensure_ascii=False) + "\n")

    def _assess_risk(self, sql: str) -> str:
        sql_upper = sql.upper()
        if any(kw in sql_upper for kw in ["DROP", "DELETE", "UPDATE"]):
            return "high"
        if any(kw in sql_upper for kw in ["password", "secret", "token"]):
            return "high"
        if "LIMIT" not in sql_upper and "COUNT" not in sql_upper:
            return "medium"  # 无限制查询可能返回大量数据
        return "low"

10.5 结果解释:将数据洞察转化为自然语言报告

分析结果的结构化输出

python
class AnalysisReporter:
    """将分析结果转化为自然语言报告"""

    def __init__(self, llm):
        self.llm = llm

    def generate_report(self, question: str, query_result: str, 
                        chart_path: str = None) -> str:
        report_prompt = f"""
用户问题:{question}
数据查询结果:
{query_result}

请生成一份结构化的分析报告,包含:
1. 核心发现(1-2句话总结最重要的洞察)
2. 数据详情(关键数字和变化)
3. 趋势分析(如果有时间维度)
4. 建议行动(基于数据的可操作建议)

要求:
- 用具体数字说话,不要说"有所增长",要说"增长了23.5%"
- 如有异常值,特别标注
- 语言简洁,避免数据堆砌
"""
        return self.llm.invoke(report_prompt).content

    def generate_summary_table(self, data: pd.DataFrame) -> str:
        """将DataFrame转化为Markdown摘要表格"""
        if len(data) > 10:
            summary = data.describe().to_markdown()
            top5 = data.head(5).to_markdown()
            return f"统计摘要:\n{summary}\n\n前5行数据:\n{top5}"
        return data.to_markdown()

完整的端到端流程

python
class DataAnalysisAgent:
    """完整的智能数据分析Agent"""

    def __init__(self, db_path: str = "sales.db"):
        self.llm = ChatOpenAI(model="gpt-4o", temperature=0)
        self.db_path = db_path
        self.sanitizer = OutputSanitizer()
        self.auditor = QueryAuditor()
        self.reporter = AnalysisReporter(self.llm)

        # 构建Agent
        db_schema = get_condensed_schema(db_path)
        system_prompt = f"""你是专业数据分析助手。数据库结构:{db_schema}
工作流程:分析问题 -> 生成SQL -> 执行查询 -> 解读结果。
只执行SELECT查询。给出具体数字。"""

        prompt = ChatPromptTemplate.from_messages([
            ("system", system_prompt),
            ("human", "{input}"),
            ("placeholder", "{agent_scratchpad}"),
        ])

        tools = [execute_sql, generate_chart]
        agent = create_openai_tools_agent(self.llm, tools, prompt)
        self.executor = AgentExecutor(agent=agent, tools=tools, verbose=True, max_iterations=5)

    def analyze(self, question: str, user: str = "anonymous") -> dict:
        # 1. Agent执行
        result = self.executor.invoke({"input": question})

        # 2. 输出脱敏
        safe_output = self.sanitizer.sanitize(result["output"])

        # 3. 审计记录
        self.auditor.audit(user, question, "auto-generated", safe_output[:200])

        return {
            "question": question,
            "answer": safe_output,
            "success": True,
        }

本章小结

模块核心实现关键要点
需求分析意图识别 + 架构设计先定义能力边界,再设计架构
Schema映射精简描述 + 示例数据LLM需要足够但不冗余的Schema信息
SQL生成执行Function Calling + 安全检查只允许SELECT,禁止修改操作
安全围栏提示词 + 工具层 + 输出层三层防护,输出脱敏
结果解释结构化报告 + 具体数字数据洞察用数字说话

下一章,我们将构建第二个实战项目——自动化研发运维Agent。

热爱生活,喜好美食,追求未来!