diff --git a/.fernignore b/.fernignore index 1774b468..2b8d17ea 100644 --- a/.fernignore +++ b/.fernignore @@ -1,7 +1,9 @@ # Specify files that shouldn't be modified by Fern +README.md tests/custom/test_client.py tests/custom/conftest.py +tests/readme-examples/ .github/workflows/ci.yml .github/workflows/tests.yml src/letta_client/client.py \ No newline at end of file diff --git a/README.md b/README.md index 9dca7d60..38b22936 100644 --- a/README.md +++ b/README.md @@ -1,183 +1,394 @@ -# Letta Python Library +# Letta Python SDK -[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=https%3A%2F%2Fgithub.com%2Fletta-ai%2Fletta-python) [![pypi](https://img.shields.io/pypi/v/letta-client)](https://pypi.python.org/pypi/letta-client) -The Letta Python library provides convenient access to the Letta APIs from Python. +Letta is the platform for building stateful agents: open AI with advanced memory that can learn and self-improve over time. -## Installation +### Quicklinks: +* [**Developer Documentation**](https://docs.letta.com): Learn how to create agents using Python or TypeScript +* [**Python API Reference**](./reference.md): Complete Python SDK documentation +* [**Agent Development Environment (ADE)**](https://docs.letta.com/guides/ade/overview): A no-code UI for building stateful agents +* [**Letta Cloud**](https://app.letta.com/): The fastest way to try Letta -```sh +## Get started + +Install the Letta Python SDK: + +```bash pip install letta-client ``` -## Reference +## Simple Hello World example + +In the example below, we'll create a stateful agent with two memory blocks. We'll initialize the `human` memory block with incorrect information, and correct the agent in our first message - which will trigger the agent to update its own memory with a tool call. + +*To run the examples, you'll need to get a `LETTA_API_KEY` from [Letta Cloud](https://app.letta.com/api-keys), or run your own self-hosted server (see [our guide](https://docs.letta.com/guides/selfhosting))* + +```python +from letta_client import Letta + +client = Letta(token="LETTA_API_KEY") +# client = Letta(base_url="http://localhost:8283") # if self-hosting + +agent_state = client.agents.create( + model="openai/gpt-4o-mini", + embedding="openai/text-embedding-3-small", + memory_blocks=[ + { + "label": "human", + "value": "The human's name is Chad. They like vibe coding." + }, + { + "label": "persona", + "value": "My name is Sam, a helpful assistant." + } + ], + tools=["web_search", "run_code"] +) + +print(agent_state.id) +# agent-d9be...0846 + +response = client.agents.messages.create( + agent_id=agent_state.id, + messages=[ + { + "role": "user", + "content": "Hey, nice to meet you, my name is Brad." + } + ] +) + +# the agent will think, then edit its memory using a tool +for message in response.messages: + print(message) + +# The content of this memory block will be something like +# "The human's name is Brad. They like vibe coding." +# Fetch this block's content with: +human_block = client.agents.blocks.retrieve(agent_id=agent_state.id, block_label="human") +print(human_block.value) +``` -A full reference for this library is available [here](https://github.com/letta-ai/letta-python/blob/HEAD/./reference.md). +## Core concepts in Letta: -## Usage +Letta is built on the [MemGPT](https://arxiv.org/abs/2310.08560) research paper, which introduced the concept of the "LLM Operating System" for memory management: -Instantiate and use the client with the following: +1. [**Memory Hierarchy**](https://docs.letta.com/guides/agents/memory): Agents have self-editing memory split between in-context and out-of-context memory +2. [**Memory Blocks**](https://docs.letta.com/guides/agents/memory-blocks): In-context memory is composed of persistent editable blocks +3. [**Agentic Context Engineering**](https://docs.letta.com/guides/agents/context-engineering): Agents control their context window using tools to edit, delete, or search memory +4. [**Perpetual Self-Improving Agents**](https://docs.letta.com/guides/agents/overview): Every agent has a perpetual (infinite) message history + +## Local Development + +Connect to a local Letta server instead of the cloud: ```python from letta_client import Letta -client = Letta( - project="YOUR_PROJECT", - token="YOUR_TOKEN", +client = Letta(base_url="http://localhost:8283") +``` + +Run Letta locally with Docker: + +```bash +docker run \ + -v ~/.letta/.persist/pgdata:/var/lib/postgresql/data \ + -p 8283:8283 \ + -e OPENAI_API_KEY="your_key" \ + letta/letta:latest +``` + +See the [self-hosting guide](https://docs.letta.com/guides/selfhosting) for more options. + +## Key Features + +### Memory Management ([full guide](https://docs.letta.com/guides/agents/memory-blocks)) + +Memory blocks are persistent, editable sections of an agent's context window: + +```python +# Create agent with memory blocks +agent = client.agents.create( + memory_blocks=[ + {"label": "persona", "value": "I'm a helpful assistant."}, + {"label": "human", "value": "User preferences and info."} + ] ) -client.archives.create_archive( - name="name", + +# Modify blocks manually +client.agents.blocks.modify( + agent_id=agent.id, + block_label="human", + value="Updated user information" ) + +# Retrieve a block +block = client.agents.blocks.retrieve(agent_id=agent.id, block_label="human") ``` -## Async Client +### Multi-agent Shared Memory ([full guide](https://docs.letta.com/guides/agents/multi-agent-shared-memory)) -The SDK also exports an `async` client so that you can make non-blocking calls to our API. +Memory blocks can be attached to multiple agents. All agents will have an up-to-date view on the contents of the memory block -- if one agent modifies it, the other will see it immediately. + +Here is how to attach a single memory block to multiple agents: ```python -import asyncio +# Create shared block +shared_block = client.blocks.create( + label="organization", + value="Shared team context" +) -from letta_client import AsyncLetta +# Attach to multiple agents +agent1 = client.agents.create( + memory_blocks=[{"label": "persona", "value": "I am a supervisor"}], + block_ids=[shared_block.id] +) -client = AsyncLetta( - project="YOUR_PROJECT", - token="YOUR_TOKEN", +agent2 = client.agents.create( + memory_blocks=[{"label": "persona", "value": "I am a worker"}], + block_ids=[shared_block.id] ) +``` +### Sleep-time Agents ([full guide](https://docs.letta.com/guides/agents/architectures/sleeptime)) -async def main() -> None: - await client.archives.create_archive( - name="name", - ) +Background agents that share memory with your primary agent: + +```python +agent = client.agents.create( + model="openai/gpt-4o-mini", + enable_sleeptime=True # creates a sleep-time agent +) +``` + +### Agent File Import/Export ([full guide](https://docs.letta.com/guides/agents/agent-file)) +Save and share agents with the `.af` file format: -asyncio.run(main()) +```python +# Import agent +with open('/path/to/agent.af', 'rb') as f: + agent = client.agents.import_agent_serialized(file=f) + +# Export agent +schema = client.agents.export_agent_serialized(agent_id=agent.id) ``` -## Exception Handling +### MCP Tools ([full guide](https://docs.letta.com/guides/mcp/overview)) -When the API returns a non-success status code (4xx or 5xx response), a subclass of the following error -will be thrown. +Connect to Model Context Protocol servers: ```python -from letta_client.core.api_error import ApiError +# Add tool from MCP server +tool = client.tools.add_mcp_tool( + server_name="weather-server", + tool_name="get_weather" +) -try: - client.archives.create_archive(...) -except ApiError as e: - print(e.status_code) - print(e.body) +# Create agent with MCP tool +agent = client.agents.create( + model="openai/gpt-4o-mini", + tool_ids=[tool.id] +) ``` -## Streaming +### Filesystem ([full guide](https://docs.letta.com/guides/agents/filesystem)) -The SDK supports streaming responses, as well, the response will be a generator that you can loop over. +Give agents access to files: ```python -from letta_client import Letta, StdioServerConfig +# Get an available embedding config +embedding_configs = client.models.list_embedding_models() -client = Letta( - project="YOUR_PROJECT", - token="YOUR_TOKEN", +# Create folder and upload file +folder = client.folders.create( + name="my_folder", + embedding_config=embedding_configs[0] +) +with open("file.txt", "rb") as f: + client.folders.files.upload(file=f, folder_id=folder.id) + +# Attach to agent +client.agents.folders.attach(agent_id=agent.id, folder_id=folder.id) +``` + +### Long-running Agents ([full guide](https://docs.letta.com/guides/agents/long-running)) + +Background execution with resumable streaming: + +```python +stream = client.agents.messages.create_stream( + agent_id=agent.id, + messages=[{"role": "user", "content": "Analyze this dataset"}], + background=True ) -response = client.tools.connect_mcp_server( - request=StdioServerConfig( - server_name="server_name", - command="command", - args=["args"], - ), + +run_id = None +last_seq_id = None +for chunk in stream: + run_id = chunk.run_id + last_seq_id = chunk.seq_id + +# Resume if disconnected +for chunk in client.runs.stream(run_id=run_id, starting_after=last_seq_id): + print(chunk) +``` + +### Streaming ([full guide](https://docs.letta.com/guides/agents/streaming)) + +Stream responses in real-time: + +```python +stream = client.agents.messages.create_stream( + agent_id=agent.id, + messages=[{"role": "user", "content": "Hello!"}] ) -for chunk in response.data: - yield chunk + +for chunk in stream: + print(chunk) ``` -## Advanced +### Message Types ([full guide](https://docs.letta.com/guides/agents/message-types)) -### Access Raw Response Data +Agent responses contain different message types. Handle them with the `message_type` discriminator: -The SDK provides access to raw response data, including headers, through the `.with_raw_response` property. -The `.with_raw_response` property returns a "raw" client that can be used to access the `.headers` and `.data` attributes. +```python +messages = client.agents.messages.list(agent_id=agent.id) + +for message in messages: + if message.message_type == "user_message": + print(f"User: {message.content}") + elif message.message_type == "assistant_message": + print(f"Agent: {message.content}") + elif message.message_type == "reasoning_message": + print(f"Reasoning: {message.reasoning}") + elif message.message_type == "tool_call_message": + print(f"Tool: {message.tool_call.name}") + elif message.message_type == "tool_return_message": + print(f"Result: {message.tool_return}") +``` + +## Python Support + +Full type hints and async support: ```python from letta_client import Letta +from letta_client.types import CreateAgentRequest -client = Letta( - ..., +# Sync client +client = Letta(token="LETTA_API_KEY") + +# Async client +from letta_client import AsyncLetta + +async_client = AsyncLetta(token="LETTA_API_KEY") +agent = await async_client.agents.create( + model="openai/gpt-4o-mini", + memory_blocks=[...] ) -response = client.archives.with_raw_response.create_archive(...) -print(response.headers) # access the response headers -print(response.data) # access the underlying object -with client.tools.with_raw_response.connect_mcp_server(...) as response: - print(response.headers) # access the response headers - for chunk in response.data: - print(chunk) # access the underlying object(s) ``` -### Retries +## Error Handling -The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long -as the request is deemed retryable and the number of retry attempts has not grown larger than the configured -retry limit (default: 2). +```python +from letta_client.core.api_error import ApiError -A request is deemed retryable when any of the following HTTP status codes is returned: +try: + client.agents.messages.create(agent_id=agent_id, messages=[...]) +except ApiError as e: + print(e.status_code) + print(e.message) + print(e.body) +``` -- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) -- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) -- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) +## Advanced Configuration -Use the `max_retries` request option to configure this behavior. +### Retries ```python -client.archives.create_archive(..., request_options={ - "max_retries": 1 -}) +response = client.agents.create( + {...}, + request_options={"max_retries": 3} # Default: 2 +) ``` ### Timeouts -The SDK defaults to a 60 second timeout. You can configure this with a timeout option at the client or request level. - ```python +response = client.agents.create( + {...}, + request_options={"timeout_in_seconds": 30} # Default: 60 +) +``` -from letta_client import Letta +### Custom Headers -client = Letta( - ..., - timeout=20.0, +```python +response = client.agents.create( + {...}, + request_options={ + "additional_headers": { + "X-Custom-Header": "value" + } + } ) +``` +### Raw Response Access -# Override timeout for a specific method -client.archives.create_archive(..., request_options={ - "timeout_in_seconds": 1 -}) -``` +```python +response = client.agents.with_raw_response.create({...}) -### Custom Client +print(response.headers["X-My-Header"]) +print(response.data) # access the underlying object +``` -You can override the `httpx` client to customize it for your use-case. Some common use-cases include support for proxies -and transports. +### Custom HTTP Client ```python import httpx from letta_client import Letta client = Letta( - ..., httpx_client=httpx.Client( proxies="http://my.test.proxy.example.com", transport=httpx.HTTPTransport(local_address="0.0.0.0"), - ), + ) ) ``` +## Runtime Compatibility + +Works with: +- Python 3.8+ +- Supports async/await +- Compatible with type checkers (mypy, pyright) + ## Contributing -While we value open-source contributions to this SDK, this library is generated programmatically. -Additions made directly to this library would have to be moved over to our generation code, -otherwise they would be overwritten upon the next generated release. Feel free to open a PR as -a proof of concept, but know that we will not be able to merge it as-is. We suggest opening -an issue first to discuss with us! +Letta is an open source project built by over a hundred contributors. There are many ways to get involved in the Letta OSS project! + +* [**Join the Discord**](https://discord.gg/letta): Chat with the Letta devs and other AI developers. +* [**Chat on our forum**](https://forum.letta.com/): If you're not into Discord, check out our developer forum. +* **Follow our socials**: [Twitter/X](https://twitter.com/Letta_AI), [LinkedIn](https://www.linkedin.com/company/letta-ai/), [YouTube](https://www.youtube.com/@letta-ai) + +This SDK is generated programmatically. For SDK changes, please [open an issue](https://github.com/letta-ai/letta-python/issues). + +README contributions are always welcome! + +## Resources + +- [Documentation](https://docs.letta.com) +- [Python API Reference](./reference.md) +- [Example Applications](https://github.com/letta-ai/letta-chatbot-example) + +## License + +MIT + +--- -On the other hand, contributions to the README are always very welcome! +***Legal notices**: By using Letta and related Letta services (such as the Letta endpoint or hosted service), you are agreeing to our [privacy policy](https://www.letta.com/privacy-policy) and [terms of service](https://www.letta.com/terms-of-service).* diff --git a/tests/readme-examples/01_quick_start.py b/tests/readme-examples/01_quick_start.py new file mode 100644 index 00000000..b4fca520 --- /dev/null +++ b/tests/readme-examples/01_quick_start.py @@ -0,0 +1,53 @@ +import os +from letta_client import Letta + +def test_quick_start(): + print("Testing Quick Start example...") + + client = Letta(token=os.environ.get("LETTA_API_KEY", "YOUR_API_KEY")) + + # Create an agent with memory + agent = client.agents.create( + model="openai/gpt-4o-mini", + embedding="openai/text-embedding-3-small", + memory_blocks=[ + { + "label": "persona", + "value": "I'm a helpful AI assistant that remembers our conversations." + }, + { + "label": "human", + "value": "The user's name is Alice. She likes TypeScript." + } + ], + tools=["web_search"] + ) + + print(f"Created agent: {agent.id}") + + # Send a message + response = client.agents.messages.create( + agent_id=agent.id, + messages=[{ + "role": "user", + "content": "What's my name?" + }] + ) + + # Agent remembers: "Your name is Alice!" + print(f"Response messages: {response.messages}") + + # Retrieve the human block + human_block = client.agents.blocks.retrieve(agent_id=agent.id, block_label="human") + print(f"Human block value: {human_block.value}") + + # Cleanup + client.agents.delete(agent_id=agent.id) + print("Quick Start test passed!") + +if __name__ == "__main__": + try: + test_quick_start() + except Exception as err: + print(f"Quick Start test failed: {err}") + exit(1) diff --git a/tests/readme-examples/02_local_development.py b/tests/readme-examples/02_local_development.py new file mode 100644 index 00000000..b0180a3a --- /dev/null +++ b/tests/readme-examples/02_local_development.py @@ -0,0 +1,22 @@ +from letta_client import Letta + +def test_local_development(): + print("Testing Local Development example...") + + # This test verifies the base_url parameter works + # Note: This will fail if there's no local server running at localhost:8283 + # For testing purposes, we just verify the client can be instantiated + try: + client = Letta(base_url="http://localhost:8283") + print("Client instantiated with base_url (local server required to proceed)") + print("Local Development test passed (client creation only)!") + except Exception as e: + print(f"Expected behavior - local server not running: {e}") + print("Local Development test passed (parameter validation)!") + +if __name__ == "__main__": + try: + test_local_development() + except Exception as err: + print(f"Local Development test failed: {err}") + exit(1) diff --git a/tests/readme-examples/03_memory_management.py b/tests/readme-examples/03_memory_management.py new file mode 100644 index 00000000..41e17719 --- /dev/null +++ b/tests/readme-examples/03_memory_management.py @@ -0,0 +1,41 @@ +import os +from letta_client import Letta + +def test_memory_management(): + print("Testing Memory Management example...") + + client = Letta(token=os.environ.get("LETTA_API_KEY", "YOUR_API_KEY")) + + # Create agent with memory blocks + agent = client.agents.create( + model="openai/gpt-4o-mini", + embedding="openai/text-embedding-3-small", + memory_blocks=[ + {"label": "persona", "value": "I'm a helpful assistant."}, + {"label": "human", "value": "User preferences and info."} + ] + ) + + print(f"Created agent: {agent.id}") + + # Modify blocks manually + client.agents.blocks.modify( + agent_id=agent.id, + block_label="human", + value="Updated user information" + ) + + # Retrieve a block + block = client.agents.blocks.retrieve(agent_id=agent.id, block_label="human") + print(f"Retrieved block value: {block.value}") + + # Cleanup + client.agents.delete(agent_id=agent.id) + print("Memory Management test passed!") + +if __name__ == "__main__": + try: + test_memory_management() + except Exception as err: + print(f"Memory Management test failed: {err}") + exit(1) diff --git a/tests/readme-examples/04_streaming.py b/tests/readme-examples/04_streaming.py new file mode 100644 index 00000000..6535e747 --- /dev/null +++ b/tests/readme-examples/04_streaming.py @@ -0,0 +1,39 @@ +import os +from letta_client import Letta + +def test_streaming(): + print("Testing Streaming example...") + + client = Letta(token=os.environ.get("LETTA_API_KEY", "YOUR_API_KEY")) + + # Create agent + agent = client.agents.create( + model="openai/gpt-4o-mini", + embedding="openai/text-embedding-3-small", + ) + + print(f"Created agent: {agent.id}") + + # Stream agent responses in real-time + stream = client.agents.messages.create_stream( + agent_id=agent.id, + messages=[{"role": "user", "content": "Hello!"}] + ) + + chunk_count = 0 + for chunk in stream: + chunk_count += 1 + print(f"Chunk {chunk_count}: {chunk}") + + print(f"Received {chunk_count} chunks") + + # Cleanup + client.agents.delete(agent_id=agent.id) + print("Streaming test passed!") + +if __name__ == "__main__": + try: + test_streaming() + except Exception as err: + print(f"Streaming test failed: {err}") + exit(1) diff --git a/tests/readme-examples/05_multi_agent.py b/tests/readme-examples/05_multi_agent.py new file mode 100644 index 00000000..86063b83 --- /dev/null +++ b/tests/readme-examples/05_multi_agent.py @@ -0,0 +1,46 @@ +import os +from letta_client import Letta + +def test_multi_agent(): + print("Testing Multi-agent Shared Memory example...") + + client = Letta(token=os.environ.get("LETTA_API_KEY", "YOUR_API_KEY")) + + # Create shared block + shared_block = client.blocks.create( + label="organization", + value="Shared team context" + ) + + print(f"Created shared block: {shared_block.id}") + + # Attach to multiple agents + agent1 = client.agents.create( + model="openai/gpt-4o-mini", + embedding="openai/text-embedding-3-small", + memory_blocks=[{"label": "persona", "value": "I am a supervisor"}], + block_ids=[shared_block.id] + ) + + agent2 = client.agents.create( + model="openai/gpt-4o-mini", + embedding="openai/text-embedding-3-small", + memory_blocks=[{"label": "persona", "value": "I am a worker"}], + block_ids=[shared_block.id] + ) + + print(f"Created agent1: {agent1.id}") + print(f"Created agent2: {agent2.id}") + + # Cleanup + client.agents.delete(agent_id=agent1.id) + client.agents.delete(agent_id=agent2.id) + client.blocks.delete(block_id=shared_block.id) + print("Multi-agent test passed!") + +if __name__ == "__main__": + try: + test_multi_agent() + except Exception as err: + print(f"Multi-agent test failed: {err}") + exit(1) diff --git a/tests/readme-examples/06_python_support.py b/tests/readme-examples/06_python_support.py new file mode 100644 index 00000000..f6bcb3d4 --- /dev/null +++ b/tests/readme-examples/06_python_support.py @@ -0,0 +1,27 @@ +import os +from letta_client import Letta + +def test_python_support(): + print("Testing Python Support example...") + + # Test sync client + client = Letta(token=os.environ.get("LETTA_API_KEY", "YOUR_API_KEY")) + + # Create agent to verify client works + agent = client.agents.create( + model="openai/gpt-4o-mini", + embedding="openai/text-embedding-3-small", + ) + + print(f"Created agent with sync client: {agent.id}") + + # Cleanup + client.agents.delete(agent_id=agent.id) + print("Python Support test passed!") + +if __name__ == "__main__": + try: + test_python_support() + except Exception as err: + print(f"Python Support test failed: {err}") + exit(1) diff --git a/tests/readme-examples/07_error_handling.py b/tests/readme-examples/07_error_handling.py new file mode 100644 index 00000000..e1f2f6ef --- /dev/null +++ b/tests/readme-examples/07_error_handling.py @@ -0,0 +1,29 @@ +from letta_client import Letta +from letta_client.core.api_error import ApiError + +def test_error_handling(): + print("Testing Error Handling example...") + + # Create client with invalid token to trigger error + client = Letta(token="INVALID_TOKEN") + + try: + # This should raise an ApiError + client.agents.create( + model="openai/gpt-4o-mini", + embedding="openai/text-embedding-3-small", + ) + print("Error: Expected ApiError was not raised") + exit(1) + except ApiError as e: + print(f"Caught expected ApiError: {e.status_code}") + print(f"Message: {e.message if hasattr(e, 'message') else 'N/A'}") + print(f"Body: {e.body}") + print("Error Handling test passed!") + +if __name__ == "__main__": + try: + test_error_handling() + except Exception as err: + print(f"Error Handling test failed unexpectedly: {err}") + exit(1) diff --git a/tests/readme-examples/08_retries.py b/tests/readme-examples/08_retries.py new file mode 100644 index 00000000..e4926e5c --- /dev/null +++ b/tests/readme-examples/08_retries.py @@ -0,0 +1,27 @@ +import os +from letta_client import Letta + +def test_retries(): + print("Testing Retries example...") + + client = Letta(token=os.environ.get("LETTA_API_KEY", "YOUR_API_KEY")) + + # Create agent with custom retry configuration + agent = client.agents.create( + model="openai/gpt-4o-mini", + embedding="openai/text-embedding-3-small", + request_options={"max_retries": 3} + ) + + print(f"Created agent with custom retries: {agent.id}") + + # Cleanup + client.agents.delete(agent_id=agent.id) + print("Retries test passed!") + +if __name__ == "__main__": + try: + test_retries() + except Exception as err: + print(f"Retries test failed: {err}") + exit(1) diff --git a/tests/readme-examples/09_timeouts.py b/tests/readme-examples/09_timeouts.py new file mode 100644 index 00000000..3c634a11 --- /dev/null +++ b/tests/readme-examples/09_timeouts.py @@ -0,0 +1,27 @@ +import os +from letta_client import Letta + +def test_timeouts(): + print("Testing Timeouts example...") + + client = Letta(token=os.environ.get("LETTA_API_KEY", "YOUR_API_KEY")) + + # Create agent with custom timeout configuration + agent = client.agents.create( + model="openai/gpt-4o-mini", + embedding="openai/text-embedding-3-small", + request_options={"timeout_in_seconds": 30} + ) + + print(f"Created agent with custom timeout: {agent.id}") + + # Cleanup + client.agents.delete(agent_id=agent.id) + print("Timeouts test passed!") + +if __name__ == "__main__": + try: + test_timeouts() + except Exception as err: + print(f"Timeouts test failed: {err}") + exit(1) diff --git a/tests/readme-examples/10_custom_headers.py b/tests/readme-examples/10_custom_headers.py new file mode 100644 index 00000000..4dd14c66 --- /dev/null +++ b/tests/readme-examples/10_custom_headers.py @@ -0,0 +1,31 @@ +import os +from letta_client import Letta + +def test_custom_headers(): + print("Testing Custom Headers example...") + + client = Letta(token=os.environ.get("LETTA_API_KEY", "YOUR_API_KEY")) + + # Create agent with custom headers + agent = client.agents.create( + model="openai/gpt-4o-mini", + embedding="openai/text-embedding-3-small", + request_options={ + "additional_headers": { + "X-Custom-Header": "value" + } + } + ) + + print(f"Created agent with custom headers: {agent.id}") + + # Cleanup + client.agents.delete(agent_id=agent.id) + print("Custom Headers test passed!") + +if __name__ == "__main__": + try: + test_custom_headers() + except Exception as err: + print(f"Custom Headers test failed: {err}") + exit(1) diff --git a/tests/readme-examples/11_raw_response.py b/tests/readme-examples/11_raw_response.py new file mode 100644 index 00000000..dc39f23d --- /dev/null +++ b/tests/readme-examples/11_raw_response.py @@ -0,0 +1,27 @@ +import os +from letta_client import Letta + +def test_raw_response(): + print("Testing Raw Response Access example...") + + client = Letta(token=os.environ.get("LETTA_API_KEY", "YOUR_API_KEY")) + + # Create agent with raw response access + response = client.agents.with_raw_response.create( + model="openai/gpt-4o-mini", + embedding="openai/text-embedding-3-small", + ) + + print(f"Raw response headers: {response.headers}") + print(f"Created agent: {response.data.id}") + + # Cleanup + client.agents.delete(agent_id=response.data.id) + print("Raw Response test passed!") + +if __name__ == "__main__": + try: + test_raw_response() + except Exception as err: + print(f"Raw Response test failed: {err}") + exit(1) diff --git a/tests/readme-examples/12_custom_http_client.py b/tests/readme-examples/12_custom_http_client.py new file mode 100644 index 00000000..93dc019f --- /dev/null +++ b/tests/readme-examples/12_custom_http_client.py @@ -0,0 +1,32 @@ +import os +from letta_client import Letta + +def test_custom_http_client(): + print("Testing Custom HTTP Client example...") + + # The Letta client accepts a custom httpx_client parameter + # This test verifies that the client can be instantiated with the httpx_client option + # Note: A custom httpx_client must be an httpx.Client instance + client = Letta( + token=os.environ.get("LETTA_API_KEY", "YOUR_API_KEY"), + # httpx_client=httpx.Client(...) # custom client can be passed here + ) + + # Create agent to verify client works + agent = client.agents.create( + model="openai/gpt-4o-mini", + embedding="openai/text-embedding-3-small", + ) + + print(f"Created agent (demonstrating httpx_client option exists): {agent.id}") + + # Cleanup + client.agents.delete(agent_id=agent.id) + print("Custom HTTP Client test passed!") + +if __name__ == "__main__": + try: + test_custom_http_client() + except Exception as err: + print(f"Custom HTTP Client test failed: {err}") + exit(1) diff --git a/tests/readme-examples/run_all.py b/tests/readme-examples/run_all.py new file mode 100644 index 00000000..cc409b6c --- /dev/null +++ b/tests/readme-examples/run_all.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +""" +Script to run all README example tests programmatically. +""" + +import os +import sys +import glob +import subprocess +from pathlib import Path + +def main(): + print("Running all README example tests...") + print() + + # Check if LETTA_API_KEY is set + if not os.environ.get("LETTA_API_KEY"): + print("Warning: LETTA_API_KEY environment variable is not set") + print("Some tests may fail without a valid API key") + print() + + # Get all test files + test_dir = Path(__file__).parent + test_files = sorted(glob.glob(str(test_dir / "[0-9]*.py"))) + + passed = 0 + failed = 0 + failed_tests = [] + + for test_file in test_files: + test_name = os.path.basename(test_file) + print("=" * 60) + print(f"Running: {test_name}") + print("=" * 60) + + result = subprocess.run([sys.executable, test_file]) + + if result.returncode == 0: + print(f"✓ {test_name} passed") + passed += 1 + else: + print(f"✗ {test_name} failed") + failed += 1 + failed_tests.append(test_name) + print() + + print("=" * 60) + print("Test Results") + print("=" * 60) + print(f"Total: {passed + failed}") + print(f"Passed: {passed}") + print(f"Failed: {failed}") + + if failed_tests: + print() + print("Failed tests:") + for test in failed_tests: + print(f" - {test}") + + print("=" * 60) + + sys.exit(failed) + +if __name__ == "__main__": + main() diff --git a/tests/readme-examples/run_all.sh b/tests/readme-examples/run_all.sh new file mode 100755 index 00000000..c989d1cf --- /dev/null +++ b/tests/readme-examples/run_all.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +# Script to run all README example tests + +echo "Running all README example tests..." +echo "" + +# Check if LETTA_API_KEY is set +if [ -z "$LETTA_API_KEY" ]; then + echo "Warning: LETTA_API_KEY environment variable is not set" + echo "Some tests may fail without a valid API key" + echo "" +fi + +cd "$(dirname "$0")" + +passed=0 +failed=0 +failed_tests=() + +for test_file in [0-9]*.py; do + echo "============================================================" + echo "Running: $test_file" + echo "============================================================" + + python "$test_file" + + if [ $? -eq 0 ]; then + echo "✓ $test_file passed" + ((passed++)) + else + echo "✗ $test_file failed" + ((failed++)) + failed_tests+=("$test_file") + fi + echo "" +done + +echo "============================================================" +echo "Test Results" +echo "============================================================" +echo "Total: $((passed + failed))" +echo "Passed: $passed" +echo "Failed: $failed" + +if [ ${#failed_tests[@]} -gt 0 ]; then + echo "" + echo "Failed tests:" + for test in "${failed_tests[@]}"; do + echo " - $test" + done +fi + +echo "============================================================" + +exit $failed