1. 前言

创建真正理解并适应用户偏好的人工智能系统仍然是一项重大挑战。虽然大语言模型(LLMs)已经彻底改变了自然语言处理,但构建能保持一致、个性化交互的系统,不仅仅是向大语言模型进行 API 调用这么简单。

本文将以餐厅推荐系统为例,探讨 Pydantic Agents 和复杂的上下文管理如何创建出极为智能和个性化的应用程序。

Pydantic Agents:基于提示注入进行上下文处理的推荐系统_AI大模型

2. Pydantic Agents 基础

Pydantic Agents 代表了我们构建人工智能系统方式的范式转变。与可能通过简单提示直接与大语言模型交互的传统方法不同,Pydantic Agents 提供了一种结构化、类型安全的方式来管理人工智能交互,同时维护上下文和状态。让我们来探讨其关键组件以及它们如何协同工作。

3. Recommendation Agent:上下文感知智能

Recommendation Agent不仅仅是大语言模型的简单包装,而是一个复杂的系统,它能理解客户历史和偏好:

recommendation_agent = Agent(
   model="ollama:qwen2.5:32b",
   result_type=Restaurant,
   deps_type=RecommendationContext
)

@recommendation_agent.system_prompt
async def add_recommendation_context(ctx: RunContext[RecommendationContext]) -> str:
   """根据客户上下文生成智能推荐提示。"""
   current_request = ctx.deps.current_request
   customer_preferences = ctx.deps.preferences
   prompt = f"为{ctx.deps.customer_name}推荐一家餐厅。"
   if customer_preferences.favorite_cuisines:
       prompt += f"\n- 最喜欢的菜系: {', '.join(customer_preferences.favorite_cuisines)}"
   if customer_preferences.dietary_restrictions:
       prompt += f"\n- 饮食限制: {', '.join(customer_preferences.dietary_restrictions)}"
   if customer_preferences.preferred_price_range:
       prompt += f"\n- 价格范围: {', '.join(customer_preferences.preferred_price_range)}"
   if ctx.deps.dining_history:
       prompt += "\n\n最近的用餐体验:"
       for exp in sorted(ctx.deps.dining_history, key=lambda x: x.date, reverse=True)[:3]:
           prompt += f"\n 评分: {exp.rating}/5"
           prompt += f"\n- {exp.restaurant}"
           prompt += f"\n 喜欢的菜品: {', '.join(exp.liked_dishes)}"
   return prompt

此代理实现展示了几个复杂的功能:

  • 使用result_type=Restaurant进行强类型检查,确保输出类型安全。
  • 上下文感知提示生成考虑了当前偏好和历史数据。
  • 将历史记录限制为最近的三次体验。
  • 提示结构仔细区分了不同类型的偏好信息。

4. 类型安全模型:构建模块

系统的核心是一组 Pydantic 模型,用于确保类型安全和数据验证。这些模型是整个推荐引擎的基础:

from pydantic import BaseModel, Field
from typing import List, Optional, Dict, Literal
from datetime import datetime
from pydantic_ai import Agent, RunContext

class Restaurant(BaseModel):
   """餐厅详细信息。"""
   name: str
   cuisine: str
   price_range: Literal["$", "$$", "$$$", "$$$$"]
   vegetarian_friendly: bool = False
   gluten_free_options: bool = False
   average_rating: float
   specialties: List[str]
   location: str
   typical_wait_time: str

我们来分析一下Restaurant模型中每个字段的重要性:

  • price_range字段使用Literal类型来强制使用有效值,防止因无效价格输入而产生错误。
  • 饮食选项的布尔标志允许根据饮食需求快速筛选餐厅。
  • specialties列表允许与用户偏好进行细致匹配。
  • 包含typical_wait_time有助于根据时间限制进行上下文感知推荐。

5. 体验跟踪:从用户交互中学习

DiningExperience类捕捉每次餐厅用餐的丰富上下文:

