diff --git a/docs/api/toolsets.md b/docs/api/toolsets.md index 6b22b23a9f..2492aaf74f 100644 --- a/docs/api/toolsets.md +++ b/docs/api/toolsets.md @@ -9,6 +9,7 @@ - ApprovalRequiredToolset - FilteredToolset - FunctionToolset + - Mem0Toolset - PrefixedToolset - RenamedToolset - PreparedToolset diff --git a/docs/examples/mem0-toolset.md b/docs/examples/mem0-toolset.md new file mode 100644 index 0000000000..1a00af3c82 --- /dev/null +++ b/docs/examples/mem0-toolset.md @@ -0,0 +1,67 @@ +# Mem0 Toolset Memory Integration + +Example demonstrating how to use the [`Mem0Toolset`][pydantic_ai.toolsets.Mem0Toolset] to add memory capabilities to your Pydantic AI agents. + +Demonstrates: + +* [Using third-party toolsets](../mem0.md) +* [Memory save and search operations](../mem0.md#memory-tools) +* [Multi-user memory isolation](../mem0.md#multi-user-isolation) +* [Different deps patterns for user identification](../mem0.md#user-identification) + +This example shows how to integrate [Mem0](https://mem0.ai) memory capabilities into your agents using a simple toolset approach, allowing agents to remember and recall information across conversations. + +## Running the Example + +With [dependencies installed and environment variables set](./setup.md#usage), run: + +```bash +pip install pydantic-ai mem0ai +export MEM0_API_KEY=your-mem0-api-key +export OPENAI_API_KEY=your-openai-api-key +python/uv-run -m pydantic_ai_examples.mem0_toolset +``` + +## Example Code + +The example demonstrates several use cases: + +1. **Basic Memory Usage**: Saving and searching memories +2. **Multi-User Isolation**: Separate memories for different users +3. **Dataclass Deps**: Using structured dependency objects +4. **Personalized Assistant**: Remembering user preferences + +```snippet {path="/examples/pydantic_ai_examples/mem0_toolset.py"}``` + +## Key Features + +### Simple Integration + +The [`Mem0Toolset`][pydantic_ai.toolsets.Mem0Toolset] provides an easy way to add memory to any agent: + +```python +from pydantic_ai import Agent, Mem0Toolset + +mem0_toolset = Mem0Toolset(api_key='your-api-key') +agent = Agent('openai:gpt-4o', toolsets=[mem0_toolset]) +``` + +### Automatic Memory Management + +The agent automatically decides when to save or search memories based on the conversation context. Users don't need to explicitly call memory functions. + +### User Isolation + +Memories are automatically scoped to individual users through the `deps` parameter, ensuring privacy and personalization: + +```python +# Alice's memories +await agent.run('My favorite color is blue', deps='user_alice') + +# Bob's memories (completely separate) +await agent.run('My favorite color is red', deps='user_bob') +``` + +## Learn More + +For detailed documentation on the Mem0 integration, see the [Mem0 Memory Integration](../mem0.md) guide. diff --git a/docs/mem0.md b/docs/mem0.md new file mode 100644 index 0000000000..da63ab7d80 --- /dev/null +++ b/docs/mem0.md @@ -0,0 +1,350 @@ +# Mem0 Memory Integration + +PydanticAI provides a lightweight integration with [Mem0](https://mem0.ai) through the [`Mem0Toolset`][pydantic_ai.toolsets.Mem0Toolset]. This toolset adds memory capabilities to your agents, allowing them to save and search through conversation memories. + +## Overview + +The [`Mem0Toolset`][pydantic_ai.toolsets.Mem0Toolset] is a simple toolset that provides two memory tools: + +- **`_search_memory_impl`**: Search through stored memories +- **`_save_memory_impl`**: Save information to memory + +This integration follows the same pattern as other third-party integrations like [LangChain tools](third-party-tools.md#langchain-tools). + +## Installation + +To use the Mem0Toolset, install PydanticAI with the `mem0` extra: + +```bash +pip install 'pydantic-ai[mem0]' +``` + +Or install Mem0 separately: + +```bash +pip install mem0ai +``` + +## Quick Start + +Here's a simple example of using the Mem0Toolset: + +```python test="skip" +import os + +from pydantic_ai import Agent, Mem0Toolset + +# Create Mem0 toolset +mem0_toolset = Mem0Toolset(api_key=os.getenv('MEM0_API_KEY')) + +# Create agent with memory capabilities +agent = Agent( + 'openai:gpt-4o', + toolsets=[mem0_toolset], + instructions='You are a helpful assistant with memory capabilities.', +) + + +async def main(): + # Use the agent - it can now save and search memories + result = await agent.run( + 'My name is Alice and I love Python. Please remember this.', + deps='user_alice', + ) + print(result.output) +``` + +## How It Works + +### User Identification + +The [`Mem0Toolset`][pydantic_ai.toolsets.Mem0Toolset] requires a user identifier to scope memories. It extracts the `user_id` from the agent's `deps` in three ways: + +1. **String deps**: If `deps` is a string, it's used directly as the `user_id` + ```python test="skip" + import os + + from pydantic_ai import Agent, Mem0Toolset + + mem0_toolset = Mem0Toolset(api_key=os.getenv('MEM0_API_KEY')) + agent = Agent('openai:gpt-4o', toolsets=[mem0_toolset]) + + + async def example(): + await agent.run('Remember this', deps='user_123') + ``` + +2. **Object with `user_id` attribute**: If `deps` has a `user_id` attribute + ```python test="skip" + import os + from dataclasses import dataclass + + from pydantic_ai import Agent, Mem0Toolset + + + @dataclass + class UserSession: + user_id: str + session_id: str + + + mem0_toolset = Mem0Toolset(api_key=os.getenv('MEM0_API_KEY')) + agent = Agent('openai:gpt-4o', toolsets=[mem0_toolset]) + + + async def example(): + await agent.run( + 'Remember this', deps=UserSession(user_id='user_123', session_id='session_1') + ) + ``` + +3. **Object with `get_user_id()` method**: If `deps` has a `get_user_id()` method + ```python test="skip" + import os + + from pydantic_ai import Agent, Mem0Toolset + + + class UserContext: + def get_user_id(self) -> str: + return 'user_123' + + + mem0_toolset = Mem0Toolset(api_key=os.getenv('MEM0_API_KEY')) + agent = Agent('openai:gpt-4o', toolsets=[mem0_toolset]) + + + async def example(): + await agent.run('Remember this', deps=UserContext()) + ``` + +### Memory Tools + +The agent automatically uses the memory tools when appropriate: + +```python test="skip" +import os + +from pydantic_ai import Agent, Mem0Toolset + +mem0_toolset = Mem0Toolset(api_key=os.getenv('MEM0_API_KEY')) +agent = Agent('openai:gpt-4o', toolsets=[mem0_toolset]) + + +async def example(): + # Agent can save memories + await agent.run( + 'I prefer concise responses and dark mode.', + deps='user_alice', + ) + + # Agent can search memories + await agent.run( + 'What are my preferences?', + deps='user_alice', + ) +``` + +## Configuration + +### API Key + +Provide your Mem0 API key in one of two ways: + +1. **Environment variable** (recommended): + ```bash + export MEM0_API_KEY=your-api-key + ``` + +2. **Constructor parameter**: + ```python test="skip" + from pydantic_ai.toolsets import Mem0Toolset + + mem0_toolset = Mem0Toolset(api_key='your-api-key') + ``` + +### Custom Configuration + +You can customize the toolset behavior: + +```python test="skip" +import os + +from pydantic_ai.toolsets import Mem0Toolset + +mem0_toolset = Mem0Toolset( + api_key=os.getenv('MEM0_API_KEY'), + limit=10, # Return top 10 memories (default: 5) + host='https://api.mem0.ai', # Custom host + org_id='your-org-id', # Organization ID + project_id='your-project-id', # Project ID + id='my-mem0-toolset', # Custom toolset ID +) +``` + +### Using a Custom Client + +You can also provide a pre-configured Mem0 client: + +```python test="skip" +import os + +from mem0 import AsyncMemoryClient + +from pydantic_ai.toolsets import Mem0Toolset + +client = AsyncMemoryClient(api_key=os.getenv('MEM0_API_KEY')) +mem0_toolset = Mem0Toolset(client=client) +``` + +## Examples + +### Basic Memory Usage + +```python test="skip" +import os + +from pydantic_ai import Agent, Mem0Toolset + +mem0_toolset = Mem0Toolset(api_key=os.getenv('MEM0_API_KEY')) +agent = Agent('openai:gpt-4o', toolsets=[mem0_toolset]) + + +async def main(): + # Save information + await agent.run( + 'My name is Alice and I love Python.', + deps='user_alice', + ) + + # Recall information + await agent.run( + 'What programming language do I like?', + deps='user_alice', + ) +``` + +### Multi-User Isolation + +Memories are automatically isolated per user: + +```python test="skip" +import os + +from pydantic_ai import Agent, Mem0Toolset + +mem0_toolset = Mem0Toolset(api_key=os.getenv('MEM0_API_KEY')) +agent = Agent('openai:gpt-4o', toolsets=[mem0_toolset]) + + +async def main(): + # Alice's memories + await agent.run('My favorite color is blue.', deps='user_alice') + + # Bob's memories + await agent.run('My favorite color is red.', deps='user_bob') + + # Each user gets their own memories back + await agent.run('What is my favorite color?', deps='user_alice') + # Output: "Your favorite color is blue." + + await agent.run('What is my favorite color?', deps='user_bob') + # Output: "Your favorite color is red." +``` + +### Using with Dataclass Deps + +```python test="skip" +import os +from dataclasses import dataclass + +from pydantic_ai import Agent, Mem0Toolset + + +@dataclass +class UserSession: + user_id: str + session_id: str + + +mem0_toolset = Mem0Toolset(api_key=os.getenv('MEM0_API_KEY')) +agent = Agent('openai:gpt-4o', toolsets=[mem0_toolset]) + + +async def main(): + session = UserSession(user_id='user_charlie', session_id='session_123') + + await agent.run( + 'I work as a data scientist.', + deps=session, + ) +``` + +### Personalized Assistant + +```python test="skip" +import os + +from pydantic_ai import Agent, Mem0Toolset + +mem0_toolset = Mem0Toolset(api_key=os.getenv('MEM0_API_KEY')) +agent = Agent( + 'openai:gpt-4o', + toolsets=[mem0_toolset], + instructions=( + 'You are a helpful assistant with memory. ' + 'Remember user preferences and provide personalized assistance.' + ), +) + + +async def main(): + # First conversation - learn preferences + await agent.run( + 'I prefer concise responses with Python code examples.', + deps='user_diana', + ) + + # Later conversation - agent recalls preferences + result = await agent.run( + 'How do I read a CSV file?', + deps='user_diana', + ) + print(result.output) + # Agent will provide concise response with Python examples +``` + +## Comparison with Other Approaches + +The [`Mem0Toolset`][pydantic_ai.toolsets.Mem0Toolset] is designed to be lightweight and follow the same pattern as other third-party tool integrations like LangChain tools. + +### Similar to LangChain Tools + +Just like you can use [LangChain tools](third-party-tools.md#langchain-tools) with PydanticAI: + +```python test="skip" +from langchain_community.tools import WikipediaQueryRun + +from pydantic_ai import Agent +from pydantic_ai.ext.langchain import LangChainToolset + +toolset = LangChainToolset([WikipediaQueryRun()]) +agent = Agent('openai:gpt-4o', toolsets=[toolset]) +``` + +You can use Mem0 tools the same way: + +```python test="skip" +from pydantic_ai import Agent, Mem0Toolset + +toolset = Mem0Toolset(api_key='your-api-key') +agent = Agent('openai:gpt-4o', toolsets=[toolset]) +``` + +## API Reference + +For detailed API documentation, see [`Mem0Toolset`][pydantic_ai.toolsets.Mem0Toolset]. + +## Complete Example + +See the [complete example](examples/mem0-toolset.md) for a full demonstration of the Mem0Toolset capabilities. diff --git a/examples/pydantic_ai_examples/mem0_toolset.py b/examples/pydantic_ai_examples/mem0_toolset.py new file mode 100644 index 0000000000..da626d7fcb --- /dev/null +++ b/examples/pydantic_ai_examples/mem0_toolset.py @@ -0,0 +1,187 @@ +"""Example demonstrating Mem0Toolset for memory capabilities. + +This example shows how to use Mem0Toolset to add memory capabilities +to your Pydantic AI agents using a simple toolset approach. + +Install requirements: + pip install pydantic-ai mem0ai + +Set environment variables: + export MEM0_API_KEY=your-mem0-api-key + export OPENAI_API_KEY=your-openai-api-key +""" + +# pyright: reportArgumentType=false, reportAssignmentType=false, reportOptionalMemberAccess=false + +import asyncio +import os + +from pydantic_ai import Agent, Mem0Toolset + +# Initialize agent and toolset as None - will be created in main() +agent = None +mem0_toolset = None + + +def create_agent_with_memory(): + """Create agent with Mem0 toolset. Requires MEM0_API_KEY to be set.""" + # Create Mem0 toolset + # The toolset provides two tools: _search_memory_impl and _save_memory_impl + toolset = Mem0Toolset( + api_key=os.getenv('MEM0_API_KEY'), + limit=5, # Return top 5 memories by default + ) + + # Create agent with Mem0 toolset + # The agent can now automatically use memory tools + return Agent( + 'openai:gpt-4o', + toolsets=[toolset], + instructions=( + 'You are a helpful assistant with memory capabilities. ' + 'You can save important information to memory and search through memories. ' + 'Use these tools to remember user preferences and provide personalized assistance.' + ), + ) + + +async def example_basic_memory(): + """Demonstrate basic memory save and search.""" + print('=== Basic Memory Example ===\n') + + # The agent can save information to memory + result1 = await agent.run( + 'My name is Alice and I love Python programming. Please remember this.', + deps='user_alice', + ) + print(f'Agent: {result1.output}\n') + + # Later, the agent can search and recall memories + result2 = await agent.run( + 'What do you know about my programming preferences?', + deps='user_alice', + ) + print(f'Agent: {result2.output}\n') + + +async def example_multi_user(): + """Demonstrate memory isolation between users.""" + print('=== Multi-User Memory Isolation ===\n') + + # User Alice + result_alice = await agent.run( + 'Please remember that my favorite color is blue.', + deps='user_alice', + ) + print(f"Alice's Agent: {result_alice.output}\n") + + # User Bob + result_bob = await agent.run( + 'Please remember that my favorite color is red.', + deps='user_bob', + ) + print(f"Bob's Agent: {result_bob.output}\n") + + # Check Alice's memory + result_alice_recall = await agent.run( + 'What is my favorite color?', + deps='user_alice', + ) + print('Alice asks: What is my favorite color?') + print(f'Agent: {result_alice_recall.output}\n') + + # Check Bob's memory + result_bob_recall = await agent.run( + 'What is my favorite color?', + deps='user_bob', + ) + print('Bob asks: What is my favorite color?') + print(f'Agent: {result_bob_recall.output}\n') + + +async def example_with_dataclass(): + """Demonstrate using a dataclass for deps with user_id.""" + from dataclasses import dataclass + + @dataclass + class UserSession: + user_id: str + session_id: str + + print('=== Using Dataclass Deps ===\n') + + session = UserSession(user_id='user_charlie', session_id='session_123') + + result = await agent.run( + 'I work as a data scientist and prefer TypeScript for web development.', + deps=session, + ) + print(f'Agent: {result.output}\n') + + result2 = await agent.run( + 'What do you know about my profession and programming preferences?', + deps=session, + ) + print(f'Agent: {result2.output}\n') + + +async def example_personalized_assistant(): + """Demonstrate a personalized assistant with memory.""" + print('=== Personalized Assistant ===\n') + + user_id = 'user_diana' + + # First conversation - learning preferences + result1 = await agent.run( + 'I prefer concise responses and always want code examples in Python.', + deps=user_id, + ) + print('User: I prefer concise responses and always want code examples in Python.') + print(f'Agent: {result1.output}\n') + + # Later conversation - agent recalls preferences + result2 = await agent.run( + 'Can you explain how to read a CSV file?', + deps=user_id, + ) + print('User: Can you explain how to read a CSV file?') + print(f'Agent: {result2.output}\n') + + +async def main(): + """Run all examples.""" + global agent + + if not os.getenv('MEM0_API_KEY'): + print('Error: MEM0_API_KEY environment variable not set') + print('Get your API key at: https://app.mem0.ai') + return + + if not os.getenv('OPENAI_API_KEY'): + print('Error: OPENAI_API_KEY environment variable not set') + return + + # Create agent with memory capabilities + agent = create_agent_with_memory() + + print('🧠 Mem0Toolset Examples for Pydantic AI\n') + print('=' * 60) + print() + + await example_basic_memory() + + print('\n' + '=' * 60 + '\n') + await example_multi_user() + + print('\n' + '=' * 60 + '\n') + await example_with_dataclass() + + print('\n' + '=' * 60 + '\n') + await example_personalized_assistant() + + print('\n' + '=' * 60) + print('\n✅ All examples completed successfully!') + + +if __name__ == '__main__': + asyncio.run(main()) diff --git a/mkdocs.yml b/mkdocs.yml index 7e6b9aed4e..3aee32499d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -46,6 +46,7 @@ nav: - input.md - thinking.md - retries.md + - mem0.md - MCP: - Overview: mcp/overview.md - mcp/client.md @@ -90,6 +91,8 @@ nav: - Complex Workflows: - examples/flight-booking.md - examples/question-graph.md + - Memory & Integrations: + - examples/mem0-toolset.md - Business Applications: - examples/slack-lead-qualifier.md - UI Examples: @@ -307,6 +310,7 @@ plugins: - output.md - retries.md - message-history.md + - memory.md - multi-agent-applications.md - thinking.md - third-party-tools.md diff --git a/pydantic_ai_slim/pydantic_ai/__init__.py b/pydantic_ai_slim/pydantic_ai/__init__.py index 8f6254f425..c7cc50dedb 100644 --- a/pydantic_ai_slim/pydantic_ai/__init__.py +++ b/pydantic_ai_slim/pydantic_ai/__init__.py @@ -98,6 +98,7 @@ ExternalToolset, FilteredToolset, FunctionToolset, + Mem0Toolset, PrefixedToolset, PreparedToolset, RenamedToolset, @@ -198,6 +199,7 @@ 'ExternalToolset', 'FilteredToolset', 'FunctionToolset', + 'Mem0Toolset', 'PrefixedToolset', 'PreparedToolset', 'RenamedToolset', diff --git a/pydantic_ai_slim/pydantic_ai/toolsets/__init__.py b/pydantic_ai_slim/pydantic_ai/toolsets/__init__.py index a5228ca91a..7f39aedc18 100644 --- a/pydantic_ai_slim/pydantic_ai/toolsets/__init__.py +++ b/pydantic_ai_slim/pydantic_ai/toolsets/__init__.py @@ -5,6 +5,7 @@ from .external import DeferredToolset, ExternalToolset # pyright: ignore[reportDeprecated] from .filtered import FilteredToolset from .function import FunctionToolset +from .mem0 import Mem0Toolset from .prefixed import PrefixedToolset from .prepared import PreparedToolset from .renamed import RenamedToolset @@ -19,6 +20,7 @@ 'DeferredToolset', 'FilteredToolset', 'FunctionToolset', + 'Mem0Toolset', 'PrefixedToolset', 'RenamedToolset', 'PreparedToolset', diff --git a/pydantic_ai_slim/pydantic_ai/toolsets/mem0.py b/pydantic_ai_slim/pydantic_ai/toolsets/mem0.py new file mode 100644 index 0000000000..d24dc9bd4a --- /dev/null +++ b/pydantic_ai_slim/pydantic_ai/toolsets/mem0.py @@ -0,0 +1,220 @@ +"""Mem0 memory toolset for Pydantic AI. + +This toolset provides memory capabilities using the Mem0 platform, +allowing agents to save and search through conversation memories. +""" + +# pyright: reportUnknownMemberType=false, reportUnknownArgumentType=false, reportUnknownVariableType=false + +from __future__ import annotations as _annotations + +from typing import TYPE_CHECKING, Any + +from .._run_context import AgentDepsT, RunContext +from .function import FunctionToolset + +if TYPE_CHECKING: + from mem0 import AsyncMemoryClient, MemoryClient + +try: + from mem0 import AsyncMemoryClient, MemoryClient +except ImportError as _e: # pragma: no cover + _import_error = _e # pragma: no cover + AsyncMemoryClient = None # pragma: no cover + MemoryClient = None # pragma: no cover +else: + _import_error = None + +__all__ = ('Mem0Toolset',) + + +class Mem0Toolset(FunctionToolset[AgentDepsT]): + """A toolset that provides Mem0 memory capabilities to agents. + + This toolset adds two tools to your agent: + - `save_memory`: Save information to memory for later retrieval + - `search_memory`: Search through stored memories + + Example: + ```python test="skip" + from pydantic_ai import Agent + from pydantic_ai.toolsets import Mem0Toolset + + # Create toolset with Mem0 API key + mem0_toolset = Mem0Toolset(api_key='your-mem0-api-key') + + # Add to agent + agent = Agent('openai:gpt-4o', toolsets=[mem0_toolset]) + + async def main(): + # The agent can now use memory tools automatically + await agent.run( + 'Remember that my favorite color is blue', + deps='user_123' + ) + ``` + + The toolset expects the agent's `deps` to be either: + - A string representing the user_id + - An object with a `user_id` attribute + - An object with a `get_user_id()` method + + Attributes: + client: The Mem0 client instance (sync or async). + """ + + def __init__( + self, + *, + api_key: str | None = None, + host: str | None = None, + org_id: str | None = None, + project_id: str | None = None, + client: AsyncMemoryClient | MemoryClient | None = None, # type: ignore[valid-type] + id: str | None = None, + limit: int = 5, + ): + """Initialize the Mem0 toolset. + + Args: + api_key: Mem0 API key. If not provided, will look for MEM0_API_KEY env var. + host: Mem0 API host. Defaults to https://api.mem0.ai + org_id: Organization ID for Mem0 platform. + project_id: Project ID for Mem0 platform. + client: Optional pre-configured Mem0 client (sync or async). + id: Optional unique ID for the toolset. + limit: Default number of memories to retrieve in search. Defaults to 5. + + Raises: + ImportError: If mem0 package is not installed. + """ + if _import_error is not None: + raise ImportError( + 'mem0 is not installed. Install it with: pip install mem0ai\n' + 'Or install pydantic-ai with mem0 support: pip install pydantic-ai[mem0]' + ) from _import_error + + if client is not None: + self.client = client + # Check if client is async by looking for async methods + self._is_async = hasattr(client, 'search') and hasattr(getattr(client, 'search'), '__call__') + if self._is_async: + import inspect + + self._is_async = inspect.iscoroutinefunction(client.search) + else: + # Create async client by default for better performance + self.client = AsyncMemoryClient( # type: ignore[misc] + api_key=api_key, + host=host, + org_id=org_id, + project_id=project_id, + ) + self._is_async = True + + self._limit = limit + + # Initialize parent FunctionToolset + super().__init__(id=id) + + # Register memory tools + self.tool(self._search_memory_impl) + self.tool(self._save_memory_impl) + + def _extract_user_id(self, deps: Any) -> str: + """Extract user_id from deps. + + Args: + deps: The agent dependencies. + + Returns: + The user_id as a string. + + Raises: + ValueError: If user_id cannot be extracted. + """ + if isinstance(deps, str): + return deps + elif hasattr(deps, 'user_id'): + user_id = deps.user_id + if isinstance(user_id, str): + return user_id + raise ValueError(f'deps.user_id must be a string, got {type(user_id).__name__}') + elif hasattr(deps, 'get_user_id'): + user_id = deps.get_user_id() + if isinstance(user_id, str): + return user_id + raise ValueError(f'deps.get_user_id() must return a string, got {type(user_id).__name__}') + else: + raise ValueError( + 'Cannot extract user_id from deps. ' + 'Deps must be a string, have a user_id attribute, or have a get_user_id() method. ' + f'Got {type(deps).__name__}' + ) + + async def _search_memory_impl(self, ctx: RunContext[AgentDepsT], query: str) -> str: + """Search through stored memories. + + Args: + ctx: The run context containing user information. + query: The search query to find relevant memories. + + Returns: + A formatted string of relevant memories or a message if none found. + """ + user_id = self._extract_user_id(ctx.deps) + + try: + if self._is_async: + response = await self.client.search(query=query, user_id=user_id, limit=self._limit) + else: + response = self.client.search(query=query, user_id=user_id, limit=self._limit) + + # Parse response - handle both dict format and raw list + if isinstance(response, dict): + results = response.get('results', []) + elif isinstance(response, list): + results = response + else: + return 'Error: Unexpected response format from Mem0' + + if not results: + return 'No relevant memories found.' + + # Format memories for the agent + memory_lines = ['Found relevant memories:'] + for mem in results: + if isinstance(mem, dict): + memory_text = mem.get('memory', '') + score = mem.get('score', 0) + memory_lines.append(f'- {memory_text} (relevance: {score:.2f})') + + return '\n'.join(memory_lines) + + except Exception as e: + return f'Error searching memories: {str(e)}' + + async def _save_memory_impl(self, ctx: RunContext[AgentDepsT], content: str) -> str: + """Save information to memory. + + Args: + ctx: The run context containing user information. + content: The content to save as a memory. + + Returns: + A confirmation message. + """ + user_id = self._extract_user_id(ctx.deps) + + try: + messages = [{'role': 'user', 'content': content}] + + if self._is_async: + await self.client.add(messages=messages, user_id=user_id) + else: + self.client.add(messages=messages, user_id=user_id) + + return f'Successfully saved to memory: {content}' + + except Exception as e: + return f'Error saving to memory: {str(e)}' diff --git a/pydantic_ai_slim/pyproject.toml b/pydantic_ai_slim/pyproject.toml index 12c5ad9c75..216f05d608 100644 --- a/pydantic_ai_slim/pyproject.toml +++ b/pydantic_ai_slim/pyproject.toml @@ -100,6 +100,8 @@ retries = ["tenacity>=8.2.3"] temporal = ["temporalio==1.18.0"] # DBOS dbos = ["dbos>=1.14.0"] +# Memory providers +mem0 = ["mem0ai>=0.1.118"] # Prefect prefect = ["prefect>=3.4.21"] diff --git a/pyproject.toml b/pyproject.toml index b09a172045..6ef4b304f4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,7 +46,7 @@ requires-python = ">=3.10" [tool.hatch.metadata.hooks.uv-dynamic-versioning] dependencies = [ - "pydantic-ai-slim[openai,vertexai,google,groq,anthropic,mistral,cohere,bedrock,huggingface,cli,mcp,evals,ag-ui,retries,temporal,logfire]=={{ version }}", + "pydantic-ai-slim[openai,vertexai,google,groq,anthropic,mistral,cohere,bedrock,huggingface,cli,mcp,evals,ag-ui,retries,temporal,logfire,mem0]=={{ version }}", ] [tool.hatch.metadata.hooks.uv-dynamic-versioning.optional-dependencies] @@ -105,6 +105,7 @@ dev = [ "pip>=25.2", "genai-prices>=0.0.28", "mcp-run-python>=0.0.20", + "mem0ai>=0.1.118", ] lint = ["mypy>=1.11.2", "pyright>=1.1.390", "ruff>=0.6.9"] docs = [ diff --git a/tests/test_examples.py b/tests/test_examples.py index 87649e44b3..460ed801f9 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -184,6 +184,7 @@ def print(self, *args: Any, **kwargs: Any) -> None: env.set('GROK_API_KEY', 'testing') env.set('MOONSHOTAI_API_KEY', 'testing') env.set('DEEPSEEK_API_KEY', 'testing') + env.set('MEM0_API_KEY', 'testing') prefix_settings = example.prefix_settings() opt_test = prefix_settings.get('test', '') diff --git a/tests/test_mem0_toolset.py b/tests/test_mem0_toolset.py new file mode 100644 index 0000000000..b60d6cbc71 --- /dev/null +++ b/tests/test_mem0_toolset.py @@ -0,0 +1,465 @@ +"""Tests for Mem0Toolset integration.""" + +# pyright: reportPrivateUsage=false, reportUnknownMemberType=false + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any +from unittest.mock import AsyncMock, MagicMock + +import pytest + +from pydantic_ai import Agent, Mem0Toolset +from pydantic_ai._run_context import RunContext +from pydantic_ai._tool_manager import ToolManager +from pydantic_ai.models.test import TestModel +from pydantic_ai.tools import ToolDefinition +from pydantic_ai.toolsets.mem0 import _import_error +from pydantic_ai.usage import RunUsage + +pytestmark = pytest.mark.anyio + + +def build_run_context(deps: Any, run_step: int = 0) -> RunContext[Any]: + """Helper to build a RunContext for testing.""" + return RunContext( + deps=deps, + model=TestModel(), + usage=RunUsage(), + prompt=None, + messages=[], + run_step=run_step, + ) + + +def test_mem0_import_error(): + """Test that Mem0Toolset raises ImportError if mem0 is not installed.""" + if _import_error is None: + pytest.skip('mem0 is installed, skipping import error test') + + with pytest.raises(ImportError, match='mem0 is not installed'): # pragma: no cover + Mem0Toolset(api_key='test-key') # pragma: no cover + + +def test_mem0_import_error_mocked(): + """Test ImportError handling by mocking the import error.""" + from unittest.mock import patch + + # Mock the import error scenario + with patch('pydantic_ai.toolsets.mem0._import_error', new=ImportError('Mocked import error')): + with pytest.raises(ImportError, match='mem0 is not installed'): + Mem0Toolset(api_key='test-key') + + +@pytest.mark.skipif(_import_error is not None, reason='mem0 is not installed') +async def test_mem0_toolset_initialization(): + """Test Mem0Toolset initialization.""" + # Create mock client with async search method + mock_client = AsyncMock() + mock_client.search = AsyncMock() + + # Initialize toolset with mock client + toolset = Mem0Toolset(client=mock_client) + + assert toolset.client is mock_client + assert toolset._is_async is True + + +@pytest.mark.skipif(_import_error is not None, reason='mem0 is not installed') +async def test_mem0_toolset_initialization_without_client(): + """Test Mem0Toolset initialization without providing a client.""" + from unittest.mock import patch + + # Mock the AsyncMemoryClient to avoid needing real API key + with patch('pydantic_ai.toolsets.mem0.AsyncMemoryClient') as mock_client_class: + mock_client = AsyncMock() + mock_client_class.return_value = mock_client + + # Initialize toolset without client (will create AsyncMemoryClient) + toolset = Mem0Toolset( + api_key='test-key', + host='https://test.mem0.ai', + org_id='test-org', + project_id='test-project', + ) + + # Verify AsyncMemoryClient was called with correct params + mock_client_class.assert_called_once_with( + api_key='test-key', + host='https://test.mem0.ai', + org_id='test-org', + project_id='test-project', + ) + + assert toolset.client is mock_client + assert toolset._is_async is True + + +@pytest.mark.skipif(_import_error is not None, reason='mem0 is not installed') +async def test_mem0_toolset_tool_registration(): + """Test that Mem0Toolset registers the expected tools.""" + mock_client = AsyncMock() + toolset = Mem0Toolset(client=mock_client) + + # Get tools from the toolset + context = build_run_context('user_123') + tools = await toolset.get_tools(context) + + # Should have two tools + assert len(tools) == 2 + assert '_search_memory_impl' in tools + assert '_save_memory_impl' in tools + + # Check tool definitions + search_tool = tools['_search_memory_impl'] + save_tool = tools['_save_memory_impl'] + + assert isinstance(search_tool.tool_def, ToolDefinition) + assert isinstance(save_tool.tool_def, ToolDefinition) + + +@pytest.mark.skipif(_import_error is not None, reason='mem0 is not installed') +async def test_mem0_toolset_extract_user_id_string(): + """Test extracting user_id from string deps.""" + mock_client = AsyncMock() + toolset = Mem0Toolset(client=mock_client) + + user_id = toolset._extract_user_id('user_123') + assert user_id == 'user_123' + + +@pytest.mark.skipif(_import_error is not None, reason='mem0 is not installed') +async def test_mem0_toolset_extract_user_id_attribute(): + """Test extracting user_id from object with user_id attribute.""" + mock_client = AsyncMock() + toolset = Mem0Toolset(client=mock_client) + + @dataclass + class UserDeps: + user_id: str + + deps = UserDeps(user_id='user_456') + user_id = toolset._extract_user_id(deps) + assert user_id == 'user_456' + + +@pytest.mark.skipif(_import_error is not None, reason='mem0 is not installed') +async def test_mem0_toolset_extract_user_id_method(): + """Test extracting user_id from object with get_user_id method.""" + mock_client = AsyncMock() + toolset = Mem0Toolset(client=mock_client) + + class UserDeps: + def get_user_id(self) -> str: + return 'user_789' + + deps = UserDeps() + user_id = toolset._extract_user_id(deps) + assert user_id == 'user_789' + + +@pytest.mark.skipif(_import_error is not None, reason='mem0 is not installed') +async def test_mem0_toolset_extract_user_id_method_wrong_type(): + """Test that get_user_id() must return a string.""" + mock_client = AsyncMock() + toolset = Mem0Toolset(client=mock_client) + + class UserDeps: + def get_user_id(self) -> int: + return 123 + + deps = UserDeps() + with pytest.raises(ValueError, match='deps.get_user_id\\(\\) must return a string'): + toolset._extract_user_id(deps) + + +@pytest.mark.skipif(_import_error is not None, reason='mem0 is not installed') +async def test_mem0_toolset_extract_user_id_invalid(): + """Test that extracting user_id from invalid deps raises ValueError.""" + mock_client = AsyncMock() + toolset = Mem0Toolset(client=mock_client) + + with pytest.raises(ValueError, match='Cannot extract user_id from deps'): + toolset._extract_user_id(123) + + +@pytest.mark.skipif(_import_error is not None, reason='mem0 is not installed') +async def test_mem0_toolset_extract_user_id_wrong_type(): + """Test that user_id must be a string.""" + mock_client = AsyncMock() + toolset = Mem0Toolset(client=mock_client) + + @dataclass + class UserDeps: + user_id: int + + deps = UserDeps(user_id=123) + with pytest.raises(ValueError, match='deps.user_id must be a string'): + toolset._extract_user_id(deps) + + +@pytest.mark.skipif(_import_error is not None, reason='mem0 is not installed') +async def test_mem0_toolset_search_memory(): + """Test the search_memory tool.""" + mock_client = AsyncMock() + mock_client.search = AsyncMock( + return_value={ + 'results': [ + {'memory': 'User likes Python', 'score': 0.95}, + {'memory': 'User prefers dark mode', 'score': 0.88}, + ] + } + ) + + toolset = Mem0Toolset(client=mock_client) + context = build_run_context('user_123') + + result = await toolset._search_memory_impl(context, 'preferences') + + # Check that the search was called correctly + mock_client.search.assert_called_once_with(query='preferences', user_id='user_123', limit=5) + + # Check the formatted output + assert 'Found relevant memories:' in result + assert 'User likes Python (relevance: 0.95)' in result + assert 'User prefers dark mode (relevance: 0.88)' in result + + +@pytest.mark.skipif(_import_error is not None, reason='mem0 is not installed') +async def test_mem0_toolset_search_memory_no_results(): + """Test search_memory when no memories are found.""" + mock_client = AsyncMock() + mock_client.search = AsyncMock(return_value={'results': []}) + + toolset = Mem0Toolset(client=mock_client) + context = build_run_context('user_123') + + result = await toolset._search_memory_impl(context, 'nonexistent') + + assert result == 'No relevant memories found.' + + +@pytest.mark.skipif(_import_error is not None, reason='mem0 is not installed') +async def test_mem0_toolset_search_memory_error(): + """Test search_memory error handling.""" + mock_client = AsyncMock() + mock_client.search = AsyncMock(side_effect=Exception('API error')) + + toolset = Mem0Toolset(client=mock_client) + context = build_run_context('user_123') + + result = await toolset._search_memory_impl(context, 'query') + + assert 'Error searching memories: API error' in result + + +@pytest.mark.skipif(_import_error is not None, reason='mem0 is not installed') +async def test_mem0_toolset_search_memory_list_response(): + """Test search_memory with list response format.""" + mock_client = AsyncMock() + mock_client.search = AsyncMock( + return_value=[ + {'memory': 'Memory 1', 'score': 0.9}, + {'memory': 'Memory 2', 'score': 0.8}, + ] + ) + + toolset = Mem0Toolset(client=mock_client) + context = build_run_context('user_123') + + result = await toolset._search_memory_impl(context, 'test') + + assert 'Found relevant memories:' in result + assert 'Memory 1 (relevance: 0.90)' in result + assert 'Memory 2 (relevance: 0.80)' in result + + +@pytest.mark.skipif(_import_error is not None, reason='mem0 is not installed') +async def test_mem0_toolset_search_memory_unexpected_response(): + """Test search_memory with unexpected response format.""" + mock_client = AsyncMock() + mock_client.search = AsyncMock(return_value='unexpected string response') + + toolset = Mem0Toolset(client=mock_client) + context = build_run_context('user_123') + + result = await toolset._search_memory_impl(context, 'test') + + assert 'Error: Unexpected response format from Mem0' in result + + +@pytest.mark.skipif(_import_error is not None, reason='mem0 is not installed') +async def test_mem0_toolset_search_memory_with_non_dict_items(): + """Test search_memory with non-dict items in results.""" + mock_client = AsyncMock() + # Mix of dict and non-dict items + mock_client.search = AsyncMock( + return_value={ + 'results': [ + {'memory': 'Valid memory', 'score': 0.9}, + 'invalid_string_item', # This should be skipped + {'memory': 'Another valid memory', 'score': 0.8}, + None, # This should also be skipped + ] + } + ) + + toolset = Mem0Toolset(client=mock_client) + context = build_run_context('user_123') + + result = await toolset._search_memory_impl(context, 'test') + + # Only dict items should be included in the output + assert 'Found relevant memories:' in result + assert 'Valid memory (relevance: 0.90)' in result + assert 'Another valid memory (relevance: 0.80)' in result + assert 'invalid_string_item' not in result + + +@pytest.mark.skipif(_import_error is not None, reason='mem0 is not installed') +async def test_mem0_toolset_save_memory(): + """Test the save_memory tool.""" + mock_client = AsyncMock() + mock_client.add = AsyncMock(return_value=None) + + toolset = Mem0Toolset(client=mock_client) + context = build_run_context('user_123') + + result = await toolset._save_memory_impl(context, 'User loves Python') + + # Check that add was called correctly + mock_client.add.assert_called_once_with( + messages=[{'role': 'user', 'content': 'User loves Python'}], + user_id='user_123', + ) + + assert result == 'Successfully saved to memory: User loves Python' + + +@pytest.mark.skipif(_import_error is not None, reason='mem0 is not installed') +async def test_mem0_toolset_save_memory_error(): + """Test save_memory error handling.""" + mock_client = AsyncMock() + mock_client.add = AsyncMock(side_effect=Exception('Storage error')) + + toolset = Mem0Toolset(client=mock_client) + context = build_run_context('user_123') + + result = await toolset._save_memory_impl(context, 'test content') + + assert 'Error saving to memory: Storage error' in result + + +@pytest.mark.skipif(_import_error is not None, reason='mem0 is not installed') +async def test_mem0_toolset_with_agent(): + """Test Mem0Toolset integration with an Agent.""" + mock_client = AsyncMock() + mock_client.search = AsyncMock(return_value={'results': []}) + mock_client.add = AsyncMock(return_value=None) + + toolset: Mem0Toolset[str] = Mem0Toolset(client=mock_client) + agent = Agent('test', deps_type=str, toolsets=[toolset]) + + # Run agent and verify toolset is registered + result = await agent.run('test', deps='user_123') + + # The agent should have access to the memory tools + assert result is not None + + +@pytest.mark.skipif(_import_error is not None, reason='mem0 is not installed') +async def test_mem0_toolset_custom_limit(): + """Test Mem0Toolset with custom limit parameter.""" + mock_client = AsyncMock() + mock_client.search = AsyncMock(return_value={'results': []}) + + toolset = Mem0Toolset(client=mock_client, limit=10) + context = build_run_context('user_123') + + await toolset._search_memory_impl(context, 'test') + + # Should use custom limit + mock_client.search.assert_called_once_with(query='test', user_id='user_123', limit=10) + + +@pytest.mark.skipif(_import_error is not None, reason='mem0 is not installed') +async def test_mem0_toolset_sync_client(): + """Test Mem0Toolset with synchronous client.""" + mock_client = MagicMock() + mock_client.search.return_value = {'results': []} + mock_client.add.return_value = None + + toolset = Mem0Toolset(client=mock_client) + assert toolset._is_async is False + + context = build_run_context('user_123') + + # Search should still work with sync client + result = await toolset._search_memory_impl(context, 'test') + assert 'No relevant memories found.' in result + + # Save should still work with sync client + result = await toolset._save_memory_impl(context, 'test content') + assert 'Successfully saved to memory' in result + + +@pytest.mark.skipif(_import_error is not None, reason='mem0 is not installed') +async def test_mem0_toolset_client_without_search(): + """Test Mem0Toolset with a client that doesn't have search attribute.""" + + # Create a client without search attribute to test the _is_async=False path + class MinimalClient: + def add(self, messages: Any, user_id: Any) -> None: + pass + + mock_client = MinimalClient() + toolset = Mem0Toolset(client=mock_client) + + # Should detect it's not async because it doesn't have search method + assert toolset._is_async is False + + +@pytest.mark.skipif(_import_error is not None, reason='mem0 is not installed') +async def test_mem0_toolset_with_dataclass_deps(): + """Test Mem0Toolset with dataclass deps containing user_id.""" + + @dataclass + class UserSession: + user_id: str + session_id: str + + mock_client = AsyncMock() + mock_client.search = AsyncMock(return_value={'results': []}) + mock_client.add = AsyncMock(return_value=None) + + toolset = Mem0Toolset(client=mock_client) + context = build_run_context(UserSession(user_id='alice', session_id='session_1')) + + # Test search + await toolset._search_memory_impl(context, 'test') + mock_client.search.assert_called_once_with(query='test', user_id='alice', limit=5) + + # Test save + await toolset._save_memory_impl(context, 'content') + mock_client.add.assert_called_once_with(messages=[{'role': 'user', 'content': 'content'}], user_id='alice') + + +@pytest.mark.skipif(_import_error is not None, reason='mem0 is not installed') +async def test_mem0_toolset_tool_manager_integration(): + """Test that Mem0Toolset works correctly with ToolManager.""" + mock_client = AsyncMock() + mock_client.search = AsyncMock(return_value={'results': [{'memory': 'Test memory', 'score': 0.9}]}) + + toolset = Mem0Toolset(client=mock_client) + context = build_run_context('user_123') + + tool_manager = await ToolManager(toolset).for_run_step(context) + + # Verify tools are available + assert len(tool_manager.tool_defs) == 2 + + # Get tool names + tool_names = [td.name for td in tool_manager.tool_defs] + assert '_search_memory_impl' in tool_names + assert '_save_memory_impl' in tool_names diff --git a/uv.lock b/uv.lock index 486ea9bbd3..1b613a17d1 100644 --- a/uv.lock +++ b/uv.lock @@ -406,6 +406,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, ] +[[package]] +name = "backoff" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001, upload-time = "2022-10-05T19:19:32.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148, upload-time = "2022-10-05T19:19:30.546Z" }, +] + [[package]] name = "beautifulsoup4" version = "4.13.3" @@ -1733,6 +1742,67 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4d/11/1019a6cfdb2e520cb461cf70d859216be8ca122ddf5ad301fc3b0ee45fd4/groq-0.25.0-py3-none-any.whl", hash = "sha256:aadc78b40b1809cdb196b1aa8c7f7293108767df1508cafa3e0d5045d9328e7a", size = 129371, upload-time = "2025-05-16T19:57:41.786Z" }, ] +[[package]] +name = "grpcio" +version = "1.75.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/f7/8963848164c7604efb3a3e6ee457fdb3a469653e19002bd24742473254f8/grpcio-1.75.1.tar.gz", hash = "sha256:3e81d89ece99b9ace23a6916880baca613c03a799925afb2857887efa8b1b3d2", size = 12731327, upload-time = "2025-09-26T09:03:36.887Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/57/89fd829fb00a6d0bee3fbcb2c8a7aa0252d908949b6ab58bfae99d39d77e/grpcio-1.75.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:1712b5890b22547dd29f3215c5788d8fc759ce6dd0b85a6ba6e2731f2d04c088", size = 5705534, upload-time = "2025-09-26T09:00:52.225Z" }, + { url = "https://files.pythonhosted.org/packages/76/dd/2f8536e092551cf804e96bcda79ecfbc51560b214a0f5b7ebc253f0d4664/grpcio-1.75.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8d04e101bba4b55cea9954e4aa71c24153ba6182481b487ff376da28d4ba46cf", size = 11484103, upload-time = "2025-09-26T09:00:59.457Z" }, + { url = "https://files.pythonhosted.org/packages/9a/3d/affe2fb897804c98d56361138e73786af8f4dd876b9d9851cfe6342b53c8/grpcio-1.75.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:683cfc70be0c1383449097cba637317e4737a357cfc185d887fd984206380403", size = 6289953, upload-time = "2025-09-26T09:01:03.699Z" }, + { url = "https://files.pythonhosted.org/packages/87/aa/0f40b7f47a0ff10d7e482bc3af22dac767c7ff27205915f08962d5ca87a2/grpcio-1.75.1-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:491444c081a54dcd5e6ada57314321ae526377f498d4aa09d975c3241c5b9e1c", size = 6949785, upload-time = "2025-09-26T09:01:07.504Z" }, + { url = "https://files.pythonhosted.org/packages/a5/45/b04407e44050781821c84f26df71b3f7bc469923f92f9f8bc27f1406dbcc/grpcio-1.75.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ce08d4e112d0d38487c2b631ec8723deac9bc404e9c7b1011426af50a79999e4", size = 6465708, upload-time = "2025-09-26T09:01:11.028Z" }, + { url = "https://files.pythonhosted.org/packages/09/3e/4ae3ec0a4d20dcaafbb6e597defcde06399ccdc5b342f607323f3b47f0a3/grpcio-1.75.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5a2acda37fc926ccc4547977ac3e56b1df48fe200de968e8c8421f6e3093df6c", size = 7100912, upload-time = "2025-09-26T09:01:14.393Z" }, + { url = "https://files.pythonhosted.org/packages/34/3f/a9085dab5c313bb0cb853f222d095e2477b9b8490a03634cdd8d19daa5c3/grpcio-1.75.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:745c5fe6bf05df6a04bf2d11552c7d867a2690759e7ab6b05c318a772739bd75", size = 8042497, upload-time = "2025-09-26T09:01:17.759Z" }, + { url = "https://files.pythonhosted.org/packages/c3/87/ea54eba931ab9ed3f999ba95f5d8d01a20221b664725bab2fe93e3dee848/grpcio-1.75.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:259526a7159d39e2db40d566fe3e8f8e034d0fb2db5bf9c00e09aace655a4c2b", size = 7493284, upload-time = "2025-09-26T09:01:20.896Z" }, + { url = "https://files.pythonhosted.org/packages/b7/5e/287f1bf1a998f4ac46ef45d518de3b5da08b4e86c7cb5e1108cee30b0282/grpcio-1.75.1-cp310-cp310-win32.whl", hash = "sha256:f4b29b9aabe33fed5df0a85e5f13b09ff25e2c05bd5946d25270a8bd5682dac9", size = 3950809, upload-time = "2025-09-26T09:01:23.695Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a2/3cbfc06a4ec160dc77403b29ecb5cf76ae329eb63204fea6a7c715f1dfdb/grpcio-1.75.1-cp310-cp310-win_amd64.whl", hash = "sha256:cf2e760978dcce7ff7d465cbc7e276c3157eedc4c27aa6de7b594c7a295d3d61", size = 4644704, upload-time = "2025-09-26T09:01:25.763Z" }, + { url = "https://files.pythonhosted.org/packages/0c/3c/35ca9747473a306bfad0cee04504953f7098527cd112a4ab55c55af9e7bd/grpcio-1.75.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:573855ca2e58e35032aff30bfbd1ee103fbcf4472e4b28d4010757700918e326", size = 5709761, upload-time = "2025-09-26T09:01:28.528Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2c/ecbcb4241e4edbe85ac2663f885726fea0e947767401288b50d8fdcb9200/grpcio-1.75.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:6a4996a2c8accc37976dc142d5991adf60733e223e5c9a2219e157dc6a8fd3a2", size = 11496691, upload-time = "2025-09-26T09:01:31.214Z" }, + { url = "https://files.pythonhosted.org/packages/81/40/bc07aee2911f0d426fa53fe636216100c31a8ea65a400894f280274cb023/grpcio-1.75.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b1ea1bbe77ecbc1be00af2769f4ae4a88ce93be57a4f3eebd91087898ed749f9", size = 6296084, upload-time = "2025-09-26T09:01:34.596Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d1/10c067f6c67396cbf46448b80f27583b5e8c4b46cdfbe18a2a02c2c2f290/grpcio-1.75.1-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:e5b425aee54cc5e3e3c58f00731e8a33f5567965d478d516d35ef99fd648ab68", size = 6950403, upload-time = "2025-09-26T09:01:36.736Z" }, + { url = "https://files.pythonhosted.org/packages/3f/42/5f628abe360b84dfe8dd8f32be6b0606dc31dc04d3358eef27db791ea4d5/grpcio-1.75.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0049a7bf547dafaeeb1db17079ce79596c298bfe308fc084d023c8907a845b9a", size = 6470166, upload-time = "2025-09-26T09:01:39.474Z" }, + { url = "https://files.pythonhosted.org/packages/c3/93/a24035080251324019882ee2265cfde642d6476c0cf8eb207fc693fcebdc/grpcio-1.75.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b8ea230c7f77c0a1a3208a04a1eda164633fb0767b4cefd65a01079b65e5b1f", size = 7107828, upload-time = "2025-09-26T09:01:41.782Z" }, + { url = "https://files.pythonhosted.org/packages/e4/f8/d18b984c1c9ba0318e3628dbbeb6af77a5007f02abc378c845070f2d3edd/grpcio-1.75.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:36990d629c3c9fb41e546414e5af52d0a7af37ce7113d9682c46d7e2919e4cca", size = 8045421, upload-time = "2025-09-26T09:01:45.835Z" }, + { url = "https://files.pythonhosted.org/packages/7e/b6/4bf9aacff45deca5eac5562547ed212556b831064da77971a4e632917da3/grpcio-1.75.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b10ad908118d38c2453ade7ff790e5bce36580c3742919007a2a78e3a1e521ca", size = 7503290, upload-time = "2025-09-26T09:01:49.28Z" }, + { url = "https://files.pythonhosted.org/packages/3b/15/d8d69d10223cb54c887a2180bd29fe5fa2aec1d4995c8821f7aa6eaf72e4/grpcio-1.75.1-cp311-cp311-win32.whl", hash = "sha256:d6be2b5ee7bea656c954dcf6aa8093c6f0e6a3ef9945c99d99fcbfc88c5c0bfe", size = 3950631, upload-time = "2025-09-26T09:01:51.23Z" }, + { url = "https://files.pythonhosted.org/packages/8a/40/7b8642d45fff6f83300c24eaac0380a840e5e7fe0e8d80afd31b99d7134e/grpcio-1.75.1-cp311-cp311-win_amd64.whl", hash = "sha256:61c692fb05956b17dd6d1ab480f7f10ad0536dba3bc8fd4e3c7263dc244ed772", size = 4646131, upload-time = "2025-09-26T09:01:53.266Z" }, + { url = "https://files.pythonhosted.org/packages/3a/81/42be79e73a50aaa20af66731c2defeb0e8c9008d9935a64dd8ea8e8c44eb/grpcio-1.75.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:7b888b33cd14085d86176b1628ad2fcbff94cfbbe7809465097aa0132e58b018", size = 5668314, upload-time = "2025-09-26T09:01:55.424Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a7/3686ed15822fedc58c22f82b3a7403d9faf38d7c33de46d4de6f06e49426/grpcio-1.75.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:8775036efe4ad2085975531d221535329f5dac99b6c2a854a995456098f99546", size = 11476125, upload-time = "2025-09-26T09:01:57.927Z" }, + { url = "https://files.pythonhosted.org/packages/14/85/21c71d674f03345ab183c634ecd889d3330177e27baea8d5d247a89b6442/grpcio-1.75.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb658f703468d7fbb5dcc4037c65391b7dc34f808ac46ed9136c24fc5eeb041d", size = 6246335, upload-time = "2025-09-26T09:02:00.76Z" }, + { url = "https://files.pythonhosted.org/packages/fd/db/3beb661bc56a385ae4fa6b0e70f6b91ac99d47afb726fe76aaff87ebb116/grpcio-1.75.1-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4b7177a1cdb3c51b02b0c0a256b0a72fdab719600a693e0e9037949efffb200b", size = 6916309, upload-time = "2025-09-26T09:02:02.894Z" }, + { url = "https://files.pythonhosted.org/packages/1e/9c/eda9fe57f2b84343d44c1b66cf3831c973ba29b078b16a27d4587a1fdd47/grpcio-1.75.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7d4fa6ccc3ec2e68a04f7b883d354d7fea22a34c44ce535a2f0c0049cf626ddf", size = 6435419, upload-time = "2025-09-26T09:02:05.055Z" }, + { url = "https://files.pythonhosted.org/packages/c3/b8/090c98983e0a9d602e3f919a6e2d4e470a8b489452905f9a0fa472cac059/grpcio-1.75.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3d86880ecaeb5b2f0a8afa63824de93adb8ebe4e49d0e51442532f4e08add7d6", size = 7064893, upload-time = "2025-09-26T09:02:07.275Z" }, + { url = "https://files.pythonhosted.org/packages/ec/c0/6d53d4dbbd00f8bd81571f5478d8a95528b716e0eddb4217cc7cb45aae5f/grpcio-1.75.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a8041d2f9e8a742aeae96f4b047ee44e73619f4f9d24565e84d5446c623673b6", size = 8011922, upload-time = "2025-09-26T09:02:09.527Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7c/48455b2d0c5949678d6982c3e31ea4d89df4e16131b03f7d5c590811cbe9/grpcio-1.75.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3652516048bf4c314ce12be37423c79829f46efffb390ad64149a10c6071e8de", size = 7466181, upload-time = "2025-09-26T09:02:12.279Z" }, + { url = "https://files.pythonhosted.org/packages/fd/12/04a0e79081e3170b6124f8cba9b6275871276be06c156ef981033f691880/grpcio-1.75.1-cp312-cp312-win32.whl", hash = "sha256:44b62345d8403975513af88da2f3d5cc76f73ca538ba46596f92a127c2aea945", size = 3938543, upload-time = "2025-09-26T09:02:14.77Z" }, + { url = "https://files.pythonhosted.org/packages/5f/d7/11350d9d7fb5adc73d2b0ebf6ac1cc70135577701e607407fe6739a90021/grpcio-1.75.1-cp312-cp312-win_amd64.whl", hash = "sha256:b1e191c5c465fa777d4cafbaacf0c01e0d5278022082c0abbd2ee1d6454ed94d", size = 4641938, upload-time = "2025-09-26T09:02:16.927Z" }, + { url = "https://files.pythonhosted.org/packages/46/74/bac4ab9f7722164afdf263ae31ba97b8174c667153510322a5eba4194c32/grpcio-1.75.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:3bed22e750d91d53d9e31e0af35a7b0b51367e974e14a4ff229db5b207647884", size = 5672779, upload-time = "2025-09-26T09:02:19.11Z" }, + { url = "https://files.pythonhosted.org/packages/a6/52/d0483cfa667cddaa294e3ab88fd2c2a6e9dc1a1928c0e5911e2e54bd5b50/grpcio-1.75.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:5b8f381eadcd6ecaa143a21e9e80a26424c76a0a9b3d546febe6648f3a36a5ac", size = 11470623, upload-time = "2025-09-26T09:02:22.117Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e4/d1954dce2972e32384db6a30273275e8c8ea5a44b80347f9055589333b3f/grpcio-1.75.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5bf4001d3293e3414d0cf99ff9b1139106e57c3a66dfff0c5f60b2a6286ec133", size = 6248838, upload-time = "2025-09-26T09:02:26.426Z" }, + { url = "https://files.pythonhosted.org/packages/06/43/073363bf63826ba8077c335d797a8d026f129dc0912b69c42feaf8f0cd26/grpcio-1.75.1-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:9f82ff474103e26351dacfe8d50214e7c9322960d8d07ba7fa1d05ff981c8b2d", size = 6922663, upload-time = "2025-09-26T09:02:28.724Z" }, + { url = "https://files.pythonhosted.org/packages/c2/6f/076ac0df6c359117676cacfa8a377e2abcecec6a6599a15a672d331f6680/grpcio-1.75.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0ee119f4f88d9f75414217823d21d75bfe0e6ed40135b0cbbfc6376bc9f7757d", size = 6436149, upload-time = "2025-09-26T09:02:30.971Z" }, + { url = "https://files.pythonhosted.org/packages/6b/27/1d08824f1d573fcb1fa35ede40d6020e68a04391709939e1c6f4193b445f/grpcio-1.75.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:664eecc3abe6d916fa6cf8dd6b778e62fb264a70f3430a3180995bf2da935446", size = 7067989, upload-time = "2025-09-26T09:02:33.233Z" }, + { url = "https://files.pythonhosted.org/packages/c6/98/98594cf97b8713feb06a8cb04eeef60b4757e3e2fb91aa0d9161da769843/grpcio-1.75.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c32193fa08b2fbebf08fe08e84f8a0aad32d87c3ad42999c65e9449871b1c66e", size = 8010717, upload-time = "2025-09-26T09:02:36.011Z" }, + { url = "https://files.pythonhosted.org/packages/8c/7e/bb80b1bba03c12158f9254762cdf5cced4a9bc2e8ed51ed335915a5a06ef/grpcio-1.75.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5cebe13088b9254f6e615bcf1da9131d46cfa4e88039454aca9cb65f639bd3bc", size = 7463822, upload-time = "2025-09-26T09:02:38.26Z" }, + { url = "https://files.pythonhosted.org/packages/23/1c/1ea57fdc06927eb5640f6750c697f596f26183573069189eeaf6ef86ba2d/grpcio-1.75.1-cp313-cp313-win32.whl", hash = "sha256:4b4c678e7ed50f8ae8b8dbad15a865ee73ce12668b6aaf411bf3258b5bc3f970", size = 3938490, upload-time = "2025-09-26T09:02:40.268Z" }, + { url = "https://files.pythonhosted.org/packages/4b/24/fbb8ff1ccadfbf78ad2401c41aceaf02b0d782c084530d8871ddd69a2d49/grpcio-1.75.1-cp313-cp313-win_amd64.whl", hash = "sha256:5573f51e3f296a1bcf71e7a690c092845fb223072120f4bdb7a5b48e111def66", size = 4642538, upload-time = "2025-09-26T09:02:42.519Z" }, + { url = "https://files.pythonhosted.org/packages/f2/1b/9a0a5cecd24302b9fdbcd55d15ed6267e5f3d5b898ff9ac8cbe17ee76129/grpcio-1.75.1-cp314-cp314-linux_armv7l.whl", hash = "sha256:c05da79068dd96723793bffc8d0e64c45f316248417515f28d22204d9dae51c7", size = 5673319, upload-time = "2025-09-26T09:02:44.742Z" }, + { url = "https://files.pythonhosted.org/packages/c6/ec/9d6959429a83fbf5df8549c591a8a52bb313976f6646b79852c4884e3225/grpcio-1.75.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:06373a94fd16ec287116a825161dca179a0402d0c60674ceeec8c9fba344fe66", size = 11480347, upload-time = "2025-09-26T09:02:47.539Z" }, + { url = "https://files.pythonhosted.org/packages/09/7a/26da709e42c4565c3d7bf999a9569da96243ce34a8271a968dee810a7cf1/grpcio-1.75.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4484f4b7287bdaa7a5b3980f3c7224c3c622669405d20f69549f5fb956ad0421", size = 6254706, upload-time = "2025-09-26T09:02:50.4Z" }, + { url = "https://files.pythonhosted.org/packages/f1/08/dcb26a319d3725f199c97e671d904d84ee5680de57d74c566a991cfab632/grpcio-1.75.1-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:2720c239c1180eee69f7883c1d4c83fc1a495a2535b5fa322887c70bf02b16e8", size = 6922501, upload-time = "2025-09-26T09:02:52.711Z" }, + { url = "https://files.pythonhosted.org/packages/78/66/044d412c98408a5e23cb348845979a2d17a2e2b6c3c34c1ec91b920f49d0/grpcio-1.75.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:07a554fa31c668cf0e7a188678ceeca3cb8fead29bbe455352e712ec33ca701c", size = 6437492, upload-time = "2025-09-26T09:02:55.542Z" }, + { url = "https://files.pythonhosted.org/packages/4e/9d/5e3e362815152aa1afd8b26ea613effa005962f9da0eec6e0e4527e7a7d1/grpcio-1.75.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3e71a2105210366bfc398eef7f57a664df99194f3520edb88b9c3a7e46ee0d64", size = 7081061, upload-time = "2025-09-26T09:02:58.261Z" }, + { url = "https://files.pythonhosted.org/packages/1e/1a/46615682a19e100f46e31ddba9ebc297c5a5ab9ddb47b35443ffadb8776c/grpcio-1.75.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:8679aa8a5b67976776d3c6b0521e99d1c34db8a312a12bcfd78a7085cb9b604e", size = 8010849, upload-time = "2025-09-26T09:03:00.548Z" }, + { url = "https://files.pythonhosted.org/packages/67/8e/3204b94ac30b0f675ab1c06540ab5578660dc8b690db71854d3116f20d00/grpcio-1.75.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:aad1c774f4ebf0696a7f148a56d39a3432550612597331792528895258966dc0", size = 7464478, upload-time = "2025-09-26T09:03:03.096Z" }, + { url = "https://files.pythonhosted.org/packages/b7/97/2d90652b213863b2cf466d9c1260ca7e7b67a16780431b3eb1d0420e3d5b/grpcio-1.75.1-cp314-cp314-win32.whl", hash = "sha256:62ce42d9994446b307649cb2a23335fa8e927f7ab2cbf5fcb844d6acb4d85f9c", size = 4012672, upload-time = "2025-09-26T09:03:05.477Z" }, + { url = "https://files.pythonhosted.org/packages/f9/df/e2e6e9fc1c985cd1a59e6996a05647c720fe8a03b92f5ec2d60d366c531e/grpcio-1.75.1-cp314-cp314-win_amd64.whl", hash = "sha256:f86e92275710bea3000cb79feca1762dc0ad3b27830dd1a74e82ab321d4ee464", size = 4772475, upload-time = "2025-09-26T09:03:07.661Z" }, +] + [[package]] name = "grpclib" version = "0.4.7" @@ -2339,6 +2409,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] +[[package]] +name = "mem0ai" +version = "0.1.118" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "openai" }, + { name = "posthog" }, + { name = "protobuf" }, + { name = "pydantic" }, + { name = "pytz" }, + { name = "qdrant-client" }, + { name = "sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/db/1d/b7797ee607d0de2979d2a8b4c0c102989d5e1a1c9d67478dc6a2e2e0b2a8/mem0ai-0.1.118.tar.gz", hash = "sha256:d62497286616357f8726b849afc20031cd0ab56d1cf312fa289b006be33c3ce7", size = 159324, upload-time = "2025-09-25T20:53:00.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/70/e648ab026aa6505b920ed405a422727777bebdc5135691b2ca6350a02062/mem0ai-0.1.118-py3-none-any.whl", hash = "sha256:c2b371224a340fd5529d608dfbd2e77c610c7ffe421005ff7e862fd6f322cca8", size = 239476, upload-time = "2025-09-25T20:52:58.32Z" }, +] + [[package]] name = "mergedeep" version = "1.3.4" @@ -3285,6 +3373,32 @@ wheels = [ ] [[package]] +name = "portalocker" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pywin32", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/77/65b857a69ed876e1951e88aaba60f5ce6120c33703f7cb61a3c894b8c1b6/portalocker-3.2.0.tar.gz", hash = "sha256:1f3002956a54a8c3730586c5c77bf18fae4149e07eaf1c29fc3faf4d5a3f89ac", size = 95644, upload-time = "2025-06-14T13:20:40.03Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/a6/38c8e2f318bf67d338f4d629e93b0b4b9af331f455f0390ea8ce4a099b26/portalocker-3.2.0-py3-none-any.whl", hash = "sha256:3cdc5f565312224bc570c49337bd21428bba0ef363bbcf58b9ef4a9f11779968", size = 22424, upload-time = "2025-06-14T13:20:38.083Z" }, +] + +[[package]] +name = "posthog" +version = "6.7.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "backoff" }, + { name = "distro" }, + { name = "python-dateutil" }, + { name = "requests" }, + { name = "six" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e2/ce/11d6fa30ab517018796e1d675498992da585479e7079770ec8fa99a61561/posthog-6.7.6.tar.gz", hash = "sha256:ee5c5ad04b857d96d9b7a4f715e23916a2f206bfcf25e5a9d328a3d27664b0d3", size = 119129, upload-time = "2025-09-22T18:11:12.365Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/84/586422d8861b5391c8414360b10f603c0b7859bb09ad688e64430ed0df7b/posthog-6.7.6-py3-none-any.whl", hash = "sha256:b09a7e65a042ec416c28874b397d3accae412a80a8b0ef3fa686fbffc99e4d4b", size = 137348, upload-time = "2025-09-22T18:11:10.807Z" }, name = "prefect" version = "3.4.22" source = { registry = "https://pypi.org/simple" } @@ -3651,7 +3765,7 @@ wheels = [ name = "pydantic-ai" source = { editable = "." } dependencies = [ - { name = "pydantic-ai-slim", extra = ["ag-ui", "anthropic", "bedrock", "cli", "cohere", "evals", "google", "groq", "huggingface", "logfire", "mcp", "mistral", "openai", "retries", "temporal", "vertexai"] }, + { name = "pydantic-ai-slim", extra = ["ag-ui", "anthropic", "bedrock", "cli", "cohere", "evals", "google", "groq", "huggingface", "logfire", "mcp", "mem0", "mistral", "openai", "retries", "temporal", "vertexai"] }, ] [package.optional-dependencies] @@ -3681,6 +3795,7 @@ dev = [ { name = "genai-prices" }, { name = "inline-snapshot" }, { name = "mcp-run-python" }, + { name = "mem0ai" }, { name = "pip" }, { name = "pytest" }, { name = "pytest-examples" }, @@ -3715,7 +3830,7 @@ lint = [ requires-dist = [ { name = "fasta2a", marker = "extra == 'a2a'", specifier = ">=0.4.1" }, { name = "pydantic-ai-examples", marker = "extra == 'examples'", editable = "examples" }, - { name = "pydantic-ai-slim", extras = ["ag-ui", "anthropic", "bedrock", "cli", "cohere", "evals", "google", "groq", "huggingface", "logfire", "mcp", "mistral", "openai", "retries", "temporal", "vertexai"], editable = "pydantic_ai_slim" }, + { name = "pydantic-ai-slim", extras = ["ag-ui", "anthropic", "bedrock", "cli", "cohere", "evals", "google", "groq", "huggingface", "logfire", "mcp", "mem0", "mistral", "openai", "retries", "temporal", "vertexai"], editable = "pydantic_ai_slim" }, { name = "pydantic-ai-slim", extras = ["dbos"], marker = "extra == 'dbos'", editable = "pydantic_ai_slim" }, { name = "pydantic-ai-slim", extras = ["prefect"], marker = "extra == 'prefect'", editable = "pydantic_ai_slim" }, ] @@ -3734,6 +3849,7 @@ dev = [ { name = "genai-prices", specifier = ">=0.0.28" }, { name = "inline-snapshot", specifier = ">=0.19.3" }, { name = "mcp-run-python", specifier = ">=0.0.20" }, + { name = "mem0ai", specifier = ">=0.1.118" }, { name = "pip", specifier = ">=25.2" }, { name = "pytest", specifier = ">=8.3.3" }, { name = "pytest-examples", specifier = ">=0.0.18" }, @@ -3865,6 +3981,9 @@ logfire = [ mcp = [ { name = "mcp" }, ] +mem0 = [ + { name = "mem0ai" }, +] mistral = [ { name = "mistralai" }, ] @@ -3908,6 +4027,7 @@ requires-dist = [ { name = "huggingface-hub", extras = ["inference"], marker = "extra == 'huggingface'", specifier = ">=0.33.5" }, { name = "logfire", extras = ["httpx"], marker = "extra == 'logfire'", specifier = ">=3.14.1" }, { name = "mcp", marker = "extra == 'mcp'", specifier = ">=1.12.3" }, + { name = "mem0ai", marker = "extra == 'mem0'", specifier = ">=0.1.118" }, { name = "mistralai", marker = "extra == 'mistral'", specifier = ">=1.9.10" }, { name = "openai", marker = "extra == 'openai'", specifier = ">=1.107.2" }, { name = "opentelemetry-api", specifier = ">=1.28.0" }, @@ -3925,6 +4045,7 @@ requires-dist = [ { name = "tenacity", marker = "extra == 'retries'", specifier = ">=8.2.3" }, { name = "typing-inspection", specifier = ">=0.4.0" }, ] +provides-extras = ["a2a", "ag-ui", "anthropic", "bedrock", "cli", "cohere", "dbos", "duckduckgo", "evals", "google", "groq", "huggingface", "logfire", "mcp", "mem0", "mistral", "openai", "retries", "tavily", "temporal", "vertexai"] provides-extras = ["a2a", "ag-ui", "anthropic", "bedrock", "cli", "cohere", "dbos", "duckduckgo", "evals", "google", "groq", "huggingface", "logfire", "mcp", "mistral", "openai", "prefect", "retries", "tavily", "temporal", "vertexai"] [[package]] @@ -4364,6 +4485,21 @@ wheels = [ ] [[package]] +name = "qdrant-client" +version = "1.15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "grpcio" }, + { name = "httpx", extra = ["http2"] }, + { name = "numpy" }, + { name = "portalocker" }, + { name = "protobuf" }, + { name = "pydantic" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/8b/76c7d325e11d97cb8eb5e261c3759e9ed6664735afbf32fdded5b580690c/qdrant_client-1.15.1.tar.gz", hash = "sha256:631f1f3caebfad0fd0c1fba98f41be81d9962b7bf3ca653bed3b727c0e0cbe0e", size = 295297, upload-time = "2025-07-31T19:35:19.627Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/33/d8df6a2b214ffbe4138db9a1efe3248f67dc3c671f82308bea1582ecbbb7/qdrant_client-1.15.1-py3-none-any.whl", hash = "sha256:2b975099b378382f6ca1cfb43f0d59e541be6e16a5892f282a4b8de7eff5cb63", size = 337331, upload-time = "2025-07-31T19:35:17.539Z" }, name = "readchar" version = "4.2.1" source = { registry = "https://pypi.org/simple" }