惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

B
Blog RSS Feed
WordPress大学
WordPress大学
博客园_首页
罗磊的独立博客
D
Docker
N
Netflix TechBlog - Medium
博客园 - Franky
Hugging Face - Blog
Hugging Face - Blog
D
DataBreaches.Net
I
InfoQ
L
LangChain Blog
GbyAI
GbyAI
V
V2EX
博客园 - 聂微东
P
Proofpoint News Feed
博客园 - 【当耐特】
腾讯CDC
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
量子位
Martin Fowler
Martin Fowler
有赞技术团队
有赞技术团队
U
Unit 42
博客园 - 司徒正美
大猫的无限游戏
大猫的无限游戏

博客园 - bamb00

PDF 解析为什么难:一份面向 RAG 的问题清单与解决路线 一个项目带你入门AI应用开发08 一个项目带你入门AI应用开发08 一个项目带你入门AI应用开发课程介绍 一个项目带你入门AI应用开发06 一个项目带你入门AI应用开发05 一个项目带你入门AI应用开发05 一个项目带你入门AI应用开发04 一个项目带你入门AI应用开发03 一个项目带你 入门AI应用开发02 一个项目带你入门AI应用开发01 3・15 曝光 AI 安全暗礁:企业如何筑牢投毒与幻觉防护墙 CodeQL学习——导航调用图 CodeQL学习——java程序抽象语法树 linux进程隐藏手段及对抗方法 OAuth2.0安全设计之Authorization Code CodeQL学习——CodeQl数据流分析 CodeQL学习——CodeQL java库 CodeQL学习——自定义查询 CodeQL学习——CodeQL CLI入门 用户中心 - 博客园 浏览器解析js intent 参数的规范 Fiddler如何自动修改请求和响应包 Burp Suite学习之Intruder的4种攻击模式 对Android系统权限的认识 "INSTALL_FAILED_DUPLICATE_PERMISSION "错误解决 Android hook神器frida(二) 移动广告作弊技术研究 Android如果有一个任意写入的漏洞,如何将写权限转成执行权限
一个项目带你入门AI应用开发07
bamb00 · 2026-08-03 · via 博客园 - bamb00

第 7 课:可插拔的数据源

7.1 你的目标

在不改 Agent 代码的前提下,切换数据源(从演示数据到真实数据库)。

.env 中:
DATA_PROVIDER=demo       → 使用内存演示数据
DATA_PROVIDER=postgres   → 使用 PostgreSQL 数据库

7.2 问题:数据耦合

第 4 课的 _execute_tool

def _execute_tool(name, args):
    if name == "query_order":
        # 订单数据写死在代码里
        _ORDERS = {
            "ORD-001": {"status": "shipped", ...},
        }
        return _ORDERS.get(args["order_id"])

想换成 PostgreSQL?要改 _execute_tool。想换成从 CSV 读?要改 _execute_tool。想写单元测试?需要准备真实数据。

Agent 的逻辑和数据源耦合在一起了。

7.3 本质问题

不是"代码写得不好",而是一个架构设计问题:

  • Agent 的逻辑("什么时候应该查订单")应该稳定
  • 数据源("订单数据从哪来")应该灵活

稳定的逻辑不应该依赖灵活的实现。

7.4 解法:抽象接口

第一步:定义数据模型

@dataclass(frozen=True)
class Order:
    order_id: str
    status: str
    product: str
    amount: float

第二步:定义抽象接口

class ECommerceDataProvider(ABC):
    @abstractmethod
    def get_order(self, order_id: str) -> Optional[Order]: ...
    @abstractmethod
    def get_shipment(self, order_id: str) -> Optional[Shipment]: ...
    @abstractmethod
    def get_all_knowledge_docs(self) -> list[KnowledgeDoc]: ...

第三步:实现多个版本

class DemoDataProvider(ECommerceDataProvider):
    """内存演示数据"""
    def get_order(self, order_id):
        return _ORDERS.get(order_id)

class PostgresDataProvider(ECommerceDataProvider):
    """PostgreSQL 实现"""
    def __init__(self, conn_string):
        self.conn = psycopg2.connect(conn_string)
    def get_order(self, order_id):
        cursor = self.conn.cursor()
        cursor.execute("SELECT * FROM orders WHERE id = %s", (order_id,))
        row = cursor.fetchone()
        if row:
            return Order(row[0], row[1], row[2], row[3])
        return None

第四步:工厂模式

def get_data_provider():
    provider_type = settings.data_provider
    if provider_type == "demo":
        return DemoDataProvider()
    elif provider_type == "postgres":
        return PostgresDataProvider(settings.database_url)
from app.data.config import get_data_provider

def _execute_tool(name, args):
    provider = get_data_provider()
    
    if name == "query_order":
        order = provider.get_order(args.get("order_id", ""))
        if order:
            return json.dumps({"found": True, "order": asdict(order)})
        return json.dumps({"found": False})

_execute_tool 不再知道数据从哪来。它只知道"调 provider.get_order() 能拿到订单"。

7.6 这和 Web 开发的分层有什么不同?

Web 开发中也常用接口抽象数据层。但 Agent 系统的数据层有两点不同:

  1. 数据是为 LLM 服务的。接口设计要考虑 LLM 需要什么数据、以什么格式获取。比如 get_order 返回的 Order 对象会被序列化成 JSON 给 LLM 看,字段名要清晰。

  2. 错误处理要友好。如果数据库连接失败,LLM 应该回复"系统暂时无法查询订单"而不是抛出 500 错误。

本课知识点

概念 你做了什么 为什么
抽象接口 ABC + abstractmethod Agent 依赖接口不依赖实现
工厂模式 根据配置创建实例 切换数据源只改配置,不改代码
开闭原则 新增实现类即可 不修改 Agent 逻辑

课后作业

  1. 实现一个 CSVDataProvider,从 CSV 文件读取订单数据
  2. 实现一个 RedisDataProvider,从 Redis 读取会话历史

面试可能会问

"Data Provider 模式和 Repository 模式有什么区别?"
本质相同,都是隔离数据访问。区别在于命名偏好和抽象粒度。Data Provider 更强调"提供数据",Repository 更强调"聚合根"。