class DiningExperience(BaseModel):
   """顾客用餐体验记录。"""
   restaurant: str
   date: datetime
   liked_dishes: List[str]
   rating: int
   disliked_dishes: List[str]
   comments: str
   party_size: int
   occasion: Optional[str] = None

这个模型在区分喜欢和不喜欢的菜品方面特别巧妙。这种区分使我们的系统能够:

  • 更细致地理解口味偏好。
  • 确定始终得到正面或负面反馈的特定菜品。
  • 考虑用餐人数和场合,为未来推荐提供参考。
  • 跟踪用餐体验中的时间模式。

6. 上下文管理:自适应系统的核心

系统的真正智能来自其复杂的上下文管理。接下来详细研究其工作原理。

6.1 丰富的客户偏好

CustomerPreferences类充当系统的记忆:

class CustomerPreferences(BaseModel):
   """顾客的用餐偏好。"""
   favorite_cuisines: List[str] = Field(default_factory=list)
   dietary_restrictions: List[str] = Field(default_factory=list)
   preferred_price_range: List[str] = Field(default_factory=list)
   typical_party_size: int = 2
   favorite_restaurants: List[str] = Field(default_factory=list)
   disliked_restaurants: List[str] = Field(default_factory=list)
   preferred_locations: List[str] = Field(default_factory=list)
   usual_occasions: List[str] = Field(default_factory=list)

这个模型在偏好跟踪方面堪称典范:

  • 独立捕获多个维度的偏好。
  • 设置默认值防止初始化错误。
  • 列表允许每个类别有多个值。
  • 偏好的组合允许进行复杂的筛选。

6.2 上下文演变:学习系统

CustomerContext类展示了偏好如何随时间演变:

class CustomerContext:
   """跟踪顾客在多次餐厅用餐中的上下文。"""
   def __init__(self, name: str, initial_preferences: CustomerPreferences):
       self.name = name
       self.preferences = initial_preferences
       self.visit_history: List[DiningExperience] = []
       self.recommendation_history: List[Restaurant] = []
       self.context_updates: List[dict] = []  # 跟踪所有上下文更改

   def add_visit(self, experience: DiningExperience, restaurant: Restaurant):
       """添加新的餐厅用餐并更新上下文。"""
       self.visit_history.append(experience)
       self.recommendation_history.append(restaurant)
       # 在更新前存储上下文状态
       before_state = self.preferences.model_dump()
       # 根据体验更新偏好
       if experience.rating >= 4:
           if restaurant.name not in self.preferences.favorite_restaurants:
               self.preferences.favorite_restaurants.append(restaurant.name)
           if restaurant.cuisine not in self.preferences.favorite_cuisines:
               self.preferences.favorite_cuisines.append(restaurant.cuisine)
       elif experience.rating <= 2:
           if restaurant.name not in self.preferences.disliked_restaurants:
               self.preferences.disliked_restaurants.append(restaurant.name)
       # 更新用餐人数偏好
       self.preferences.typical_party_size = (
               (self.preferences.typical_party_size * 2 + experience.party_size) / 3
       )
       # 更新场合偏好
       if experience.occasion and experience.occasion not in self.preferences.usual_occasions:
           self.preferences.usual_occasions.append(experience.occasion)

上下文演变系统特别复杂:

  • 它维护完整的用餐和推荐历史记录。
  • 跟踪所有上下文更新以进行分析。
  • 对用餐人数偏好使用加权平均。
  • 对正面和负面体验实施不同的阈值。
  • 保留之前的状态以分析偏好演变。

6.3 上下文持久化:维护长期记忆

系统实现了强大的上下文持久化:

