diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index ae3231d..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,34 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [0.1.0] - 2025-10-25 - -### Added -- Modern Letta Python SDK integration with `agents.create()` API -- Memory blocks support for agent creation -- Streaming response support via `send_message_stream()` -- Built-in tools configuration (`web_search`, `run_code`) -- Embedding model configuration -- CLI streaming support with `--stream` flag -- Comprehensive test coverage -- Enhanced README with API examples -- Discord bot integration skeleton and examples -- Custom tool examples (code runner, tool management) -- Configuration management via environment variables - -### Features -- Simplified configuration with environment-based setup and sensible defaults -- Modal integration for serverless deployment -- Enhanced typing with proper message_type handling -- CLI tools for quick agent operations -- Support for multiple message types (assistant, tool_call, tool_return, reasoning) - -### Configuration -- Default LLM model: `openai/gpt-4.1` -- Default embedding model: `openai/text-embedding-3-small` -- Tools configured via `MODALETTA_TOOLS` environment variable (comma-separated) -- Full configuration via environment variables (see README.md) diff --git a/README.md b/README.md index e47566e..717b263 100644 --- a/README.md +++ b/README.md @@ -1,247 +1,75 @@ -# Modaletta +# Modaletta Project -**โœจ Updated for Modern Letta API**: This package now uses the latest Letta Python SDK with proper agent creation, memory blocks, and message handling. +This repository contains multiple integrations for AI agents powered by [Letta](https://docs.letta.com) and [Modal](https://modal.com/docs). -A Python package that integrates [Letta](https://docs.letta.com) (AI agent framework) with [Modal](https://modal.com/docs) (serverless platform) for scalable stateful AI agent deployment. +## Project Structure -## Current Status +The repository is organized into separate directories for each integration: -### โœ… What's New (v0.1.0) -- **Modern Letta API**: Updated to use latest Letta Python SDK - - Uses `client.agents.create()` with `memory_blocks` parameter - - Proper message handling with `message_type` field - - Support for streaming responses - - Built-in tools support (`web_search`, `run_code`) -- **Improved Configuration**: - - Modern model defaults (`openai/gpt-4.1`, `openai/text-embedding-3-small`) - - Tool configuration support - - Embedding model configuration -- **Enhanced CLI**: - - Streaming support with `--stream` flag - - Better message type handling and display -- **Updated Tests**: All tests pass with proper mocking of new API structure +### `modaletta/` - Core Package +The main Modaletta Python package for building AI agents with Letta and Modal. -### ๐Ÿงช Ready to Test -The codebase provides: -- **Letta Integration**: Complete wrapper around modern letta-client API -- **Modal Deployment**: Serverless functions for agent execution on Modal -- **Agent Management**: High-level abstractions for stateful agent operations -- **CLI Commands**: Full command-line interface with streaming support +- **Documentation**: See [modaletta/README.md](modaletta/README.md) for full details +- **Installation**: `cd modaletta && pip install -e .` +- **Features**: + - Letta integration with modern API support + - Modal serverless deployment + - CLI tools for agent management + - Streaming support + - Built-in tools (web search, code execution) -### ๐Ÿ“‹ Prerequisites for Testing -- **Letta Server**: Self-hosted or Letta Cloud account with API key -- **OpenAI API Key**: For using default models (or configure other models) -- **Modal Account**: Only needed for serverless deployment features +### `discord/` - Discord Bot Integration +A Discord bot powered by Modaletta agents. -## Installation - -**From Source (Recommended for now)**: - -```bash -git clone https://github.com/jakemannix/modaletta.git -cd modaletta -uv sync -source .venv/bin/activate # On Windows: .venv\Scripts\activate -``` +- **Documentation**: See [discord/README.md](discord/README.md) for setup and usage +- **Installation**: `cd discord && pip install -e .` +- **Features**: + - Discord bot integration + - Stateful conversations with memory + - Per-channel agent customization ## Quick Start -1. **Configuration** - -Modaletta uses environment variables for configuration: - -| Variable | Description | Default | -|----------|-------------|---------| -| `LETTA_SERVER_URL` | Letta server URL (use `https://api.letta.com` for Letta Cloud) | `http://localhost:8283` | -| `LETTA_API_KEY` | Letta API key (required for Letta Cloud) | None | -| `MODAL_TOKEN_ID` | Modal token ID | None | -| `MODAL_TOKEN_SECRET` | Modal token secret | None | -| `MODALETTA_AGENT_NAME` | Default agent name | `modaletta-agent` | -| `MODALETTA_MEMORY_CAPACITY` | Agent memory capacity | `2000` | -| `MODALETTA_LLM_MODEL` | LLM model to use (with provider prefix) | `openai/gpt-4.1` | -| `MODALETTA_EMBEDDING_MODEL` | Embedding model to use | `openai/text-embedding-3-small` | -| `MODALETTA_TEMPERATURE` | LLM temperature | `0.7` | -| `MODALETTA_TOOLS` | Comma-separated list of tools | `` (empty) | - -### Example `.env` file +### For the Core Modaletta Package ```bash -# For Letta Cloud -LETTA_SERVER_URL=https://api.letta.com -LETTA_API_KEY=your_letta_api_key_here - -# For self-hosted Letta -# LETTA_SERVER_URL=http://localhost:8283 -# LETTA_API_KEY= # Optional for self-hosted - -# Model configuration -MODALETTA_LLM_MODEL=openai/gpt-4.1 -MODALETTA_EMBEDDING_MODEL=openai/text-embedding-3-small -MODALETTA_TOOLS=web_search,run_code - -# Optional Modal configuration (only needed for serverless deployment) -# MODAL_TOKEN_ID=your_modal_token_id -# MODAL_TOKEN_SECRET=your_modal_token_secret - -# Optional: E2B API key for run_code tool (get free key at https://e2b.dev) -# E2B_API_KEY=your_e2b_api_key -``` +cd modaletta -2. **Verify basic functionality**: +# Create virtual environment (recommended) +python -m venv .venv && source .venv/bin/activate -```bash +# Install and use +pip install -e . modaletta --help -modaletta config-info -``` - -3. **Create and use an agent**: - -```bash -# Create an agent with custom persona -modaletta create-agent \ - --name "my-assistant" \ - --persona "I am a helpful AI assistant specializing in Python development." \ - --human "The user is a Python developer." - -# List all agents -modaletta list-agents - -# Send a message (use the agent ID from list-agents) -modaletta send-message "Hello! Can you help me debug some Python code?" - -# Send with streaming (see response as it's generated) -modaletta send-message --stream "Tell me a story about AI." - -# View agent memory -modaletta get-memory -``` - - - -**Note**: The `run_code` tool requires an E2B API key for self-hosted servers. It works automatically on Letta Cloud. Get a free key at [e2b.dev](https://e2b.dev). - -## Python API - -### Quick Start - -```python -from modaletta import ModalettaAgent, ModalettaClient, ModalettaConfig - -# Configure (loads from environment variables) -config = ModalettaConfig.from_env() -config.tools = ["web_search", "run_code"] # Add built-in tools - -# Option 1: Use the client directly -client = ModalettaClient(config) -agent_id = client.create_agent( - name="my-assistant", - persona="I am a helpful AI assistant that specializes in coding and research.", - human="The user is a Python developer working on AI projects." -) - -# Send a message (note: Letta agents are STATEFUL, only send new messages) -response = client.send_message(agent_id, "Hello! Can you help me with Python?") - -# Process response with proper message_type handling -for msg in response: - message_type = msg.get("message_type", "") - if message_type == "assistant_message": - print(f"Assistant: {msg.get('content', '')}") - elif message_type == "tool_call_message": - tool_call = msg.get("tool_call", {}) - print(f"[Calling tool: {tool_call.get('name', '')}]") - elif message_type == "tool_return_message": - print(f"[Tool result: {msg.get('tool_return', '')}]") - -# Option 2: Use the agent wrapper (easier) -agent = ModalettaAgent( - config=config, - persona="I am a helpful AI assistant.", - human="The user is a developer." -) - -response = agent.send_message("What's 25 * 47? Use run_code to calculate it.") -for msg in response: - if msg.get("message_type") == "assistant_message": - print(msg.get("content", "")) - -# Streaming example -for chunk in agent.send_message_stream("Tell me a story", stream_tokens=True): - if chunk.get("message_type") == "assistant_message": - content = chunk.get("content", "") - if content: - print(content, end="", flush=True) -print() # New line at end - -# Get agent memory -memory = agent.get_memory() -print(f"Memory blocks: {list(memory.keys())}") ``` -### Key API Concepts - -**Stateful Agents**: Letta agents maintain conversation history server-side. Always send only NEW messages, never the full history. - -```python -# โœ… CORRECT - Single new message -response = client.send_message(agent_id, "What's the weather?") - -# โŒ WRONG - Don't send conversation history -response = client.send_message(agent_id, previous_messages + [new_message]) -``` - -**Message Types**: Responses use `message_type` field to distinguish different message kinds: -- `assistant_message`: Agent's response (has `content` field) -- `reasoning_message`: Agent's internal reasoning (has `reasoning` field) -- `tool_call_message`: Agent calling a tool (has `tool_call` dict with `name` and `arguments`) -- `tool_return_message`: Tool execution result (has `tool_return` field) -- `usage_statistics`: Token usage information - -## Modal Deployment (Theoretical) +See [modaletta/README.md](modaletta/README.md) for detailed usage instructions. -**โš ๏ธ Completely untested** +### For the Discord Bot -The codebase includes Modal deployment functions but these have not been tested: +```bash +cd discord -```python -import modal -from modaletta.agent import app, create_modal_agent, send_message_modal +# Create virtual environment (recommended) +python -m venv .venv && source .venv/bin/activate -# Theoretical usage - may not work: -with app.run(): - config_dict = {"letta_server_url": "http://localhost:8283"} - agent_id = create_modal_agent.remote(config_dict) - response = send_message_modal.remote(agent_id, "Hello from Modal!", config_dict) - print(response) +# Install and run +pip install -e . +python modaletta.py ``` -## Development +See [discord/README.md](discord/README.md) for configuration details. -### Tested Commands -```bash -# These work: -uv sync --extra dev # Install with dev dependencies -uv run pytest tests/ -v # Run test suite (passes) -uv run modaletta --help # CLI help works -``` +**Tip**: For faster installation, consider using [uv](https://docs.astral.sh/uv/) instead of pip. -### Untested Commands -```bash -# These should work but are untested: -ruff check . # Linting -ruff format . # Code formatting -mypy . # Type checking -``` +## Environment Configuration -## Requirements +Both integrations use environment variables for configuration. Create `.env` files in their respective directories: -### Confirmed Working -- Python 3.9+ (tested with 3.12) -- Dependencies install correctly via pip/uv +- `modaletta/.env` - For the core package +- `discord/.env` - For the Discord bot -### Required for Full Functionality (Untested) -- Letta server running (for agent operations) -- Modal account and authentication (for deployment) +See the documentation in each directory for specific configuration options. ## License @@ -249,40 +77,11 @@ MIT License - see [LICENSE](LICENSE) for details. ## Contributing -This package is in early development. The most valuable contributions would be: -1. **Testing with real Letta servers**: Verify agent operations actually work -2. **Modal deployment testing**: Test the serverless deployment functions -3. **Integration testing**: End-to-end workflows -4. **Documentation improvements**: Based on actual usage experience - -## Architecture - -Modaletta provides multiple layers of abstraction: - -1. **ModalettaConfig**: Configuration management with environment variable support -2. **ModalettaClient**: Low-level client wrapping the Letta Python SDK with modern API -3. **ModalettaAgent**: High-level agent wrapper for easier usage -4. **Modal Functions**: Serverless deployment functions for running agents on Modal -5. **CLI**: Command-line interface for all agent operations - -### Why Modaletta? - -While you can use the Letta Python SDK directly, Modaletta provides: - -- **Simplified Configuration**: Environment-based config with sensible defaults -- **Modal Integration**: Ready-to-use serverless deployment on Modal -- **Enhanced Typing**: All responses properly typed with message_type handling -- **CLI Tools**: Command-line interface for quick agent operations -- **Best Practices**: Built-in patterns following Letta's latest guidelines - -## Known Limitations - -- **Modal Deployment**: Modal functions have basic testing but need real-world validation -- **Error Handling**: Could be more comprehensive for edge cases -- **Async Support**: Currently synchronous; async support could be added +Contributions are welcome! Please see the individual component READMEs for specific contribution guidelines. ## Support -- [GitHub Issues](https://github.com/jakemannix/modaletta/issues) - Please report what you actually tried and what failed -- [Letta Documentation](https://docs.letta.com) - For Letta server setup and API details -- [Modal Documentation](https://modal.com/docs) - For Modal deployment and authentication \ No newline at end of file +- [GitHub Issues](https://github.com/jakemannix/modaletta/issues) +- [Letta Documentation](https://docs.letta.com) +- [Modal Documentation](https://modal.com/docs) + diff --git a/discord/README.md b/discord/README.md index 9cc7769..5916cc2 100644 --- a/discord/README.md +++ b/discord/README.md @@ -25,13 +25,36 @@ A Discord bot integration for [Modaletta](https://github.com/jakemannix/modalett 2. **Install the main Modaletta package**: ```bash - uv sync + cd modaletta + + # Create and activate virtual environment (recommended) + python -m venv .venv + source .venv/bin/activate # On Windows: .venv\Scripts\activate + + # Or use uv for faster installation (install from https://docs.astral.sh/uv/) + # uv venv && source .venv/bin/activate + + # Install the package + pip install -e . + cd .. ``` 3. **Install Discord bot dependencies**: ```bash cd discord - uv venv && uv pip install -r requirements.txt + + # Create and activate virtual environment (recommended) + python -m venv .venv + source .venv/bin/activate # On Windows: .venv\Scripts\activate + + # Or use uv for faster installation + # uv venv && source .venv/bin/activate + + # Install the bot + pip install -e . + + # Optionally install with modaletta integration + # pip install -e ".[modaletta]" ``` ## Configuration diff --git a/discord/examples/example_bot.py b/discord/examples/example_bot.py index 8677194..4a72bc0 100644 --- a/discord/examples/example_bot.py +++ b/discord/examples/example_bot.py @@ -10,6 +10,7 @@ client = discord.Client(intents=intents) + @client.event async def on_ready(): print(f'We have logged in as {client.user}') diff --git a/discord/pyproject.toml b/discord/pyproject.toml new file mode 100644 index 0000000..77a4c40 --- /dev/null +++ b/discord/pyproject.toml @@ -0,0 +1,66 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "modaletta-discord" +version = "0.1.0" +description = "Discord bot integration for Modaletta AI agents" +readme = "README.md" +requires-python = ">=3.9" +license = {text = "MIT"} +authors = [ + {name = "Jake Mannix", email = "jake.mannix@gmail.com"}, +] +keywords = ["discord", "bot", "ai", "agents", "letta", "modaletta"] +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] + +dependencies = [ + "discord.py", + "python-dotenv", +] + +[project.optional-dependencies] +dev = [ + "pytest>=7.0.0", + "pytest-asyncio", + "ruff", + "mypy", +] + +# Optional: Add modaletta integration +modaletta = [ + "modaletta", +] + +[project.urls] +Homepage = "https://github.com/jakemannix/modaletta" +Repository = "https://github.com/jakemannix/modaletta" +Issues = "https://github.com/jakemannix/modaletta/issues" + +[tool.ruff] +line-length = 120 +target-version = "py39" + +[tool.ruff.lint] +select = ["E", "F", "W", "I", "N", "UP", "B", "A", "COM", "C4", "ISC", "G", "PIE", "PT", "Q", "SIM", "TID", "ARG", "PTH", "ERA", "RUF"] +ignore = ["ANN101", "ANN102", "COM812", "ISC001"] + +[tool.ruff.format] +quote-style = "double" +indent-style = "space" + +[tool.mypy] +python_version = "3.9" +warn_return_any = true +warn_unused_configs = true + diff --git a/discord/requirements.txt b/discord/requirements.txt deleted file mode 100644 index 95004ae..0000000 --- a/discord/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -discord.py -python-dotenv \ No newline at end of file diff --git a/modaletta/CHANGELOG.md b/modaletta/CHANGELOG.md new file mode 100644 index 0000000..57eee49 --- /dev/null +++ b/modaletta/CHANGELOG.md @@ -0,0 +1,45 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.1.0] - 2025-10-25 + +### Added +- Modern Letta Python SDK integration with proper `agents.create()` API +- Memory blocks support for agent creation +- Streaming response support via `send_message_stream()` +- Built-in tools configuration (`web_search`, `run_code`) +- Embedding model configuration +- CLI streaming support with `--stream` flag +- Comprehensive test coverage for new API +- Migration guide documentation +- Enhanced README with modern API examples + +### Changed +- **BREAKING**: Updated to use modern Letta API structure (`client.agents.*` instead of flat methods) +- **BREAKING**: Response format now uses `message_type` field instead of `role`/`text` +- **BREAKING**: Model names must include provider prefix (e.g., `openai/gpt-4.1`) +- Updated default LLM model from `gpt-4` to `openai/gpt-4.1` +- Updated all examples to use new API patterns +- Enhanced CLI with better message type handling +- Improved configuration with tools and embedding model support + +### Fixed +- Proper handling of different message types (assistant, tool_call, tool_return, reasoning) +- Correct API method calls matching latest Letta SDK +- Configuration parsing for tools from environment variables + +### Deprecated +- Old API method names (still work through wrapper but will be removed in future) + +## [0.0.1] - 2024-XX-XX + +### Added +- Initial release with basic Letta integration +- Modal deployment support +- Basic CLI commands +- Configuration management + diff --git a/modaletta/MIGRATION_GUIDE.md b/modaletta/MIGRATION_GUIDE.md new file mode 100644 index 0000000..86a90d1 --- /dev/null +++ b/modaletta/MIGRATION_GUIDE.md @@ -0,0 +1,190 @@ +# Migration Guide: Updated to Modern Letta API + +This document outlines the changes made to update Modaletta to use the modern Letta Python SDK. + +## Overview + +Modaletta has been updated from the old Letta API to the modern, officially supported Letta Python SDK (`letta-client`). This update ensures compatibility with the latest Letta features and follows Letta's recommended best practices. + +## What Changed + +### 1. Client API Methods + +**Before (Old API):** +```python +# Old method names +agents = client.letta_client.list_agents() +agent = client.letta_client.create_agent(name="test") +client.letta_client.delete_agent(agent_id) +response = client.letta_client.send_message(agent_id, message) +memory = client.letta_client.get_agent_memory(agent_id) +``` + +**After (New API):** +```python +# New nested structure: client.agents.* +agents = client.letta_client.agents.list() +agent = client.letta_client.agents.create(name="test", memory_blocks=[...]) +client.letta_client.agents.delete(agent_id) +response = client.letta_client.agents.messages.create(agent_id, messages=[...]) +memory = client.letta_client.agents.memory.get(agent_id) +``` + +### 2. Agent Creation with Memory Blocks + +**Before:** +```python +agent = client.create_agent( + name="my-agent", + persona="I am helpful", + human="User is a developer" +) +``` + +**After:** +```python +agent = client.create_agent( + name="my-agent", + memory_blocks=[ + {"label": "persona", "value": "I am helpful"}, + {"label": "human", "value": "User is a developer"} + ], + model="openai/gpt-4.1", + embedding="openai/text-embedding-3-small", + tools=["web_search", "run_code"] +) +``` + +### 3. Message Response Format + +**Before:** +```python +response = client.send_message(agent_id, "Hello") +for msg in response: + text = msg.get("text", "") + role = msg.get("role", "") +``` + +**After:** +```python +response = client.send_message(agent_id, "Hello") +for msg in response: + message_type = msg.get("message_type", "") + if message_type == "assistant_message": + content = msg.get("content", "") + elif message_type == "tool_call_message": + tool_call = msg.get("tool_call", {}) + elif message_type == "tool_return_message": + tool_return = msg.get("tool_return", "") +``` + +### 4. Configuration Updates + +**New Default Models:** +- `llm_model`: `openai/gpt-4.1` (was `gpt-4`) +- `embedding_model`: `openai/text-embedding-3-small` (new field) +- `tools`: Comma-separated list via `MODALETTA_TOOLS` env var (new field) + +### 5. Streaming Support + +**New Feature:** +```python +# Stream responses +for chunk in client.send_message_stream(agent_id, "Tell me a story", stream_tokens=True): + if chunk.get("message_type") == "assistant_message": + print(chunk.get("content", ""), end="", flush=True) +``` + +## Updated Files + +### Core Files +- **`src/modaletta/client.py`**: Updated all API calls to use modern nested structure +- **`src/modaletta/config.py`**: Added embedding model and tools configuration +- **`src/modaletta/agent.py`**: Added streaming support and better initialization +- **`src/modaletta/cli.py`**: Updated to handle new message types with `--stream` flag + +### Tests +- **`tests/test_client.py`**: Updated mocks for nested API structure +- **`tests/test_config.py`**: Added tests for new configuration fields + +### Examples +- **`examples/basic_agent.py`**: Updated to use new API patterns +- **`examples/modal_deployment.py`**: Updated message handling + +### Documentation +- **`README.md`**: Comprehensive update with modern examples +- **`MIGRATION_GUIDE.md`**: This file + +## Key Differences from Raw Letta SDK + +While Modaletta now uses the modern Letta SDK, it provides additional benefits: + +1. **Simplified Configuration**: Environment-based config with sensible defaults +2. **Convenience Methods**: Higher-level abstractions for common operations +3. **Modal Integration**: Ready-to-use serverless deployment functions +4. **CLI Tools**: Command-line interface for quick operations +5. **Type Hints**: Full type hints for better IDE support + +## Environment Variables + +New/updated environment variables: + +```bash +# Updated defaults +MODALETTA_LLM_MODEL=openai/gpt-4.1 # was gpt-4 +MODALETTA_EMBEDDING_MODEL=openai/text-embedding-3-small # new + +# New: Tools configuration +MODALETTA_TOOLS=web_search,run_code # comma-separated list +``` + +## Breaking Changes + +1. **Response Format**: All message responses now use `message_type` instead of `role`/`text` +2. **Model Names**: Must include provider prefix (e.g., `openai/gpt-4.1` not just `gpt-4`) +3. **Agent Creation**: Memory blocks are now explicitly structured +4. **Memory Methods**: Changed from `get_agent_memory` to nested `agents.memory.get` + +## Testing + +All tests pass with the new API: +```bash +$ python -m pytest tests/ -v +============================= test session starts ============================== +tests/test_client.py::test_client_initialization PASSED +tests/test_client.py::test_letta_client_property PASSED +tests/test_client.py::test_list_agents PASSED +tests/test_client.py::test_create_agent PASSED +tests/test_client.py::test_send_message PASSED +tests/test_client.py::test_get_agent_memory PASSED +tests/test_config.py::test_default_config PASSED +tests/test_config.py::test_config_from_env PASSED +tests/test_config.py::test_config_to_dict PASSED +tests/test_config.py::test_tools_parsing PASSED +======================== 10 passed in 0.86s ======================== +``` + +## Migration Checklist + +If you have existing code using Modaletta, follow these steps: + +- [ ] Update environment variables with new defaults +- [ ] Update any custom agent creation code to use `memory_blocks` +- [ ] Update message response parsing to use `message_type` instead of `role` +- [ ] Add provider prefixes to model names (e.g., `openai/`) +- [ ] Test with a Letta server (self-hosted or Letta Cloud) +- [ ] Consider using new streaming features + +## Resources + +- [Letta Documentation](https://docs.letta.com) +- [Letta Python SDK](https://github.com/letta-ai/letta-python) +- [Letta Cloud](https://app.letta.com) +- [Modal Documentation](https://modal.com/docs) + +## Support + +For issues or questions: +- [GitHub Issues](https://github.com/jakemannix/modaletta/issues) +- [Letta Discord](https://discord.gg/letta) + diff --git a/modaletta/README.md b/modaletta/README.md new file mode 100644 index 0000000..d259319 --- /dev/null +++ b/modaletta/README.md @@ -0,0 +1,345 @@ +# Modaletta + +**โœจ Updated for Modern Letta API**: This package now uses the latest Letta Python SDK with proper agent creation, memory blocks, and message handling. + +A Python package that integrates [Letta](https://docs.letta.com) (AI agent framework) with [Modal](https://modal.com/docs) (serverless platform) for scalable stateful AI agent deployment. + +## Current Status + +### โœ… What's New (v0.1.0) +- **Modern Letta API**: Updated to use latest Letta Python SDK + - Uses `client.agents.create()` with `memory_blocks` parameter + - Proper message handling with `message_type` field + - Support for streaming responses + - Built-in tools support (`web_search`, `run_code`) +- **Improved Configuration**: + - Modern model defaults (`openai/gpt-4.1`, `openai/text-embedding-3-small`) + - Tool configuration support + - Embedding model configuration +- **Enhanced CLI**: + - Streaming support with `--stream` flag + - Better message type handling and display +- **Updated Tests**: All tests pass with proper mocking of new API structure + +### ๐Ÿงช Ready to Test +The codebase provides: +- **Letta Integration**: Complete wrapper around modern letta-client API +- **Modal Deployment**: Serverless functions for agent execution on Modal +- **Agent Management**: High-level abstractions for stateful agent operations +- **CLI Commands**: Full command-line interface with streaming support + +### ๐Ÿ“‹ Prerequisites for Testing +- **Letta Server**: Self-hosted or Letta Cloud account with API key +- **OpenAI API Key**: For using default models (or configure other models) +- **Modal Account**: Only needed for serverless deployment features + +## Installation + +**From Source (Recommended for now)**: + +```bash +git clone https://github.com/jakemannix/modaletta.git +cd modaletta/modaletta +``` + +**Create a virtual environment** (recommended): + +```bash +# Standard approach +python -m venv .venv +source .venv/bin/activate # On Windows: .venv\Scripts\activate + +# Or use uv (modern, faster alternative - install from https://docs.astral.sh/uv/) +uv venv +source .venv/bin/activate # On Windows: .venv\Scripts\activate +``` + +**Install the package**: + +```bash +# Standard pip +pip install -e . + +# Or with uv (much faster) +uv pip install -e . +``` + +## Quick Start + +1. **Set up environment variables**: + +Create a `.env` file in your project root: + +```bash +# For Letta Cloud (easiest) +LETTA_SERVER_URL=https://api.letta.com +LETTA_API_KEY=your_letta_api_key_here # Get from https://app.letta.com/api-keys + +# For self-hosted Letta +# LETTA_SERVER_URL=http://localhost:8283 +``` + +2. **Verify basic functionality**: + +```bash +modaletta --help +modaletta config-info +``` + +3. **Create and use an agent**: + +```bash +# Create an agent with custom persona +modaletta create-agent \ + --name "my-assistant" \ + --persona "I am a helpful AI assistant specializing in Python development." \ + --human "The user is a Python developer." + +# List all agents +modaletta list-agents + +# Send a message (use the agent ID from list-agents) +modaletta send-message "Hello! Can you help me debug some Python code?" + +# Send with streaming (see response as it's generated) +modaletta send-message --stream "Tell me a story about AI." + +# View agent memory +modaletta get-memory +``` + +## Configuration + +Modaletta uses environment variables for configuration: + +| Variable | Description | Default | +|----------|-------------|---------| +| `LETTA_SERVER_URL` | Letta server URL (use `https://api.letta.com` for Letta Cloud) | `http://localhost:8283` | +| `LETTA_API_KEY` | Letta API key (required for Letta Cloud) | None | +| `MODAL_TOKEN_ID` | Modal token ID | None | +| `MODAL_TOKEN_SECRET` | Modal token secret | None | +| `MODALETTA_AGENT_NAME` | Default agent name | `modaletta-agent` | +| `MODALETTA_MEMORY_CAPACITY` | Agent memory capacity | `2000` | +| `MODALETTA_LLM_MODEL` | LLM model to use (with provider prefix) | `openai/gpt-4.1` | +| `MODALETTA_EMBEDDING_MODEL` | Embedding model to use | `openai/text-embedding-3-small` | +| `MODALETTA_TEMPERATURE` | LLM temperature | `0.7` | +| `MODALETTA_TOOLS` | Comma-separated list of tools | `` (empty) | + +### Example `.env` file + +```bash +# For Letta Cloud +LETTA_SERVER_URL=https://api.letta.com +LETTA_API_KEY=your_letta_api_key_here + +# For self-hosted Letta +# LETTA_SERVER_URL=http://localhost:8283 +# LETTA_API_KEY= # Optional for self-hosted + +# Model configuration +MODALETTA_LLM_MODEL=openai/gpt-4.1 +MODALETTA_EMBEDDING_MODEL=openai/text-embedding-3-small +MODALETTA_TOOLS=web_search,run_code + +# Optional Modal configuration (only needed for serverless deployment) +# MODAL_TOKEN_ID=your_modal_token_id +# MODAL_TOKEN_SECRET=your_modal_token_secret + +# Optional: E2B API key for run_code tool (get free key at https://e2b.dev) +# E2B_API_KEY=your_e2b_api_key +``` + +**Note**: The `run_code` tool requires an E2B API key for self-hosted servers. It works automatically on Letta Cloud. Get a free key at [e2b.dev](https://e2b.dev). + +## Python API + +### Quick Start + +```python +from modaletta import ModalettaAgent, ModalettaClient, ModalettaConfig + +# Configure (loads from environment variables) +config = ModalettaConfig.from_env() +config.tools = ["web_search", "run_code"] # Add built-in tools + +# Option 1: Use the client directly +client = ModalettaClient(config) +agent_id = client.create_agent( + name="my-assistant", + persona="I am a helpful AI assistant that specializes in coding and research.", + human="The user is a Python developer working on AI projects." +) + +# Send a message (note: Letta agents are STATEFUL, only send new messages) +response = client.send_message(agent_id, "Hello! Can you help me with Python?") + +# Process response with proper message_type handling +for msg in response: + message_type = msg.get("message_type", "") + if message_type == "assistant_message": + print(f"Assistant: {msg.get('content', '')}") + elif message_type == "tool_call_message": + tool_call = msg.get("tool_call", {}) + print(f"[Calling tool: {tool_call.get('name', '')}]") + elif message_type == "tool_return_message": + print(f"[Tool result: {msg.get('tool_return', '')}]") + +# Option 2: Use the agent wrapper (easier) +agent = ModalettaAgent( + config=config, + persona="I am a helpful AI assistant.", + human="The user is a developer." +) + +response = agent.send_message("What's 25 * 47? Use run_code to calculate it.") +for msg in response: + if msg.get("message_type") == "assistant_message": + print(msg.get("content", "")) + +# Streaming example +for chunk in agent.send_message_stream("Tell me a story", stream_tokens=True): + if chunk.get("message_type") == "assistant_message": + content = chunk.get("content", "") + if content: + print(content, end="", flush=True) +print() # New line at end + +# Get agent memory +memory = agent.get_memory() +print(f"Memory blocks: {list(memory.keys())}") +``` + +### Key API Concepts + +**Stateful Agents**: Letta agents maintain conversation history server-side. Always send only NEW messages, never the full history. + +```python +# โœ… CORRECT - Single new message +response = client.send_message(agent_id, "What's the weather?") + +# โŒ WRONG - Don't send conversation history +response = client.send_message(agent_id, previous_messages + [new_message]) +``` + +**Message Types**: Responses use `message_type` field to distinguish different message kinds: +- `assistant_message`: Agent's response (has `content` field) +- `reasoning_message`: Agent's internal reasoning (has `reasoning` field) +- `tool_call_message`: Agent calling a tool (has `tool_call` dict with `name` and `arguments`) +- `tool_return_message`: Tool execution result (has `tool_return` field) +- `usage_statistics`: Token usage information + +## Modal Deployment (Theoretical) + +**โš ๏ธ Completely untested** + +The codebase includes Modal deployment functions but these have not been tested: + +```python +import modal +from modaletta.agent import app, create_modal_agent, send_message_modal + +# Theoretical usage - may not work: +with app.run(): + config_dict = {"letta_server_url": "http://localhost:8283"} + agent_id = create_modal_agent.remote(config_dict) + response = send_message_modal.remote(agent_id, "Hello from Modal!", config_dict) + print(response) +``` + +## Development + +### Tested Commands +```bash +# These work: +pip install -e .[dev] # Install with dev dependencies +python -m pytest tests/ -v # Run test suite (passes) +modaletta --help # CLI help works +``` + +### Untested Commands +```bash +# These should work but are untested: +ruff check . # Linting +ruff format . # Code formatting +mypy . # Type checking +``` + +## Requirements + +### Confirmed Working +- Python 3.9+ (tested with 3.12) +- Dependencies install correctly via pip/uv + +### Required for Full Functionality (Untested) +- Letta server running (for agent operations) +- Modal account and authentication (for deployment) + +## License + +MIT License - see [LICENSE](LICENSE) for details. + +## Contributing + +This package is in early development. The most valuable contributions would be: +1. **Testing with real Letta servers**: Verify agent operations actually work +2. **Modal deployment testing**: Test the serverless deployment functions +3. **Integration testing**: End-to-end workflows +4. **Documentation improvements**: Based on actual usage experience + +## Architecture + +Modaletta provides multiple layers of abstraction: + +1. **ModalettaConfig**: Configuration management with environment variable support +2. **ModalettaClient**: Low-level client wrapping the Letta Python SDK with modern API +3. **ModalettaAgent**: High-level agent wrapper for easier usage +4. **Modal Functions**: Serverless deployment functions for running agents on Modal +5. **CLI**: Command-line interface for all agent operations + +### Why Modaletta? + +While you can use the Letta Python SDK directly, Modaletta provides: + +- **Simplified Configuration**: Environment-based config with sensible defaults +- **Modal Integration**: Ready-to-use serverless deployment on Modal +- **Enhanced Typing**: All responses properly typed with message_type handling +- **CLI Tools**: Command-line interface for quick agent operations +- **Best Practices**: Built-in patterns following Letta's latest guidelines + +## Migration from Old Letta API + +If you have existing code using the old Letta API, here are the key changes: + +```python +# OLD API (deprecated) +from letta import create_client +client = create_client() +agent = client.create_agent(name="test") +response = client.user_message(agent_id, "Hello") + +# NEW API (Modaletta with modern Letta) +from modaletta import ModalettaClient +client = ModalettaClient() +agent_id = client.create_agent( + name="test", + persona="I am a helpful assistant", + human="The user is a developer" +) +response = client.send_message(agent_id, "Hello") + +# Response format changed: +# OLD: response["messages"][0]["text"] +# NEW: response[0]["content"] (if message_type == "assistant_message") +``` + +## Known Limitations + +- **Modal Deployment**: Modal functions have basic testing but need real-world validation +- **Error Handling**: Could be more comprehensive for edge cases +- **Async Support**: Currently synchronous; async support could be added + +## Support + +- [GitHub Issues](https://github.com/jakemannix/modaletta/issues) - Please report what you actually tried and what failed +- [Letta Documentation](https://docs.letta.com) - For Letta server setup and API details +- [Modal Documentation](https://modal.com/docs) - For Modal deployment and authentication \ No newline at end of file diff --git a/examples/basic_agent.py b/modaletta/examples/basic_agent.py similarity index 91% rename from examples/basic_agent.py rename to modaletta/examples/basic_agent.py index af49bcf..6df8e60 100644 --- a/examples/basic_agent.py +++ b/modaletta/examples/basic_agent.py @@ -46,8 +46,9 @@ def main() -> None: # Get agent memory memory = agent.get_memory() print(f"\nAgent memory blocks:") - for label, value in memory.items(): - print(f" {label}: {value[:100] if value else '(empty)'}...") + for block_id, block_data in memory.items(): + if isinstance(block_data, dict): + print(f" {block_id}: {block_data.get('value', '')[:100]}...") if __name__ == "__main__": diff --git a/examples/custom_tool_local_code_runner.py b/modaletta/examples/custom_tool_local_code_runner.py similarity index 100% rename from examples/custom_tool_local_code_runner.py rename to modaletta/examples/custom_tool_local_code_runner.py diff --git a/examples/manage_tools.py b/modaletta/examples/manage_tools.py similarity index 100% rename from examples/manage_tools.py rename to modaletta/examples/manage_tools.py diff --git a/examples/modal_deployment.py b/modaletta/examples/modal_deployment.py similarity index 100% rename from examples/modal_deployment.py rename to modaletta/examples/modal_deployment.py diff --git a/examples/simple_custom_tool.py b/modaletta/examples/simple_custom_tool.py similarity index 100% rename from examples/simple_custom_tool.py rename to modaletta/examples/simple_custom_tool.py diff --git a/examples/test_tool_directly.py b/modaletta/examples/test_tool_directly.py similarity index 100% rename from examples/test_tool_directly.py rename to modaletta/examples/test_tool_directly.py diff --git a/pyproject.toml b/modaletta/pyproject.toml similarity index 100% rename from pyproject.toml rename to modaletta/pyproject.toml diff --git a/src/modaletta/README_digest.md b/modaletta/src/modaletta/README_digest.md similarity index 100% rename from src/modaletta/README_digest.md rename to modaletta/src/modaletta/README_digest.md diff --git a/src/modaletta/__init__.py b/modaletta/src/modaletta/__init__.py similarity index 100% rename from src/modaletta/__init__.py rename to modaletta/src/modaletta/__init__.py diff --git a/src/modaletta/agent.py b/modaletta/src/modaletta/agent.py similarity index 100% rename from src/modaletta/agent.py rename to modaletta/src/modaletta/agent.py diff --git a/src/modaletta/cli.py b/modaletta/src/modaletta/cli.py similarity index 100% rename from src/modaletta/cli.py rename to modaletta/src/modaletta/cli.py diff --git a/src/modaletta/client.py b/modaletta/src/modaletta/client.py similarity index 97% rename from src/modaletta/client.py rename to modaletta/src/modaletta/client.py index 118159f..bd202fb 100644 --- a/src/modaletta/client.py +++ b/modaletta/src/modaletta/client.py @@ -171,8 +171,8 @@ def get_agent_memory(self, agent_id: str) -> Dict[str, Any]: Returns: Agent memory blocks as a dict of {label: value}. """ - blocks = self.letta_client.agents.blocks.list(agent_id) - return {block.label: block.value for block in blocks} + memory = self.letta_client.agents.core_memory.retrieve(agent_id) + return memory.model_dump() def update_agent_memory( self, diff --git a/src/modaletta/config.py b/modaletta/src/modaletta/config.py similarity index 100% rename from src/modaletta/config.py rename to modaletta/src/modaletta/config.py diff --git a/src/modaletta/digest.py b/modaletta/src/modaletta/digest.py similarity index 100% rename from src/modaletta/digest.py rename to modaletta/src/modaletta/digest.py diff --git a/src/modaletta/digest_config.yaml b/modaletta/src/modaletta/digest_config.yaml similarity index 100% rename from src/modaletta/digest_config.yaml rename to modaletta/src/modaletta/digest_config.yaml diff --git a/tests/__init__.py b/modaletta/tests/__init__.py similarity index 100% rename from tests/__init__.py rename to modaletta/tests/__init__.py diff --git a/tests/test_client.py b/modaletta/tests/test_client.py similarity index 89% rename from tests/test_client.py rename to modaletta/tests/test_client.py index 6fcb143..65aeea7 100644 --- a/tests/test_client.py +++ b/modaletta/tests/test_client.py @@ -33,8 +33,6 @@ def mock_letta_client() -> Mock: mock_client.agents.memory = Mock() mock_client.agents.memory.get = Mock() mock_client.agents.memory.update = Mock() - mock_client.agents.blocks = Mock() - mock_client.agents.blocks.list = Mock() return mock_client @@ -149,20 +147,17 @@ def test_get_agent_memory( mock_letta_client: Mock ) -> None: """Test getting agent memory.""" - mock_human_block = Mock() - mock_human_block.label = "human" - mock_human_block.value = "Test user" - mock_persona_block = Mock() - mock_persona_block.label = "persona" - mock_persona_block.value = "Test assistant" - mock_letta_client.agents.blocks.list.return_value = [mock_human_block, mock_persona_block] + mock_memory = Mock() + mock_memory.model_dump.return_value = { + "human": {"value": "Test user"}, + "persona": {"value": "Test assistant"} + } + mock_letta_client.agents.memory.get.return_value = mock_memory mock_letta_class.return_value = mock_letta_client client = ModalettaClient(mock_config) memory = client.get_agent_memory("test-agent-id") assert "human" in memory - assert memory["human"] == "Test user" assert "persona" in memory - assert memory["persona"] == "Test assistant" - mock_letta_client.agents.blocks.list.assert_called_once_with("test-agent-id") \ No newline at end of file + mock_letta_client.agents.memory.get.assert_called_once_with("test-agent-id") diff --git a/tests/test_config.py b/modaletta/tests/test_config.py similarity index 100% rename from tests/test_config.py rename to modaletta/tests/test_config.py