class ContextPersistence:
   """处理顾客上下文的保存和加载。"""
   @staticmethod
   def save_context(customer_ctx: 'CustomerContext') -> None:
       context_file = CONTEXT_DIR / f"{customer_ctx.name.lower()}_context.json"
       # 将上下文转换为可序列化格式
       context_data = {
           'name': customer_ctx.name,
           'visit_history': [visit.model_dump() for visit in customer_ctx.visit_history],
           'preferences': customer_ctx.preferences.model_dump(),
           'recommendation_history': [rest.model_dump() for rest in customer_ctx.recommendation_history],
           'context_updates': customer_ctx.context_updates,
           'last_updated': datetime.now().isoformat()
       }
       # 使用自定义编码器保存到文件
       with open(context_file, 'w') as f:
           json.dump(context_data, f, indent=2, cls=DateTimeEncoder)

持久化层值得注意的地方在于:

  1. 使用 ISO 格式日期进行可靠的序列化。
  2. 维护完整的审计跟踪。
  3. 保留交互的完整历史记录。
  4. 支持系统分析和调试。

7. 生成智能推荐

推荐过程将所有内容整合在一起:

async def get_restaurant_recommendation(
       customer_name: str,
       request: str,
       occasion: Optional[str] = None,
       party_size: Optional[int] = None,
       time_of_day: Optional[str] = None
) -> Optional[Restaurant]:
   """为顾客获取个性化的餐厅推荐。"""
   try:
       # 获取顾客偏好
       preferences = CUSTOMERS.get(customer_name)
       if not preferences:
           print(f"未找到{customer_name}的偏好")
           return None
       # 创建推荐上下文
       context = RecommendationContext(
           preferences=preferences,
           customer_name=customer_name,
           current_request=request,
           current_occasion=occasion,
           party_size=party_size,
           time_of_day=time_of_day
       )
       # 获取推荐
       restaurant = await recommendation_agent.run(context)
       print(f"\n找到推荐: {restaurant.name}")
       print(f"菜系: {restaurant.cuisine}")
       print(f"价格范围: {restaurant.price_range}")
       print(f"位置: {restaurant.location}")
       print(f"特色菜: {', '.join(restaurant.specialties)}")
       print(f"平均评分: {restaurant.average_rating}/5")
       print(f"等待时间: {restaurant.typical_wait_time}")
       return restaurant
   except Exception as e:
       print(f"获取推荐时出错: {str(e)}")
       return None

这个推荐函数展示了几个复杂的功能:

  • 集成多个上下文因素(场合、用餐人数、时间)。
  • 错误处理并提供有用的消息。
  • 详细的输出格式,便于用户理解。
  • 异步操作以提高性能。

8. 运行效果

00:50:01.063 Starting in pydantic_restaurant_context.py

 Loaded saved context for Alice
Last updated: 2024-12-20T14:12:51.783619

 Loaded saved context for Bob
Last updated: 2024-12-20T14:10:52.003580

=== Restaurant Recommendation System with Context ===


 Alice's New Request:
Looking for lunch after previous visits to Bella Italia

 Initial Context for Alice:
Favorite cuisines: ['Italian', 'Thai']
Dietary restrictions: ['Vegetarian']
Price range: ['$', '$$']
Typical party size: 4

 Request Context:
Request: Want a light lunch, something different from Italian
Occasion: Lunch
Party size: 2
Time: Afternoon

 [Recommendation Agent] Finding the perfect restaurant for Alice...
00:50:01.446 recommendation_agent run prompt=Want a light lunch, something different from Italian
00:50:01.447   preparing model and tools run_step=1
00:50:01.448   model request
Logfire project URL: https://logfire.pydantic.dev/cnndabbler/pydantic-agent-20dec2024
00:50:19.974   handle model response

 Found recommendation: Thai Spice
Cuisine: Thai
Price Range: $$
Location: Hayes Valley
Specialties: Pad Thai, Green Curry, Mango Sticky Rice
Average Rating: 4.4/5
Wait Time: 15-20 minutes
Dietary Options: Vegetarian-friendly, Gluten-free options

 Saved context for Alice to customer_contexts/alice_context.json

 Updated Alice's preferences based on experience at Thai Spice
Added to favorite restaurants!

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

 Bob's New Request:
Looking for Japanese after previous steakhouse experience

 Initial Context for Bob:
Favorite cuisines: ['Japanese', 'Steakhouse']
Dietary restrictions: ['Gluten-free']
Price range: ['$$$', '$$$$']
Typical party size: 2

 Request Context:
Request: Want to try some high-end Japanese food
Occasion: Date night
Party size: 2
Time: Evening

 [Recommendation Agent] Finding the perfect restaurant for Bob...
00:50:19.977 recommendation_agent run prompt=Want to try some high-end Japanese food
00:50:19.978   preparing model and tools run_step=1
00:50:19.979   model request
00:50:24.081   handle model response

=== Complete Context Journey ===

 Context Journey for Alice
==================================================

 Visit #1: Bella Italia
+ Added to favorite_restaurants: ['Bella Italia']

Current preferences after visit:
- Favorite cuisines: ['Italian', 'Thai']
- Favorite restaurants: ['Bella Italia']
- Typical party size: 4
--------------------------------------------------

 Visit #2: Thai Spice
~ Changed typical_party_size: 4 → 3
+ Added to favorite_restaurants: ['Thai Spice']
+ Added to usual_occasions: ['Lunch']

Current preferences after visit:
- Favorite cuisines: ['Italian', 'Thai']
- Favorite restaurants: ['Bella Italia', 'Thai Spice']
- Typical party size: 3
--------------------------------------------------

 Visit #3: Thai Spice
~ Changed typical_party_size: 3 → 2

Current preferences after visit:
- Favorite cuisines: ['Italian', 'Thai']
- Favorite restaurants: ['Bella Italia', 'Thai Spice']
- Typical party size: 2
--------------------------------------------------



 Context Journey for Bob
==================================================

 Visit #1: The Prime Cut
+ Added to favorite_restaurants: ['The Prime Cut']

Current preferences after visit:
- Favorite cuisines: ['Japanese', 'Steakhouse']
- Favorite restaurants: ['The Prime Cut']
- Typical party size: 2
--------------------------------------------------

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

Demo completed! Showed how context persists and improves over time:
1. Loaded previous dining experiences
2. Made recommendations considering past visits
3. Updated preferences with new experiences
4. Maintained complete dining history
5. Evolved cuisine preferences

 Context files are saved in: /mnt/NVME_1T_2/containers/agentic/customer_contexts
You can find the following files:
- bob_context.json
- alice_context.json

9. 其他应用领域

本文的这种模式适用于许多复杂的人工智能应用:

1. 教育系统

  • 跟踪个人学习进度。
  • 根据学生需求调整教学风格。
  • 识别并解决知识差距。
  • 个性化课程进度。

2. 智能家居系统

  • 学习家庭模式。
  • 根据上下文预测需求。
  • 适应季节变化。
  • 优化能源使用。

3. 内容推荐

  • 跟踪内容交互模式。
  • 理解上下文相关偏好。
  • 适应不断变化的兴趣。
  • 平衡探索与利用。

10. 小结

结合 Pydantic Agents 和复杂的上下文管理为构建智能、自适应系统创建了一个强大的框架。关键见解如下:

  1. 类型安全和验证:强类型确保系统可靠性,并在错误发生之前防止错误。
  2. 丰富的上下文管理:维护详细的上下文使系统能够根据历史模式和当前需求做出明智的决策。
  3. 从交互中学习:系统通过仔细跟踪和分析每个用户交互随时间不断改进。
  4. 灵活的架构:这里展示的设计模式可以适应各种领域和用例。

人工智能系统的未来不仅在于强大的模型,还在于它们维护上下文、从交互中学习并适应个体用户的能力。通过结合强类型、丰富的上下文管理和复杂的学习机制,我们可以创建真正理解并满足用户需求的系统。

github:https://gist.github.com/cnndabbler/0bbca28dcd7de2e97060c7ac90a67126