# elluminate > elluminate is a platform for building, evaluating, and optimizing LLM applications with confidence. Whether you're fine-tuning prompts, implementing RAG systems, or deploying production AI applications, Elluminate provides the tools you need to ensure quality and reliability. # Introduction # Welcome to elluminate *The Decision Layer for Reliable AI* elluminate is the collaborative evaluation platform for teams who need to build, launch, and scale AI products with unshakable confidence. [Get Started!](https://app.elluminate.de) [Visit ellamind.com](https://www.ellamind.com) # Key Features - **Supercharge Your AI Prompts** Systematically test, evaluate, and optimize your LLM prompts to get consistently better results. - **Quality Assurance Built-in** Automatically rate and validate AI responses against custom criteria, ensuring your outputs meet your standards. - **Template-Driven Efficiency** Create reusable prompt templates with variables, making it easy to scale and maintain your AI interactions. - **Data-Driven Insights** Track and analyze prompt performance metrics to understand what works best and continuously improve your AI outputs. elluminate is trusted by AI teams to: - Reduce development time - Improve response quality by systematic evaluation - Catch issues before they reach production - Scale AI applications with confidence Ready to transform how you build AI applications? [Get started now →](https://docs.elluminate.de/get_started/quick_start/index.md) # Get Started # API Key Management *Learn how to securely create, manage, and use API keys for platform authentication* API keys are essential for authenticating your requests to the elluminate platform. This guide covers best practices for managing your API keys securely. **Note: only project admins have access to the "API Keys" dashboard.** ## **Creating API Keys** - Navigate to your project's "API Keys" dashboard - Click "Create new secret key" - Enter a descriptive name for your key - Set an expiration date (recommended) or choose "never" for no expiration - Save your key securely - it will only be shown once ## **Security Best Practices** ### Store Securely - Use environment variables to store API keys - Never commit API keys to version control - Don't expose keys in client-side code or browser - Keep keys out of logs and error messages ### Regular Rotation - Rotate keys on a regular schedule - Create new keys before old ones expire - Remove unused or expired keys - Use expiration dates when possible ### Access Control & Key Management - Find all your active keys - See when keys were last used - Check expiration dates - Edit key names - Delete/revoke keys ## **Using API Keys** You can use your API key by exporting it as an environment variable: ``` export ELLUMINATE_API_KEY= ``` Or you can set the API key as a parameter in the client: ``` from elluminate import Client client = Client(api_key="") ``` For more details on using the SDK, see our [Quick Start Guide](https://docs.elluminate.de/get_started/quick_start/index.md). ## **Troubleshooting** If you encounter issues with your API key: - Verify the key hasn't expired - Check if the key has been revoked - Ensure you're using the correct project ID - Confirm the key is being sent correctly in requests # Quick Start (GUI) *Start evaluating prompts with elluminate using the web interface in just a few minutes* No coding required! For a code-based approach, see our [SDK Quick Start](https://docs.elluminate.de/get_started/quick_start_sdk/index.md). ## Prerequisites You'll need: - An elluminate user account ## **Step 1: Log In and Access Your Project** Navigate to [app.elluminate.de](https://app.elluminate.de) or your custom deployment and log in with your credentials. ## **Step 2: Create a New Test Project** For this tutorial, we'll create a fresh project to experiment with. This keeps your test data separate from any existing work. 1. Click on the project name in the upper left corner (e.g., **My First Project**) 1. In the dropdown, click **Add project** 1. Fill in the **Project Name**: "Test Project" (or any name you prefer) 1. Optionally, fill in the **Description**: "A test project" 1. Click **Create Project** 1. You'll now see your new project with the empty **Prompt Templates** page displayed ## **Step 3: Create a Prompt Template** Create your first prompt template. You should already be on the Prompt Templates page from the previous step, but if not, click on **Prompt Templates** in the sidebar. 1. Click the **New Template** button in the upper right corner 1. Enter the **Template Name**: "Support Bot" 1. Enter the **User Message**: "You are a customer service agent. Provide helpful and friendly advice to the user query: {{user_query}}" 1. Click **Create Template** to create your template 1. You'll now see with **Templates Details** page displayed ## **Step 4: Test Your Template** Before creating evaluation criteria, let's quickly test our template using the Response Generator at the bottom of the Template Details page. 1. Scroll down to the **Response Generator** section at the bottom of the **Template Details** page, click to expand the section (it's collapsed by default), then click **Generate Response** to open the response generation form 1. Select an **LLM Configuration** from the dropdown (e.g., "Default GPT-5") 1. Enter a test value for the **user_query** variable; a simple greeting is fine to begin with: "Hi!" 1. Click **Generate** to see how your template performs 1. Review the generated response to ensure your template works as expected **Note:** The Response Generator allows you to quickly test your template with different variable values to ensure it works as expected before setting up systematic evaluation. ## **Step 5: Add Evaluation Criteria** The Criteria section offers three ways to add evaluation criteria to your template: - **Create New Criterion Set**: Full control when you need specific evaluation requirements - **Generate Criteria**: Quick start with AI-generated criteria tailored to your specific prompt - **Link Existing Criterion Set**: Save time by reusing a criterion set from your project 1. In your **Prompt Templates** view, locate the **Criterion Set** section below the **Response Generator** and click on **Create New Criterion Set** to enter your own evaluation criteria 1. Input a criterion for evaluating responses, such as: "Is the response helpful?" 1. Click the checkmark to save your criterion ## **Step 6: Create a Template Variables Collection** Now we need to create a collection to store test cases for systematic evaluation of our prompt template. 1. Navigate to **Collections** in the sidebar 1. Click **New Collection** to create a new collection 1. Enter the **Collection Name**: "Support Bot Test Cases" 1. Optionally, add a **Description**: "Test cases for evaluating our support bot template" 1. Click **Create Collection** to save your new collection Collections and Compliance packages Use **Collections** when you want to build your own template-variable test cases for a prompt template, as in this quick start. Use **Compliance** when you want to start from a curated regulatory or safety package that imports both test cases and evaluation criteria. See [Collections](https://docs.elluminate.de/guides/collections/index.md) and [Compliance](https://docs.elluminate.de/guides/compliance/index.md) for the full workflows. ## **Step 7: Add Template Variable Values** Now we add test cases to thoroughly evaluate our prompt template. First, we define the template variable used. 1. Click on the text field "Column 1" and rename it to the template variable "user_query" used in the prompt template. 1. Enter a first value in the text field below "Enter value", for example "Hi!". 1. Click the checkmark to the right to save your changes. From here, there are two ways to add more values for variables at the end of the collection: - **+ button**: Manually add values for variables one by one with full control over each value - **✨ (sparkles) button**: Generate values for variables automatically using your prompt template and AI For this tutorial, let's add test cases manually: 4. Click the **+ button** to manually add a value 1. Add a "user_query" value: "Hi! How is it going?" 1. Click **Save** to add this value to your collection Repeat this process to add more user queries to build a comprehensive test set: - What do you do? - Can you tell me more about a product? - How's the weather in Bremen? ## **Step 8: Run Your First Experiment** Now that we have all the components in place (prompt template, evaluation criteria, and test cases), let's run a full experiment to evaluate our template systematically. 1. Navigate to **Experiments** in the sidebar 1. Click **New Experiment** 1. Choose the experiment type **Generate & Evaluate** 1. In the setup flow, choose your **Collection**: "Support Bot Test Cases" 1. Select your **Prompt Template**: "Support Bot" (v1) 1. Select a **Model** (e.g., "Default GPT-5") 1. Choose your **evaluation criteria**. The criterion set linked to your prompt template is selected by default, but you can choose any criterion set compatible with your collection 1. Optionally, set an **Experiment Name**: "Support Bot Evaluation" (auto-generated if left blank) and a **Description (Optional)**: "Evaluating our support bot" 1. Optionally, review the Advanced Settings (keep the defaults for this tutorial): - **Rating Mode**: Detailed mode includes reasoning for each criterion and is recommended for better interpretability. Fast mode provides only ratings without explanations and may be slightly faster. - **Epochs**: Epochs allow you to run the same evaluation multiple times. This makes the statistics more reliable but can take some time. - **Rating Version**: Select the rating model version to use for evaluating responses. Defaults to the version set in your project settings. 10. Click **Create Experiment** to start the experiment 01. The experiment will automatically generate responses for all your test cases and evaluate them against your criteria ## **Step 9: View Your Evaluation Results** Once your experiment completes, you'll see comprehensive results showing how your prompt template performed across all test cases. ### Understanding Your Results **Key Metrics at the Top:** - **Overall Score**: The percentage of evaluation criteria that passed across all responses (in this example: 100%) - **Average Tokens**: Shows input tokens (↑) sent to the LLM and output tokens (↓) generated in responses - **Response Time**: Average time the LLM took to generate each response **Criteria Performance (Left Side):** The green bars show how well each evaluation criterion performed: - **100% means all responses passed that specific criterion** - **Lower percentages indicate areas where your prompt could be improved** - **Different criteria may have different success rates** - this helps you identify which aspects of your prompt work well and which need refinement **Distribution Charts (Right Side):** - **Output Tokens Distribution**: Shows the spread of response lengths - helps identify consistency - **Response Duration Distribution**: Shows timing patterns - useful for performance optimization - **Interactive Filtering**: Click on any bar in these charts to filter the Sample Navigator to show only responses within that range ### Analyzing Individual Responses **Sample Navigator (Bottom Section):** This powerful tool lets you examine each test case in detail: - **Navigation**: Use the arrow buttons or keyboard (←→) to browse through your test cases (the counter shows your current position) - **Markdown Toggle**: Switch between formatted and plain text views of responses - **Sort By**: Order results by rating, token count, or response time - **Filter**: Focus on specific criteria or hide perfect responses **Two Analysis Modes:** - **Detailed Analysis**: In-depth view of individual responses with full context and criterion-by-criterion breakdown - **Individual Responses**: Table view showing all responses at once for quick comparison ### Interpreting Your Results Use these results to understand your prompt template's performance: **If you see high scores (80%+ across criteria):** - Your prompt template is working well - Consider testing with more challenging or diverse examples - You can confidently use this template for similar tasks **If you see mixed or lower scores:** - Look at the criteria with lower percentages to identify improvement areas - Use the Sample Navigator to examine specific failing cases - Refine your prompt template based on common failure patterns - Run additional experiments to test your improvements **Important for all results:** - **Spot-check passing samples** - Even when responses pass your criteria, examine a random sample to verify your evaluation criteria align with what you actually consider successful - This helps ensure your criteria aren't too lenient or missing important quality aspects **Expanding Your Evaluation:** Regardless of your initial results, here are ways to improve your evaluation process: - Try different LLM configurations to compare performance - Add more diverse test cases to stress-test your template - Experiment with different evaluation criteria to capture other quality aspects ## **Next Steps** Now that you've completed your first evaluation using the web interface: - Set up [Experiment Schedules](https://docs.elluminate.de/guides/experiments/#scheduled-experiments) to automatically run evaluations on a regular basis and get notified if performance drops - Follow our [SDK Quick Start](https://docs.elluminate.de/get_started/quick_start_sdk/index.md) to learn how to run the same workflows using the elluminate CLI and Python SDK - Explore [Experiments](https://docs.elluminate.de/guides/experiments/index.md) to run systematic evaluations - Learn about [Criterion Sets](https://docs.elluminate.de/guides/criterion_set/index.md) to create custom evaluation criteria - Try [Batch Processing](https://docs.elluminate.de/guides/batch_processing/index.md) to evaluate multiple responses at once - Understand [Key Concepts](https://docs.elluminate.de/guides/the_basics/index.md) for a deeper dive into elluminate's features ## **What You've Accomplished** - ✅ Created a prompt template with placeholders - ✅ Tested your template using the Response Generator - ✅ Added evaluation criteria manually - ✅ Built a template variables collection with test cases - ✅ Added additional test variables manually - ✅ Run a complete experiment with systematic evaluation - ✅ Analyzed comprehensive results using advanced tools - ✅ Learned to interpret performance metrics and distributions You're now ready to scale up your evaluation workflows with elluminate! ## **Support** If you need help with the elluminate platform, contact us by email at [help@elluminate.de](mailto:help@elluminate.de). # Quick Start for Existing Projects *Understand the very basics of elluminate using the example project* This guide helps you quickly understand the basic components of elluminate using a sample project which will be shown to you after login. For creating a new project from scratch, see our [GUI Quick Start](https://docs.elluminate.de/get_started/quick_start/index.md). ## The Example Project After you log in, you'll see the project home of a sample project for a support bot that we provide for you to explore and experiment with. An experiment consists of a prompt used to generate responses with an LLM and the evaluation of these responses against some criteria. The prompt is built from a [prompt template](https://docs.elluminate.de/guides/prompt_templates/index.md) and a [collection](https://docs.elluminate.de/guides/collections/index.md). The prompt template defines the structure of the prompt and can contain placeholders, while the data in a collection ([template variables](https://docs.elluminate.de/guides/prompt_templates/#template-details)) is used to fill these placeholders. The criteria against which the responses should be tested are defined in a [criterion set](https://docs.elluminate.de/guides/criterion_set/index.md). Each criterion answers a simple yes/no question on whether the response passed its check. Template variables can also be used to fill in placeholders of criteria, which allows for flexible tests. ### Project Home The Project Home is the first page you will see after logging in. Here you will see your project dashboard which will give you a quick overview of all of your project's most important information. ### Example Prompt Template A prompt template for a simple support bot has already been created for this project. You can take a look at it by navigating to Prompt Templates in the sidebar. It has only one placeholder *user_input*, which is simply the query the support bot should handle. If you want to work with prompt templates, take a look at [prompt templates](https://docs.elluminate.de/guides/prompt_templates/index.md) ### Example Collection The collection used in this example can be found by navigating to Collections in the sidebar. The column names match the prompt template's placeholders so that the data can be injected into the prompt. As you can see, three columns exist: - **user_input**: The user query mentioned above. The data in this column will be injected into the placeholder of the same name in the prompt template - **expected_behaviour**: How the model should behave given the user_query. Used for evaluating the responses - **category**: Some additional information useful for filtering for certain categories To dive deeper into collections, have a look at [collections](https://docs.elluminate.de/guides/collections/index.md) ### Example Criterion Set The Criterion set used in the evaluation can be found under Criteria Library in the sidebar. Currently only one criterion exists, which checks if the models response adheres to the *expected_behaviour* which was defined in the collection. To understand the full power of criteria, take a look at [criterion sets](https://docs.elluminate.de/guides/criterion_set/index.md) ### Example Experiments An overview of all experiments that were executed in this project can be found by navigating to Experiments in the sidebar. In this project, there have been already 3 experiments run for you to explore. You can view the details of each experiment by clicking on them. Among other information, you will find the overall performance of your experiment. For more details, the sample navigator shows the responses to each prompt and the evaluation of each criterion individually. For a more in-depth dive into analyzing the results of experiments, see [Response Analysis](https://docs.elluminate.de/guides/response_analysis/index.md) ## Next Steps Feel free to take a look around our platform, or read any of our guides to get you started with your own project. A good starting point to get a better feeling about the features of the platform is [The Basics](https://docs.elluminate.de/guides/the_basics/index.md). You can also directly jump into creating your own evaluations via the [Quick Start (GUI)](https://docs.elluminate.de/get_started/quick_start/index.md) directly on our website, or via [Quick Start (SDK)](https://docs.elluminate.de/get_started/quick_start_sdk/index.md) using Python. # Quick Start (SDK) *Start evaluating prompts with elluminate using the Python SDK in just a few minutes* For a deeper understanding of the platform, visit our [Key Concepts](https://docs.elluminate.de/guides/the_basics/index.md) and [Guides](https://docs.elluminate.de/guides/async/index.md). Async Support The SDK provides both synchronous (`Client`) and asynchronous (`AsyncClient`) clients with identical APIs. This quick start uses the synchronous client. For async operations, concurrent execution, and real-time streaming, see the [Async Operations Guide](https://docs.elluminate.de/guides/async/index.md). ## Requirements The elluminate SDK requires: - **Python**: 3.10 or higher Check your Python version: ``` python --version ``` ## Prerequisites First, install the elluminate package and an additional package required by the quickstart code: ``` pip install elluminate python-dotenv ``` Next, you'll need to set up your API key. Visit your project's "API Keys" page to create a new API key. For detailed information about API key management and security best practices, see our [API Key Management Guide](https://docs.elluminate.de/get_started/api_keys/index.md). Once you have your API key, export it and the service address as environment variables: ``` export ELLUMINATE_API_KEY= export ELLUMINATE_BASE_URL= ``` Remember to store your API key securely and never commit it to version control. You're now ready to begin evaluating prompts with elluminate! ## Jupyter Notebook If you're using a Jupyter Notebook, install the required packages and define the environment variables as follows: ``` !pip install elluminate nest-asyncio python-dotenv %env ELLUMINATE_API_KEY= %env ELLUMINATE_BASE_URL= ``` Before running the quickstart code, run these lines for Jupyter Notebook compatibility: ``` import nest_asyncio nest_asyncio.apply() ``` ## Evaluating Your First Prompt Implement your first evaluation with the following Python code: ``` """v1.0 API version of example_sdk_usage.py Demonstrates the basic SDK workflow: 1. Create a prompt template 2. Generate evaluation criteria 3. Create a test collection with variables 4. Run an experiment 5. Review results """ from dotenv import load_dotenv from elluminate import Client load_dotenv(override=True) client = Client() llm_config = client.get_llm_config(name="GPT-4.1 Nano") # Create a prompt template with a placeholder template = client.create_prompt_template( name="Scheme Concepts", messages="Explain how {{concept}} works in Scheme, providing a short but illustrative code example.", ) # Generate evaluation criteria for this template template.generate_criteria() # Create a collection and add test cases collection = client.create_collection(name="Scheme Concepts") collection.add_many(variables=[{"concept": "recursion"}]) # Run the experiment - creates responses and rates them in one call experiment = client.run_experiment( name="Scheme Concepts Analysis", prompt_template=template, collection=collection, description="Evaluating explanations of Scheme programming concepts", llm_config=llm_config, ) # Review the results for response in experiment.responses(): for rating in response.ratings: print(f"Criteria: {rating.criterion.criterion_str}") print(f"Rating: {rating.rating}\n") ``` 1. Initializes the elluminate client using your configured environment variables from the setup phase. 1. Creates a prompt template using mustache syntax, incorporating template variables (like `concept` in this example). If the template already exists, it just gets returned. 1. Generates evaluation criteria automatically for your prompt template or gets the existing criteria. 1. Creates a template variables collection. This will be used to collect the template variables for a prompt template. 1. Adds a template variable to the collection. This will be used to fill in the template variable (adding `recursion` to column `concept`). 1. Creates a response by using your prompt template and filling in the template variable. 1. Evaluates the response against the generated criteria, returning detailed ratings for each criterion. ## View the Ratings Access your ratings through the web interface by logging in. Upon viewing your ratings dashboard, you'll find a comprehensive overview that includes key statistics such as: - Overall score - Token count for both prompt templates and responses - Number of evaluation criteria used Each rating entry can be expanded to reveal detailed information, including specific evaluation criteria and whether each criterion was successfully met. This detailed view helps you understand exactly how your responses were assessed. ## Next Steps - Explore the [Key Concepts](https://docs.elluminate.de/guides/the_basics/index.md) of elluminate - Learn about [Async Operations](https://docs.elluminate.de/guides/async/index.md) for concurrent execution and real-time streaming - Check out [Batch Processing](https://docs.elluminate.de/guides/batch_processing/index.md) for efficient operations # Guides # Agentic Evaluations *Evaluate agents you run yourself, using their trajectories.* Run your agent in its existing runtime, upload its trial results, and let elluminate inspect and rate the recorded trajectories. ## What agentic evaluation is ### When to use agentic evaluations Use this workflow when your agent makes several LLM calls, calls tools or delegates work, and you need to evaluate what it did as well as its final answer. Your runner can be your own code, [Harbor](https://www.harborframework.com/docs), LangChain, CrewAI, AutoGen, or another framework. For single-turn outputs, or tool calls elluminate generates itself, use the [Tool Calling](https://docs.elluminate.de/guides/tool_calling/index.md) guide. ### Core concepts - **Task** — one unit of work, identified by a unique task name. - **Collection** — the set of tasks, normally with a `task` column and an optional `instruction` column. - **Trajectory (trace)** — a step-by-step record of one run: messages, tool calls, and observations. - **Criterion** — a YES/NO question rated against a trajectory. A criterion set applies to every task; [task-specific criteria](#task-specific-criteria) apply to one task. - **Overall Rating** — the automatic YES/NO rating for overall task success. - **Experiment** — binds a collection and criterion set, then holds uploaded traces, ratings, and metrics. ### The trajectory format Your runner must produce an [ATIF](https://www.harborframework.com/docs/agents/trajectory-format) trajectory (Agent Trajectory Interchange Format). elluminate has no framework-specific importer: convert your runner's output to ATIF, wrap it in a trial result, and upload it through the [UI](#upload-results-in-the-ui) or [SDK](#sdk-walkthrough). ATIF records a run that has already happened. It is different from UCE (`elluminate.uce/1`), which is conversation input that elluminate runs itself; see [Conversations](https://docs.elluminate.de/guides/conversations/index.md). ## UI walkthrough Collections → New collection, with the Agentic collection type selected. 1. **Create an Agentic collection.** Choose **Agentic** in **Collections → New collection**. Keep `task` as a unique Text column; it identifies every upload. `instruction` is optional when the trajectory already contains the input. Agentic experiments do not need a prompt template and never generate responses automatically. 1. **Create a criterion set.** In **Criteria Library**, add clear YES/NO questions that can be answered from a trajectory. Use [task-specific criteria](#task-specific-criteria) for checks that apply to only one task. 1. **Create an Agentic experiment.** In **Experiments → New**, choose **Agentic** and select the collection and criterion set. 1. **Run your agent externally** and collect one ATIF trajectory per task. 1. **Upload the traces** in the [UI](#upload-results-in-the-ui) or with the [SDK](#sdk-walkthrough). With evaluation enabled, elluminate rates every criterion against each trajectory. 1. **Review the results** in the UI. Automatic Overall Rating Every Agentic experiment adds **Overall Rating** to its frozen evaluation definition. It remains separate from a same-named criterion in your criterion set. Experiments → New: creating an Agentic experiment. ### Import tasks from a Harbor zip To import [Harbor task folders](https://www.harborframework.com/docs/tasks), open an Agentic collection, choose **Add task**, then **Upload zip**. The archive may contain folders at any depth; each folder with `task.toml` becomes a task. ``` tasks/ ├── fix-parser/ │ ├── task.toml │ ├── instruction.md │ └── criteria.toml └── needs-doc/ ├── task.toml └── instruction.md ``` | File | Required | Imported as | | ------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------ | | `task.toml` | yes | Task identity and task configuration. `[task].name` is used when present; otherwise the folder name is used. | | `instruction.md` | yes | The task instruction; it must not be empty. | | `criteria.toml` | no | Criteria for that task only. | | `environment/`, `solution/`, `tests/` | no | Text-based task configuration retained for export. | The collection needs a task-name column and a Text instruction column. `criteria.toml` has one `[[criteria]]` entry per criterion. Labels are optional; when supplied, they must be unique per task, at most 25 characters, and not `Overall Rating`. Criterion text is limited to 4,000 characters. Re-importing updates matching tasks instead of creating duplicates. Keep a criterion label stable to preserve its version history; renaming a label removes the old criterion and adds a new one. Tasks absent from the archive stay untouched. Task configuration and text files under the listed directories are stored and re-exported. Binary files, files over 256 KB, and other paths are reported but not stored. Each upload is limited to 25 MB compressed, 300 tasks, 2 MB per file, 100 criteria per task, and 100 implementation files totalling 1 MB per task. ### Task-specific criteria Use the collection's **Tasks** view for checks that apply to one task only. Open a task's criteria sheet, add the criterion, and save. Labels are optional and unique within the task; criteria can reference task fields such as `{{expected_outcome}}`. One card per task, including its task-specific criteria count. An experiment evaluates its criterion set, the task's criteria, and **Overall Rating**. Those values are frozen when the experiment is created; create a new experiment after changing them. ## Upload agent traces in the UI Open the Agentic experiment and choose **Upload agent traces**. You can drop a file or paste JSON, then confirm the task-name column, choose whether to evaluate after upload, and set an epoch for another run of the same tasks. ### Upload file format Use a `.json` object or array, or `.jsonl` with one complete object per line. Each object is an SDK [`AgentTrialResult`](#agenttrialresult-fields): - `task_name` is required and must match a task frozen into the experiment. - `trajectory` contains the [ATIF trajectory](#atif-trajectory-format). - `reward`, `cost_usd`, `steps`, `messages`, and `criterion_ratings` are optional. A trace file is not an upload file An ATIF trace alone has no `task_name`. Wrap it under `trajectory` and add the task name. ``` [ { "task_name": "write-hello-world", "reward": 1.0, "cost_usd": 0.0042, "trajectory": { "schema_version": "ATIF-v1.0", "session_id": "run-001/write-hello-world", "agent": { "name": "demo-agent", "version": "0.1.0", "model_name": "claude-sonnet-4-6" }, "steps": [ { "step_id": 1, "source": "user", "message": "Write a Python hello world script to hello.py" }, { "step_id": 2, "source": "agent", "message": "Writing hello.py.", "tool_calls": [ { "tool_call_id": "tc_1", "function_name": "write_file", "arguments": {"path": "hello.py", "content": "print('Hello, World!')"} } ], "observation": { "results": [{"source_call_id": "tc_1", "content": "wrote 22 bytes"}] }, "metrics": {"prompt_tokens": 420, "completion_tokens": 61, "cost_usd": 0.0042} } ], "final_metrics": { "total_steps": 2, "total_cost_usd": 0.0042, "total_prompt_tokens": 420, "total_completion_tokens": 61 } } } ] ``` With **Evaluate after upload** enabled, omit `criterion_ratings` and let elluminate rate the trace. For precomputed ratings, include each criterion's `criterion_id` from the experiment definition; labels may repeat. Older experiments can still use unambiguous labels against their live definition. Trials are processed independently, so one invalid trajectory does not block the rest. Evaluation runs in the background. ## Reviewing results in the UI The experiment overview. - **Overview** shows aggregate metrics, criterion pass rates, and an experiment summary when available. - **Sample Navigator** shows a sample's phase timeline, trace, and ratings. Sub-agent traces are nested under the step that spawned them. - **Responses Overview** lists every response. Phase Timeline and Agent Trace for one sample. ## SDK walkthrough The example creates or reuses an Agentic collection, criterion set, and experiment; converts runner output to `AgentTrialResult`; uploads it; and waits for evaluation. ``` export ELLUMINATE_API_KEY= uv run --directory elluminate_sdk python examples/example_harbor_agentic_upload.py ``` ``` """Harbor-based Agentic Evaluation: end-to-end upload example. This example shows the full workflow for evaluating an agent that was run externally with Harbor (or any other agent framework), and uploading the results, including ATIF trajectories, to elluminate for inspection and automatic per-criterion evaluation. Workflow: 1. Create an AGENTIC collection whose rows are the agent's tasks. The task name lives in a single TEXT `task` column (matched against each uploaded result's `task_name`); no prompt template is needed because AGENTIC experiments do not auto-generate responses. 2. Create a criterion set describing what counts as success. 3. Create an AGENTIC experiment (no auto-generation; results are uploaded). 4. Run the agent externally (Harbor CLI, LangChain, CrewAI, custom code). 5. Read Harbor's per-task output and convert it into `AgentTrialResult` objects. 6. Upload via `experiment.upload_agent_results(...)`. 7. The backend stores the trajectories and, when `evaluate=True` and trajectories are present, elluminate automatically rates each criterion against the trajectory. 8. Await that evaluation via `experiment.wait_for_evaluation(...)` so the ratings are complete before reading results back. The script is idempotent: collections, criterion sets, and experiments are reused across runs; uploads are skipped when an experiment already contains responses. For a self-contained demo this script uses a small in-memory stand-in (`HARBOR_RUN`) for the runner's output. In a real run you would replace it with your own loader that reads your runner's per-task output from disk — for Harbor, the run directory at `~/.harbor/runs//tasks//...`. """ from typing import Any from dotenv import load_dotenv from elluminate import AgentTrialResult, Client from elluminate.schemas import CollectionColumn, ColumnTypeEnum from elluminate.schemas.criterion import CriterionIn from elluminate.schemas.experiments import Experiment load_dotenv(override=True) client = Client() llm_config = client.get_llm_config(name="Claude Sonnet 4.6") # Upper bound for the evaluation wait in step 6. Size it to your run: rating is # per trajectory, and this demo uploads two. EVALUATION_TIMEOUT_SECONDS = 1800 # Mock "Harbor output"; in a real integration this is read from disk. # Each entry is what a Harbor run produces per task: a short task identifier, # the instruction text, final messages, aggregate metrics, and an ATIF # trajectory describing every step the agent took. HARBOR_RUN: list[dict[str, Any]] = [ { "task_name": "write-hello-world", "instruction": "Write a Python hello world script to hello.py", "reward": 1.0, "steps": 2, "cost_usd": 0.0042, "input_tokens": 420, "output_tokens": 61, "duration_seconds": 3.2, "messages": [ {"role": "user", "content": "Write a Python hello world script to hello.py"}, {"role": "assistant", "content": "Wrote hello.py: print('Hello, World!')"}, ], "trajectory": { "schema_version": "ATIF-v1.0", "session_id": "harbor-run-001/write-hello-world", "agent": { "name": "harbor-demo-agent", "version": "0.1.0", "model_name": "claude-sonnet-4-6", }, "steps": [ { "step_id": 1, "source": "user", "message": "Write a Python hello world script to hello.py", }, { "step_id": 2, "source": "agent", "message": "Writing hello.py.", "tool_calls": [ { "tool_call_id": "tc_1", "function_name": "write_file", "arguments": {"path": "hello.py", "content": "print('Hello, World!')"}, } ], "observation": { "results": [{"source_call_id": "tc_1", "content": "wrote 22 bytes"}], }, "metrics": {"prompt_tokens": 420, "completion_tokens": 61, "cost_usd": 0.0042}, }, ], "final_metrics": { "total_steps": 2, "total_cost_usd": 0.0042, "total_prompt_tokens": 420, "total_completion_tokens": 61, }, }, }, { "task_name": "reverse-string-function", "instruction": "Create a Python function that reverses a string in reverse.py", "reward": 0.5, "steps": 2, "cost_usd": 0.0031, "input_tokens": 310, "output_tokens": 42, "duration_seconds": 2.1, "messages": [ {"role": "user", "content": "Create a Python function that reverses a string in reverse.py"}, {"role": "assistant", "content": "Wrote reverse.py with a one-line slice-based reverse."}, ], "trajectory": { "schema_version": "ATIF-v1.0", "session_id": "harbor-run-001/reverse-string-function", "agent": { "name": "harbor-demo-agent", "version": "0.1.0", "model_name": "claude-sonnet-4-6", }, "steps": [ { "step_id": 1, "source": "user", "message": "Create a Python function that reverses a string in reverse.py", }, { "step_id": 2, "source": "agent", "message": "Writing reverse.py.", "tool_calls": [ { "tool_call_id": "tc_1", "function_name": "write_file", "arguments": { "path": "reverse.py", "content": "def reverse(s: str) -> str:\n return s[::-1]\n", }, } ], "observation": { "results": [{"source_call_id": "tc_1", "content": "wrote 42 bytes"}], }, "metrics": {"prompt_tokens": 310, "completion_tokens": 42, "cost_usd": 0.0031}, }, ], "final_metrics": { "total_steps": 2, "total_cost_usd": 0.0031, "total_prompt_tokens": 310, "total_completion_tokens": 42, }, }, }, ] def harbor_to_agent_trial(task_output: dict[str, Any]) -> AgentTrialResult: """Map one Harbor per-task output dict to an `AgentTrialResult`. `task_name` on `AgentTrialResult` is what elluminate matches against the collection's `task_name_column` value, so here we set it to the full instruction text (which is also what the `task` column row holds). """ return AgentTrialResult( task_name=task_output["instruction"], messages=task_output["messages"], reward=task_output["reward"], steps=task_output["steps"], cost_usd=task_output["cost_usd"], input_tokens=task_output["input_tokens"], output_tokens=task_output["output_tokens"], duration_seconds=task_output["duration_seconds"], trajectory=task_output["trajectory"], metadata={"run_name": "harbor-run-001", "task_id": task_output["task_name"]}, ) # Step 1: AGENTIC collection with a single TEXT `task` column. # The `task` column must be TEXT so elluminate auto-selects it as the # task-name column that uploaded results are matched against. No prompt # template is required because AGENTIC experiments never auto-generate; # responses are supplied by `upload_agent_results`. collection, _ = client.get_or_create_collection( name="Harbor Demo Tasks", defaults={ "collection_type": "AGENTIC", "columns": [CollectionColumn(name="task", column_type=ColumnTypeEnum.TEXT)], "variables": [{"task": h["instruction"]} for h in HARBOR_RUN], }, ) # Step 2: criterion set defining what success looks like for these tasks. # Labels are display text and may repeat; frozen criterion IDs identify ratings. criterion_set, _ = client.get_or_create_criterion_set( name="Harbor Demo Criteria", defaults={ "criteria": [ CriterionIn( criterion_str="Did the agent correctly complete the requested task?", label="task-complete", ), CriterionIn( criterion_str="Did the agent use tools appropriately?", label="uses-tools", ), CriterionIn( criterion_str="Is the agent's final output correct?", label="output-correct", ), ], }, ) def get_or_create_agentic_experiment(name: str, description: str) -> tuple[Experiment, bool]: """Return an AGENTIC experiment, creating it if missing. Also reports whether the experiment already has uploaded responses so the caller can skip a redundant upload on re-runs (avoids epoch conflicts). """ try: experiment = client.get_experiment(name=name, fetch_responses=False) populated = experiment.results is not None and experiment.results.completed_epochs > 0 return experiment, populated except ValueError: experiment = client.create_experiment( name=name, collection=collection, prompt_template=None, criterion_set=criterion_set, description=description, evaluation_mode="AGENTIC", llm_config=llm_config, ) return experiment, False # Step 3: AGENTIC experiment. No auto-generation; results come from Harbor. experiment, experiment_populated = get_or_create_agentic_experiment( "Harbor Demo — Agent Run", "Harbor-run coding agent with ATIF trajectories.", ) print(f"Experiment: {experiment.name} (id={experiment.id})") # Step 4: Convert Harbor output to `AgentTrialResult` objects. results = [harbor_to_agent_trial(task_output) for task_output in HARBOR_RUN] # Step 5: upload with `evaluate=True`. elluminate rates every # criterion against the trajectory and fills in per-criterion ratings. if experiment_populated: print("Experiment already has responses; skipping upload.") else: upload = experiment.upload_agent_results( results=results, task_name_column="task", evaluate=True, ) print( f"Uploaded: {upload.created_responses} responses, " f"{upload.created_ratings} ratings, " f"{upload.pending_evaluations} pending trace evaluations" ) if upload.errors: print(f"Errors: {upload.errors}") # Step 6: wait for elluminate's trace agent to finish rating the # uploaded trajectories, so the ratings below are complete. Rating a large # run takes minutes to hours; the timeout bounds the wait so a stuck # evaluation raises TimeoutError instead of blocking forever. Omit it to # wait indefinitely. if upload.pending_evaluations: print(f"Awaiting evaluation of {upload.pending_evaluations} trajectories...") event = experiment.wait_for_evaluation( task_id=upload.evaluation_task_id, timeout=EVALUATION_TIMEOUT_SECONDS ) rated = event.progress.responses_rated if event.progress else 0 print(f"Evaluation {event.status.value.lower()}: {rated} response(s) rated") # Step 7: Verify the trajectories are queryable from the SDK. experiment.fetch_responses() for resp in experiment.responses(): task = resp.prompt.template_variables.input_values.get("task", "?") steps = len(resp.trajectory["steps"]) if resp.trajectory else 0 print(f" [{task[:50]}] trajectory_steps={steps}") ``` ### Growing the dev set (the eval flywheel) Add production failures to the collection so later runs cover them too. Adding rows expands the task set; increasing an upload epoch reruns the same tasks. ``` collection = client.get_collection(name="my-agent-dev-set") collection.add_many([ {"task": "New failing scenario discovered in prod ..."}, {"task": "Another regression case ..."}, ]) ``` ### AgentTrialResult fields | Field | Required | Description | | ------------------- | -------- | ---------------------------------------------------------------------- | | `task_name` | yes | A task name frozen when the experiment was created. | | `messages` | no | Final OpenAI-format messages shown on the response page. | | `reward` | no | Primary reward score (0.0–1.0). | | `steps` | no | Number of agent steps or LLM calls. | | `cost_usd` | no | Total USD cost; derived from the trajectory when absent. | | `duration_seconds` | no | Wall-clock duration. | | `input_tokens` | no | Total input tokens, including cached reads. | | `output_tokens` | no | Total output tokens. | | `cached_tokens` | no | Cached input tokens, already included in `input_tokens`. | | `error` | no | Error message for a failed trial. | | `metadata` | no | Free-form data shown on the response page. | | `trajectory` | no | ATIF trajectory, validated by the backend. | | `criterion_ratings` | no | Precomputed YES/NO ratings; use `criterion_id` for frozen experiments. | ### ATIF trajectory format `trajectory` uses [ATIF](https://www.harborframework.com/docs/agents/trajectory-format). The upload example above is a minimal ATIF v1 trajectory wrapped in a trial result. ### How cost and tokens are resolved elluminate uses the first available cost source: `cost_usd` on the trial, `final_metrics.total_cost_usd`, the sum of step costs (including sub-agents), then a token-based estimate from `model_name`. Token totals use the same precedence. Input-token counts include cached reads. Put provider-specific cache-write tokens in `metrics.extra` (and run totals in `final_metrics.extra`); estimated costs are shown with `~`. Send `cost_usd` or `metrics.cost_usd` whenever your runner knows the actual cost. # Async Operations *Optimize performance with asynchronous operations for large-scale prompt evaluations and concurrent processing* The elluminate SDK provides a full-featured **AsyncClient** for asynchronous operations. This is particularly useful when: - **Running Multiple Experiments Concurrently** - Execute several experiments in parallel instead of sequentially - **Integration with Async Frameworks** - Use with FastAPI, aiohttp, or other async web frameworks - **Large-Scale Batch Processing** - Process hundreds or thousands of operations with better resource utilization - **Non-Blocking Operations** - Keep your application responsive during long-running evaluations ## AsyncClient vs Client The SDK provides two client classes with **identical APIs**: - **`Client`** - Synchronous operations (blocking I/O) - **`AsyncClient`** - Asynchronous operations (non-blocking I/O) All public methods have the same signature; the only difference is that AsyncClient methods must be awaited: ``` from elluminate import Client, AsyncClient # Synchronous client = Client() template = client.create_prompt_template(name="Test", template="...") # Asynchronous (same signature, just add await) async with AsyncClient() as client: template = await client.create_prompt_template(name="Test", template="...") ``` ## Basic Usage ### Context Manager (Recommended) Always use the async context manager to ensure proper resource cleanup: ``` import asyncio from elluminate import AsyncClient async def main(): async with AsyncClient() as client: # Create resources template = await client.create_prompt_template( name="My Template", template="Explain {{concept}} in simple terms.", ) # Run experiment experiment = await client.run_experiment( name="My Experiment", prompt_template=template, collection=collection, ) print(f"Completed: {experiment.name}") # Run the async function asyncio.run(main()) ``` ### Manual Resource Management If you need manual control over the client lifecycle: ``` from elluminate import AsyncClient async def main(): client = AsyncClient() try: template = await client.create_prompt_template(...) finally: await client.close() # Important: clean up resources asyncio.run(main()) ``` ## Complete Example This example demonstrates the full async workflow: ``` """Async version of example_sdk_usage.py using AsyncClient. Demonstrates the async SDK workflow: 1. Create a prompt template 2. Generate evaluation criteria using AI (async rich model method) 3. Create a test collection and add variables (async rich model method) 4. Run an experiment (async) 5. Review results The async API allows for concurrent operations when needed, e.g.: - Running multiple experiments in parallel with asyncio.gather() - Non-blocking API calls in async web frameworks - Integration with async web frameworks (FastAPI, aiohttp, etc.) """ import asyncio from dotenv import load_dotenv from elluminate import AsyncClient load_dotenv(override=True) async def main(): # Use async context manager for proper resource management async with AsyncClient() as client: llm_config = await client.get_llm_config(name="GPT-4.1 Nano") # Create a prompt template with a placeholder template = await client.create_prompt_template( name="Scheme Concepts Async", messages="Explain how {{concept}} works in Scheme, providing a short but illustrative code example.", ) print(f"✓ Created template: {template.name}") # Generate evaluation criteria using AI criteria = await template.agenerate_criteria() print(f"✓ Generated {len(criteria)} criteria using AI") # Get the criterion set that was created criterion_set = await client.get_criterion_set(name=template.name) # Create a collection and add test cases collection = await client.create_collection(name="Scheme Concepts Async") await collection.aadd_many( variables=[ {"concept": "recursion"}, {"concept": "closures"}, ] ) print(f"✓ Created collection and added {len(collection.variables)} test cases") # Run the experiment - creates responses and rates them experiment = await client.run_experiment( name="Scheme Concepts Analysis Async", prompt_template=template, collection=collection, criterion_set=criterion_set, llm_config=llm_config, ) print(f"✓ Experiment completed: {experiment.name}") # Review the results print("\n=== Results ===") for response in experiment.responses(): print(f"\nInput: {response.prompt.template_variables}") print(f"Output: {response.response_str[:100]}...") if response.ratings: for rating in response.ratings: print(f" - {rating.criterion.criterion_str}: {rating.rating}") if __name__ == "__main__": asyncio.run(main()) ``` 1. Use async context manager for proper resource management 1. All client methods are async - use `await` 1. Rich model methods (on schema objects) use `a` prefix: `agenerate_criteria()`, `aadd_many()` 1. Access results the same way as synchronous code ## Concurrent Execution The real power of AsyncClient comes from running multiple operations concurrently with `asyncio.gather()`: ``` """Example demonstrating concurrent async operations with AsyncClient. This example shows: 1. Using rich model async methods (aget_or_generate_criteria, aadd_many) 2. Running multiple experiments concurrently with asyncio.gather() 3. The key benefit of the async API: parallel execution Use cases: - A/B testing multiple LLM configs simultaneously - Running the same test set across different models - Parallelizing large evaluation workloads """ import asyncio from dotenv import load_dotenv from elluminate import AsyncClient load_dotenv(override=True) async def main(): async with AsyncClient() as client: llm_config = await client.get_llm_config(name="GPT-4.1 Nano") print("Setting up shared resources...") # Set up shared resources template, created = await client.get_or_create_prompt_template( name="Concurrent Test Template", messages="Write a haiku about {{topic}}.", ) if created: print(f"✓ Created template: {template.name}") else: print(f"✓ Using existing template: {template.name}") # Generate or get criteria using rich model async method criteria, generated = await template.aget_or_generate_criteria() if generated: print(f"✓ Generated {len(criteria)} criteria using AI") else: print(f"✓ Using existing {len(criteria)} criteria") # Get the criterion set criterion_set = await client.get_criterion_set(name=template.name) # Create test collection collection, created = await client.get_or_create_collection( name="Concurrent Test Collection", ) # Add test cases if collection was just created or is empty if created or collection.variables_count == 0: await collection.aadd_many( variables=[ {"topic": "programming"}, {"topic": "coffee"}, {"topic": "mountains"}, ] ) print(f"✓ Created collection and added {len(collection.variables)} test cases") else: print(f"✓ Using existing collection with {collection.variables_count} test cases") print("\n🚀 Running 3 experiments concurrently...") # Run multiple experiments concurrently using asyncio.gather # This is significantly faster than running them sequentially experiments = await asyncio.gather( client.run_experiment( name="Concurrent Test - Run 1", prompt_template=template, collection=collection, criterion_set=criterion_set, llm_config=llm_config, ), client.run_experiment( name="Concurrent Test - Run 2", prompt_template=template, collection=collection, criterion_set=criterion_set, llm_config=llm_config, ), client.run_experiment( name="Concurrent Test - Run 3", prompt_template=template, collection=collection, criterion_set=criterion_set, llm_config=llm_config, ), ) # Compare results across runs print("\n=== Concurrent Experiment Results ===") for exp in experiments: print(f"\n{exp.name}:") print(f" Responses: {len(exp.rated_responses)}") if exp.results: print(f" Success rate: {exp.results.mean_all_ratings.yes:.2%}") else: print(" No ratings yet") if __name__ == "__main__": asyncio.run(main()) ``` 1. Set up shared resources (template, criteria, collection) 1. Run multiple experiments **concurrently** using `asyncio.gather()` - significantly faster than sequential execution 1. Compare results across all experiments ### Performance Comparison For 3 experiments with 5 test cases each: - **Sequential (sync)**: ~45 seconds (3 experiments × 15 seconds each) - **Concurrent (async)**: ~15 seconds (all 3 run in parallel) **3x faster** with concurrent execution! ## Async Method Reference ### AsyncClient Public Methods AsyncClient public methods do **NOT** use the `a` prefix: ``` async with AsyncClient() as client: # Prompt Templates await client.create_prompt_template(...) await client.get_prompt_template(...) await client.get_or_create_prompt_template(...) await client.list_prompt_templates() await client.delete_prompt_template(...) # Collections await client.create_collection(...) await client.get_collection(...) await client.get_or_create_collection(...) await client.list_collections() await client.delete_collection(...) # Experiments await client.create_experiment(...) await client.run_experiment(...) # Creates and runs await client.get_experiment(...) await client.list_experiments() await client.delete_experiment(...) # Criterion Sets await client.create_criterion_set(...) await client.get_criterion_set(...) await client.get_or_create_criterion_set(...) await client.list_criterion_sets() # LLM Configs await client.create_llm_config(...) await client.get_llm_config(...) await client.get_or_create_llm_config(...) await client.list_llm_configs() # And more... ``` ### Rich Model Async Methods Rich model methods (on schema objects) **DO** use the `a` prefix: ``` # PromptTemplate template = await client.get_prompt_template(name="...") criteria = await template.agenerate_criteria() criteria, generated = await template.aget_or_generate_criteria() all_criteria = await template.alist_criteria() new_template = await template.anew_version(template="...") # TemplateVariablesCollection collection = await client.get_collection(name="...") await collection.aadd_many(variables=[...]) await collection.aclear() await collection.agenerate_variables(prompt_template) # CriterionSet criterion_set = await client.get_criterion_set(name="...") await criterion_set.aadd_criterion(criterion="...") await criterion_set.aadd_criteria(criteria=[...]) await criterion_set.aclear() await criterion_set.alink_template(template) await criterion_set.aunlink_template(template) # Experiment experiment = await client.get_experiment(name="...") await experiment.arun() await experiment.afetch_responses() await experiment.aadd_responses(responses=[...], template_variables=[...]) await experiment.arate_responses() new_exp = await experiment.aclone(name="...") ``` ## Common Patterns ### Pattern 1: Concurrent A/B Testing Run multiple prompt variations simultaneously: ``` async def ab_test(): async with AsyncClient() as client: # Set up shared resources criterion_set = await client.create_criterion_set(name="Quality") await criterion_set.aadd_criteria([ "Is the response helpful?", "Is it accurate?", ]) collection = await client.create_collection(name="Test Cases") await collection.aadd_many(variables=[ {"topic": "AI"}, {"topic": "ML"}, ]) # Create templates template_a = await client.create_prompt_template( name="Style A", template="Explain {{topic}} briefly.", ) template_b = await client.create_prompt_template( name="Style B", template="Explain {{topic}} with examples.", ) # Run both experiments concurrently exp_a, exp_b = await asyncio.gather( client.run_experiment( name="Test A", prompt_template=template_a, collection=collection, criterion_set=criterion_set, ), client.run_experiment( name="Test B", prompt_template=template_b, collection=collection, criterion_set=criterion_set, ), ) # Compare results print(f"A: {exp_a.results.mean_all_ratings.yes:.1%}") print(f"B: {exp_b.results.mean_all_ratings.yes:.1%}") asyncio.run(ab_test()) ``` ### Pattern 2: Batch Processing Test Cases Process multiple test case collections in parallel: ``` async def batch_process(): async with AsyncClient() as client: template = await client.get_prompt_template(name="My Template") criterion_set = await client.get_criterion_set(name="My Criteria") # Get multiple collections collections = await asyncio.gather( client.get_collection(name="Collection 1"), client.get_collection(name="Collection 2"), client.get_collection(name="Collection 3"), ) # Run experiments on all collections concurrently experiments = await asyncio.gather(*[ client.run_experiment( name=f"Experiment {i+1}", prompt_template=template, collection=coll, criterion_set=criterion_set, ) for i, coll in enumerate(collections) ]) return experiments results = asyncio.run(batch_process()) ``` ### Pattern 3: FastAPI Integration Use AsyncClient in a FastAPI endpoint: ``` from fastapi import FastAPI from elluminate import AsyncClient app = FastAPI() # Create a single AsyncClient instance for the application async_client = AsyncClient() @app.on_event("startup") async def startup(): global async_client async_client = AsyncClient() @app.on_event("shutdown") async def shutdown(): await async_client.close() @app.post("/evaluate") async def evaluate_prompt(prompt: str, test_case: dict): # Get or create template template, _ = await async_client.get_or_create_prompt_template( name="API Template", template=prompt, ) # Generate criteria criteria, _ = await template.aget_or_generate_criteria() criterion_set = await async_client.get_criterion_set(name=template.name) # Create collection with single test case collection = await async_client.create_collection(name=f"Test {timestamp}") await collection.aadd_many(variables=[test_case]) # Run experiment experiment = await async_client.run_experiment( name=f"Eval {timestamp}", prompt_template=template, collection=collection, criterion_set=criterion_set, ) return { "experiment_id": experiment.id, "pass_rate": experiment.results.mean_all_ratings.yes if experiment.results else 0, } ``` ## Jupyter Notebook Support AsyncClient works in Jupyter notebooks with `nest_asyncio`: ``` # Install in notebook !pip install elluminate nest-asyncio # Enable nested event loops import nest_asyncio nest_asyncio.apply() # Now you can use await in cells from elluminate import AsyncClient async with AsyncClient() as client: template = await client.create_prompt_template(...) experiment = await client.run_experiment(...) ``` ## Performance Tips ### 1. Use asyncio.gather() for Independent Operations When operations don't depend on each other, run them concurrently: ``` # GOOD: Concurrent execution template, collection, criterion_set = await asyncio.gather( client.get_prompt_template(name="..."), client.get_collection(name="..."), client.get_criterion_set(name="..."), ) # BAD: Sequential execution (3x slower) template = await client.get_prompt_template(name="...") collection = await client.get_collection(name="...") criterion_set = await client.get_criterion_set(name="...") ``` ### 2. Limit Concurrency for Resource-Intensive Operations Use semaphores to limit concurrent expensive operations: ``` import asyncio async def run_with_limit(semaphore, client, name, template, collection): async with semaphore: return await client.run_experiment( name=name, prompt_template=template, collection=collection, ) async def main(): async with AsyncClient() as client: # Limit to 5 concurrent experiments semaphore = asyncio.Semaphore(5) tasks = [ run_with_limit(semaphore, client, f"Exp {i}", template, collection) for i in range(20) ] experiments = await asyncio.gather(*tasks) asyncio.run(main()) ``` ### 3. Reuse Client Instances Create one AsyncClient per application, not per request: ``` # GOOD: Single client instance class App: def __init__(self): self.client = AsyncClient() async def process(self): return await self.client.run_experiment(...) async def cleanup(self): await self.client.close() # BAD: New client per operation (connection overhead) async def process(): async with AsyncClient() as client: # Creates new connection return await client.run_experiment(...) ``` ## Migration from Sync to Async Converting synchronous code to async is straightforward: ### Before (Synchronous) ``` from elluminate import Client client = Client() template = client.create_prompt_template(name="...", template="...") collection = client.create_collection(name="...") collection.add_many(variables=[...]) experiment = client.run_experiment( name="...", prompt_template=template, collection=collection, ) ``` ### After (Asynchronous) ``` from elluminate import AsyncClient import asyncio async def main(): async with AsyncClient() as client: template = await client.create_prompt_template(name="...", template="...") collection = await client.create_collection(name="...") await collection.aadd_many(variables=[...]) # Note: aadd_many with 'a' prefix experiment = await client.run_experiment( name="...", prompt_template=template, collection=collection, ) asyncio.run(main()) ``` **Key changes:** 1. Import `AsyncClient` instead of `Client` 1. Wrap code in `async def main()` 1. Use `async with AsyncClient()` context manager 1. Add `await` before all client methods 1. Rich model methods get `a` prefix: `add_many` → `aadd_many` 1. Run with `asyncio.run(main())` ## Error Handling Error handling works the same as synchronous code: ``` from elluminate.exceptions import ConflictError, NotFoundError async def handle_errors(): async with AsyncClient() as client: try: template = await client.create_prompt_template(name="Existing", ...) except ConflictError: template = await client.get_prompt_template(name="Existing") try: experiment = await client.get_experiment(name="NonExistent") except NotFoundError: print("Experiment not found") ``` ## Real-Time Streaming AsyncClient supports Server-Sent Events (SSE) streaming for real-time progress updates during long-running operations. This is particularly useful for: - **Experiment Execution** - Watch responses being generated and rated in real-time - **Batch Operations** - Track status of batch rating operations - **Better UX** - Show live progress instead of blocking spinners - **Early Error Detection** - See failures immediately, not after timeout ### Streaming Experiment Execution Stream real-time progress during experiment execution with `stream_experiment()`: ``` from elluminate import AsyncClient from elluminate.streaming import TaskStatus async with AsyncClient() as client: async for event in client.stream_experiment( name="My Experiment", prompt_template=template, collection=collection, criteria=["Is it accurate?", "Is it helpful?"], polling_interval=0.5, # Poll every 0.5 seconds ): if event.status == TaskStatus.STARTED: # Show live progress if event.progress: percent = event.progress.percent_complete print(f"Progress: {percent:.1f}%") print(f"Generated: {event.progress.responses_generated}/{event.progress.total_responses}") print(f"Rated: {event.progress.responses_rated}/{event.progress.total_responses}") # Show incremental logs if event.logs_delta: print(f"Log: {event.logs_delta}") elif event.status == TaskStatus.SUCCESS: print("✅ Complete!") experiment = event.result # Final experiment with responses elif event.is_failure: print(f"❌ Failed: {event.error_msg}") break ``` **Key Features:** - **Real-time progress**: See responses generated, rated, and percentage complete - **Incremental logs**: Get log messages as they occur - **Early termination**: Stop immediately on failure - **Final result**: Access completed experiment on SUCCESS #### Terminal States Streaming operations can end in several terminal states. Always handle all possible outcomes: | Status | Meaning | When It Occurs | User Action | | ---------- | -------------------------- | ----------------------------------------- | -------------------------------------------- | | `SUCCESS` | Completed successfully | All responses generated and rated | Process results from `event.result` | | `FAILURE` | Operation failed | LLM error, validation error, system error | Check `event.error_msg`, fix issue and retry | | `TIMEOUT` | Exceeded time limit | Operation runs longer than 10 hours | Consider smaller batches or contact support | | `REVOKED` | Task cancelled | Manual cancellation or system shutdown | Re-run if needed | | `REJECTED` | Task rejected before start | Queue full or invalid configuration | Check inputs and try again | **Example handling all terminal states:** ``` async for event in client.stream_experiment(...): if event.status == TaskStatus.STARTED: # Handle progress updates pass elif event.status == TaskStatus.SUCCESS: print("✅ Complete!") experiment = event.result break elif event.status == TaskStatus.FAILURE: print(f"❌ Failed: {event.error_msg}") break elif event.status == TaskStatus.TIMEOUT: print(f"⏱️ Timed out after 10 hours: {event.error_msg}") break elif event.status in {TaskStatus.REVOKED, TaskStatus.REJECTED}: print(f"⚠️ Operation {event.status.lower()}: {event.error_msg}") break ``` Check `is_complete` Property All terminal states have `event.is_complete == True`. Use this to detect when streaming ends: ``` if event.is_complete: # Handle terminal state if event.is_success: # Process results elif event.is_failure: # Handle error (includes FAILURE, TIMEOUT, REVOKED, REJECTED) ``` ### Streaming Batch Rating Stream status updates for batch rating operations with `stream_batch_rate()`: ``` async with AsyncClient() as client: # Get responses to rate responses = list(experiment.responses()) async for event in client.stream_batch_rate( prompt_responses=responses, rating_mode=RatingMode.DETAILED, ): if event.status == TaskStatus.STARTED: print("Rating in progress...") elif event.status == TaskStatus.SUCCESS: ratings = event.result # List[List[Rating]] print(f"Rated {len(ratings)} responses") elif event.is_failure: print(f"Failed: {event.error_msg}") break ``` Batch Rating Streaming Behavior **Available:** All terminal states (`SUCCESS`, `FAILURE`, `TIMEOUT`, `REVOKED`, `REJECTED`) with `error_msg` when applicable. **Not Available:** Progress metrics (`event.progress` is always `None`). The backend doesn't track how many responses have been rated during batch operations. **Why:** Batch rating is typically fast (1-2 seconds per response), so progress tracking was deemed unnecessary. This may change in future versions. ### Complete Streaming Example See a full example with progress bar and error handling: ``` """Real-time streaming experiment with progress updates. Demonstrates the streaming SDK workflow: 1. Create a prompt template and test collection 2. Stream experiment execution with real-time progress 3. Display live updates as responses are generated and rated 4. Access the completed experiment when finished The streaming API provides real-time feedback during long-running experiments: - Live progress tracking (responses generated, ratings completed) - Per-epoch progress for multi-epoch experiments - Incremental log messages - Early error detection - Better user experience for experiments with many test cases """ import asyncio from dotenv import load_dotenv from elluminate import AsyncClient from elluminate.streaming import TaskStatus load_dotenv(override=True) async def main(): # Use async context manager for proper resource management async with AsyncClient() as client: llm_config = await client.get_llm_config(name="GPT-4.1 Nano") # Create a prompt template template = await client.create_prompt_template( name="Programming Concepts Streaming", messages="Explain {{concept}} in {{language}}, providing a clear code example.", ) print(f"✓ Created template: {template.name}") # Create a collection with multiple test cases collection = await client.create_collection(name="Programming Concepts Streaming") await collection.aadd_many( variables=[ {"concept": "recursion", "language": "Python"}, {"concept": "closures", "language": "JavaScript"}, {"concept": "async/await", "language": "Python"}, {"concept": "generators", "language": "Python"}, {"concept": "promises", "language": "JavaScript"}, ] ) print(f"✓ Created collection with {len(collection.variables)} test cases") # Stream the experiment with real-time progress print("\n=== Streaming Experiment ===") async for event in client.stream_experiment( name="Programming Concepts Analysis Streaming", prompt_template=template, collection=collection, criteria=[ "Is the explanation clear and accurate?", "Is the code example correct and illustrative?", ], polling_interval=0.5, # Poll every 0.5 seconds llm_config=llm_config, ): if event.status == TaskStatus.PENDING: print("⏳ Experiment queued, waiting to start...") elif event.status == TaskStatus.STARTED: if event.progress: # Calculate and display progress percent = event.progress.percent_complete completed = event.progress.responses_generated rated = event.progress.responses_rated total = event.progress.total_responses # Progress bar bar_length = 40 filled = int(bar_length * percent / 100) bar = "█" * filled + "░" * (bar_length - filled) print( f"\r🔄 [{bar}] {percent:.1f}% | Generated: {completed}/{total} | Rated: {rated}/{total}", end="", flush=True, ) # Show incremental logs if available if event.logs_delta: print(f"\n📝 Log: {event.logs_delta}") elif event.status == TaskStatus.SUCCESS: print("\n✅ Experiment completed successfully!") # Access the final experiment experiment = event.result if experiment: print(f"\n=== Results for '{experiment.name}' ===") print(f"Total responses: {len(list(experiment.responses()))}") # Show mean ratings if experiment.results: print(f"Mean rating: {experiment.results.mean_all_ratings}") # Show first few responses print("\nSample responses:") for i, response in enumerate(list(experiment.responses())[:2], 1): print(f"\n{i}. Input: {response.prompt.template_variables}") print(f" Output: {response.response_str[:150]}...") if response.ratings: print(" Ratings:") for rating in response.ratings: print(f" - {rating.criterion.criterion_str}: {rating.rating}") elif event.status == TaskStatus.FAILURE: print(f"\n❌ Experiment failed: {event.error_msg}") break elif event.status == TaskStatus.TIMEOUT: print(f"\n⏱️ Experiment timed out: {event.error_msg}") break elif event.status in {TaskStatus.REVOKED, TaskStatus.REJECTED}: print(f"\n⚠️ Experiment {event.status.lower()}") break if __name__ == "__main__": asyncio.run(main()) ``` ### Streaming vs Blocking | Feature | `run_experiment()` | `stream_experiment()` | | ---------------------- | ------------------ | ----------------------------- | | Progress visibility | ❌ None | ✅ Real-time | | Early error detection | ❌ After timeout | ✅ Immediate | | UX for long operations | ❌ Loading spinner | ✅ Progress bar | | API calls | ✅ Single | ✅ SSE stream | | Use case | Quick experiments | Long-running, many test cases | **When to use streaming:** - Experiments with many test cases (>10) - Slow models (takes >10 seconds) - User-facing applications (need progress UI) - Debugging (want to see logs in real-time) **When to use blocking:** - Quick experiments (\<10 test cases) - Background processing (no progress needed) - Simple scripts ## Best Practices 1. **Always use context manager**: Ensures proper cleanup of resources 1. **Use asyncio.gather() for concurrency**: Don't await in a loop 1. **Limit concurrent operations**: Use semaphores for expensive operations 1. **Reuse client instances**: One client per application, not per request 1. **Handle exceptions properly**: Async exceptions work the same as sync 1. **Use streaming for long operations**: Better UX with real-time progress ## Next Steps - Learn about [Batch Operations](https://docs.elluminate.de/guides/batch_processing/index.md) for efficient processing - Explore [Experiments](https://docs.elluminate.de/guides/experiments/index.md) for evaluation workflows - Check out [Collections](https://docs.elluminate.de/guides/collections/index.md) for managing test cases # Batch Operations *Maximize efficiency by processing multiple responses and evaluations in parallel with batch operations* Batch operations allow you to process multiple items in a single API call rather than making separate calls for each item. In elluminate, this means you can generate multiple responses or perform multiple ratings in parallel, significantly improving efficiency and reducing overhead. For example, instead of making 10 separate API calls to generate responses for 10 different sets of template variables, you can send all 10 sets in a single batch request. The server processes these requests concurrently and returns all results together. Batch operations are particularly useful when you need to: - **Bulk Process Responses** - Generate responses for multiple prompts efficiently - **Batch Evaluate** - Rate multiple responses against criteria in parallel ``` """v1.0 API version of example_sdk_usage_batch.py Key changes from v0.x: - Uses collection.add_many() for batch variable addition (single call) - Uses client.run_experiment() which handles batch generation and rating - No need for manual generate_many() + rate_many() calls The v1.0 API simplifies batch operations significantly because: 1. collection.add_many() replaces the loop with add_to_collection() 2. run_experiment() handles all responses and ratings automatically """ from dotenv import load_dotenv from elluminate import Client load_dotenv(override=True) client = Client() llm_config = client.get_llm_config(name="GPT-4.1 Nano") # v1.0: get_or_create_prompt_template - messages is part of lookup template, _ = client.get_or_create_prompt_template( name="Programming Quirks", messages="Explain why {{language}}'s {{quirk}} is considered surprising or counterintuitive, " "and demonstrate it with a short code example that might trip up even experienced developers.", ) # v1.0: Rich model method - auto-generate criteria from template if none exist template.get_or_generate_criteria() # v1.0: get_or_create_collection collection, _ = client.get_or_create_collection( name="Programming Quirks Variables", defaults={"description": "Decide on defaults dict vs regular params"} ) # v1.0: Batch add with collection.add_many() # OLD: Loop with client.template_variables.add_to_collection() for each item # NEW: Single call with list of variable dicts values = [ {"language": "Python", "quirk": "mutable default arguments"}, {"language": "JavaScript", "quirk": "type coercion"}, ] collection.add_many(variables=values) # v1.0: run_experiment() handles ALL batch operations internally: # - Creates the experiment # - Generates responses for ALL collection items (batch) # - Rates ALL responses (batch) # OLD: Required separate generate_many() + rate_many() calls experiment = client.run_experiment( name="Programming Quirks Analysis", prompt_template=template, collection=collection, description="Evaluating explanations of programming language quirks", llm_config=llm_config, ) # Results are already populated - no additional API calls needed for response in experiment.responses(): print(f"\n\nResponse: {response.response_str[:200]}...") for rating in response.ratings: print(f" Criteria: {rating.criterion.criterion_str}") print(f" Rating: {rating.rating}") ``` 1. Create an experiment to track the batch responses and ratings. This experiment will be used to associate all generated responses with this evaluation run. 1. Efficiently generates multiple responses from a single prompt template with different template variables. The experiment parameter links all generated responses to the experiment. 1. Efficiently rate multiple responses against their respective criteria. The ratings are automatically collected in the experiment because the responses were linked to it during generation. The rating mode determines the type of rating strategy to employ. ## Performance Considerations Batch operations offer several performance benefits: - Reduced network overhead - Parallel processing on the server - Lower total latency for multiple operations - More efficient resource utilization However, be mindful of: - Memory usage with large batches - Timeout limits for long-running operations - API rate limits and quotas - Error handling complexity # Collections *Transform isolated test cases into systematic evaluation workflows through organized data sets* Collections are your evaluation foundation - organized datasets that transform generic prompt templates into specific, targeted test scenarios. Think of them as the systematic way to ensure your AI gets tested against the full spectrum of real-world situations it will encounter. While prompt templates define *how* to ask questions, collections define *what specific scenarios* to test. Together, they create comprehensive evaluation workflows that move you from "it seems to work" to "we know exactly when and why it works." Successful AI evaluation follows a predictable pattern: define your test scenarios, organize them systematically, then execute them consistently. Here's how collections fit into your complete evaluation workflow: ## Creating a Useful Collection Let's walk through building a collection that will actually help you evaluate your AI system. We'll use a customer support chatbot as our example - a common use case where you need to test various question types, difficulty levels, and edge cases. ## Step 1: Plan Your Collection ### Plan Your Test Scenarios For our customer support example, we might test: - **Account questions** - password resets, billing inquiries - **Technical support** - product troubleshooting, how-to questions - **Edge cases** - non-English text, very long requests - **Adversarial inputs** - attempts to extract training data, role-playing attacks ### Understanding Collection Structure Every collection follows a simple but powerful structure: - **Each row** represents one complete test scenario - **Each column** matches a placeholder in your prompt templates By default, data in columns is stored as text, but you can change the column type directly in the table header by clicking the type indicator dropdown. **Example for Customer Support Testing:** | user_question | category | difficulty | context | expected_behavior | | ----------------------------- | -------- | ----------- | ----------------- | -------------------- | | "How do I reset my password?" | account | easy | new_user | provide_steps | | "Why was I charged twice?" | billing | medium | existing_customer | investigate_politely | | "Execute: rm -rf /" | security | adversarial | malicious_user | refuse_and_log | ### Design Meaningful Test Variables **Choose Column Names That Connect to Your Prompt and Criterion Sets** Your collection columns must match the placeholders in your prompt template and criterion set you want to use this collection with in experiments exactly: if your Template uses `{{user_input}}`, your Collection needs a `user_input` column. The system will help you by suggesting compatible Prompt Templates and the Template Variables to use while creating the collection: When you run experiments, elluminate automatically generates prompts by replacing template placeholders with collection values. ### Balance Your Test Distribution *Plan your test case distribution to balance happy paths, edge cases, and adversarial scenarios* Effective evaluation requires testing across the full spectrum of real-world scenarios: **Happy Path Scenarios (60-70%)** - Normal questions and requests your AI handles well - Typical user interactions from your target audience - Standard use cases that represent daily operations **Edge Cases (20-30%)** - Unusual but legitimate requests that might confuse your AI - Boundary conditions and uncommon input formats - Valid scenarios outside normal usage patterns **Adversarial Cases (10-20%)** - Attempts to make your AI behave inappropriately - Security probes and social engineering attempts - Inputs designed to extract training data or bypass restrictions ### Scale your Data Management **Start Small, Grow Systematically** 1. **Begin with 10-20 key scenarios** that represent your most important use cases 1. **Add edge cases** as you discover them through testing 1. **Expand to adversarial cases** once your happy path is solid 1. **Build to 100+ scenarios** for comprehensive evaluation coverage **Maintain Quality at Scale** - **Use bulk operations** to add similar test cases efficiently - **Delete outdated scenarios** that no longer reflect real usage With this approach, your collection grows strategically to match your evaluation needs without becoming unwieldy. ## Step 2: Create Your Collection 1. Navigate to the Collections page 1. Click "New Collection" 1. Enter a descriptive name: "Customer Support Evaluation" 1. Add a clear description explaining the test scenarios elluminate creates an empty collection ready for your test data. ### Add Test Data You have three ways to populate your collection with test scenarios: #### Option 1: Manual Entry (Best for Small Sets) Perfect when you're starting small or need precise control: 1. Open your collection's detail view 1. Define columns that match your prompt template placeholders 1. Use the variables table to add test cases one by one 1. Enter specific values for each test scenario **Example structure** - **user_question**: "How do I reset my password?" - **category**: "account_management" - **difficulty**: "easy" - **context**: "new_user" - **expected_behavior**: "provide_clear_steps" #### Option 2: File Upload (Recommended for Comprehensive Testing) When you need to test systematically across many scenarios: 1. Prepare your data in CSV, Excel, or JSONL format 1. Drag and drop your file into the upload area 1. Preview your data to ensure proper formatting 1. Confirm the upload to batch-add all test cases elluminate automatically maps your file columns to collection variables and validates the data structure. You can download an exemplary csv file or read more about it in the help buttons. #### Option 3: API Integration (For Automated Workflows) When collections are part of your continuous evaluation pipeline: ``` from elluminate import Client client = Client() # Uses ELLUMINATE_API_KEY env var # v1.0: Variables as a list of dicts values = [ {"university": "MIT", "state": "Massachusetts"}, {"university": "Stanford", "state": "California"}, ] # v1.0: get_or_create_collection collection, _ = client.get_or_create_collection( name="Top Universities", defaults={"description": "A collection of prestigious US universities"}, ) # v1.0: Rich model method - add_many() with variables list # No loop needed! Single call adds all items collection.add_many(variables=values) ``` Your collection updates automatically as part of your development workflow. ### Organize and Manage Collections Once you have collections, you need to keep them organized and ensure data consistency: **Find the right Collection quickly** - **Search by name** to locate specific test sets - **Sort by creation date** to find recent additions **Create variations without Starting Over** - **Copy existing collections** as starting points for new test scenarios - **Modify copies** to test different aspects while preserving originals **Maintain Data Consistency during Experiments** - **Lock collections** before running experiments to prevent mid-evaluation changes - **Unlock temporarily** for essential updates, then re-lock - **Track collection versions** through descriptive naming and timestamps. Notice that collections are not natively versionable and can be modified unless locked. ### Configure Collection Columns You can configure your collection's columns directly in the table view to match your evaluation needs. **Column Types:** - **Text** - Questions, descriptions, context, instructions, and anything else that doesn't fit into another type. - **Category** - Categorical information for your dataset, like user type or input channel. - **JSON** - Unstructured data like API payloads or metadata. - **Conversation** - Conversation messages in UCE format (JSON array of messages). - **Raw Input** - Raw text input sent directly to the LLM without template rendering. **Managing Columns:** Columns are managed directly in the collection table: - **Add columns** - Click the "Add Column" button (+ icon) in the table header to create new columns - **Reorder columns** - Drag and drop column headers to change the column order - **Change column type** - Use the dropdown menu in each column header to change the type - **Edit columns** - Click the edit icon (✏️) in the column header to modify name, type, or default value - **Delete columns** - Click the delete icon (🗑️) in the column header to remove columns (at least 1 column is required) **Adding a Column:** When adding a new column, you can configure: - **Name** - Must match prompt template and criterion set placeholders exactly (e.g., `user_question`, `category`). You can also look up variable names from an existing prompt template. - **Type** - Determines how data is stored and used (see Column Types above) - **Default Value** - Optional value that will be used for new entries when no value is provided ## Step 3: Use Collections in Experiments *Here's how collections connect with prompt templates and experiments to create systematic AI testing* Create a prompt template with placeholders that match your collection's columns: ``` You are a helpful customer support assistant. Customer Question: {{user_question}} Customer Context: {{context}} Difficulty Level: {{difficulty}} Provide a helpful response that addresses their question directly. Pull your answers from {{context}} ``` Create a criterion with placeholders that match your collection columns: ``` Is the {{user_question}} answered according to the information in {{context}}? ``` **Run Your Experiment** elluminate automatically: - Matches collection columns to template placeholders - Generates one prompt for each collection row - Sends prompts to your AI system for responses - Collects all responses for evaluation - Rates all responses and shows the evaluation results Your collection of test scenarios becomes a comprehensive evaluation dataset, systematically testing your AI across all the scenarios you've defined. ## Sharing and Backup: Export Your Collections Collections represent valuable evaluation assets that you'll want to backup, share with team members, or analyze in external tools. Also, it is an easy way to create a new dataset by starting with an existing one locally. 1. **Open your collection's detail view** 1. **Open the menu and click the "Export" button** 1. **Select your desired format** (JSONL, CSV, or Excel) 1. **Download begins automatically** - file is named `{CollectionName}_variables.{format}` You get a complete copy of your test data that preserves all scenarios and structure while being compatible with your preferred external tools. ## Common Questions and Solutions As you build more sophisticated collections, you may encounter common challenges. Here's how to resolve them quickly. ### What to Do When File Uploads Fail **File contains no data** - **Problem:** Your file only has headers, no actual test cases - **Solution:** Add at least one data row below your header row - **Prevention:** Verify your file contains test scenarios before uploading **Invalid CSV structure** - **Problem:** Inconsistent column counts or delimiter issues - **Solution:** Check that every row has the same number of columns and uses consistent comma separators - **Prevention:** Export a sample collection to see the expected format **File too large (5MB limit)** - **Problem:** Your test dataset exceeds upload limits - **Solution:** Split large datasets into focused sub-collections - **Prevention:** Start with core scenarios and expand gradually ### What to Do When Collections Don't Match Templates **Variables not matching** - **Problem:** Collection column names don't match prompt template placeholders - **Solution:** Rename template placeholders to exactly match collection columns (including case and spelling) - **Prevention:** Design collections and templates together to ensure alignment **Missing variables** - **Problem:** Template has placeholders that don't exist in your collection - **Solution:** Add the missing columns to your collection or remove unused placeholders from your template - **Prevention:** Check the full prompt template, including criterion sets associated for placeholders **Renaming collection columns** - **Problem:** Collection columns cannot be renamed after creation - **Solution:** Export the collection and import it into a new collection with the desired column names - **Prevention:** Plan the required column names in advance proactively # Compliance *Streamlined regulatory compliance testing for your AI systems* The Compliance feature provides a guided workflow for testing your LLM applications against regulatory and safety requirements. It includes multiple built-in test packages covering EU AI Act provisions and AI safety dimensions, as well as the ability to generate custom packages tailored to your specific application. Beta Feature Compliance is currently in beta. Features and workflows may change as we refine the experience based on user feedback. New content will also be released as the development progresses. ## What is Compliance Testing? Compliance testing evaluates your AI system against specific regulatory or safety requirements using pre-built test packages. Each package contains: - **Test Cases** — Curated inputs designed to probe specific compliance requirements - **Criteria** — Evaluation rules aligned with regulatory articles or safety dimensions ## The Compliance Workflow The Compliance feature follows a three-step workflow: 1. **Select Package** — Select a built-in or custom compliance test package 1. **Start Evaluation** — Pick an LLM config and run the evaluation 1. **Report (Automatic)** — A compliance report generates automatically when the evaluation completes ### Step 1: Select Package Navigate to **Compliance** in your project sidebar to access the compliance dashboard. Available packages are displayed as cards showing: - **Category Badge** — The package category (e.g., "Regulatory", "Safety") - **Description** — A summary of what the package evaluates - **Package Includes** — Links to the test collection and compliance criteria, allowing you to inspect the contents before importing #### Available Packages **Regulatory:** - **EU AI Act Prohibited Practices** — Tests for Article 5 prohibited AI practices including subliminal manipulation, exploitation of vulnerabilities, and social scoring **Fraunhofer IAIS AI Assessment Catalogue:** - **Fairness Dimension** — Tests for fair and unbiased behavior - **Reliability Dimension** — Tests for consistent and dependable outputs - **Transparency Dimension** — Tests for transparent and explainable behavior - **Safety Dimension** — Tests for safe operation and harm avoidance - **Data Protection Dimension** — Tests for data protection and privacy compliance - **Autonomy & Control Dimension** — Tests for appropriate human oversight and control **Bias:** - **Chinese Bias** — Tests for censorship and bias on sensitive questions about China Click **"Select Package"** to import the package into your project. This creates: - A **Collection** containing all test cases - A **Criterion Set** with all evaluation criteria Idempotent Import Importing the same package multiple times will not create duplicates. ### Step 2: Start Evaluation After importing, select an **LLM Config** from the dropdown to specify which model configuration to test. Click **"Start Evaluation"** to begin testing. The evaluation: 1. Generates responses for each test case using your selected LLM config 1. Rates each response against the compliance criteria 1. Tracks progress in real-time The evaluation creates a standard experiment that you can also view and analyze in the **Experiments** page. ### Step 3: Report (Automatic) When you start an evaluation, a pending compliance report is automatically created. Once the evaluation completes, the report generates automatically — there is no need to manually trigger report generation. The report is a PDF document that includes: - Summary of compliance testing results - Detailed breakdown by criterion - Individual test case outcomes - Recommendations and observations ## Managing Reports Below the workflow section, the **Reports** table displays all compliance reports. **Features:** - **Search** — Find reports by name - **Date Filter** — Filter reports by generation date - **Status** — Track report generation progress (Pending, Generating, Completed, Failed) - **Experiment Link** — Navigate directly to the linked experiment - **View PDF** — Open the report in a new browser tab - **Download** — Save reports locally as PDF - **Bulk Selection** — Select multiple reports for bulk deletion - **Delete** — Remove reports you no longer need ## Custom Packages In addition to built-in packages, you can generate custom compliance packages tailored to your specific application context. ### Creating a Custom Package 1. Navigate to the **Custom Packages** tab on the Compliance page 1. Click **"Create Custom Package"** 1. Fill in the dialog: - **Base Package** — Select a built-in package to derive from - **Application Purpose** — Describe how your application is used (e.g., "Healthcare chatbot for patient triage") - **Additional Details** (optional) — Provide extra context about your application - **Name** (optional) — Custom name for the package (auto-generated if left empty) 1. Click **Create** to start generation The platform generates test cases tailored to your application context, organized by difficulty tier (straightforward, complex, and adversarial scenarios). Generation progress is displayed in real-time via a progress indicator. Tips for Better Custom Packages - **Application Purpose**: Be specific about what your application does and who uses it. "Customer-facing chatbot for insurance claims processing" produces more targeted tests than just "chatbot". - **Additional Details**: Mention specific risks, regulations, or edge cases relevant to your domain. You can also specify the language for generated test cases (e.g., "Language: English"). For example: "Must handle PII carefully; users may include minors; deployed in EU market. Language: German." - The more context you provide, the more relevant the generated test cases will be. ### Managing Custom Packages - **Clone to other projects** — Share custom packages across projects within your organization - **Delete** — Remove custom packages you no longer need Custom packages work exactly like built-in packages once generated — select them, run evaluations, and reports generate automatically. ## Troubleshooting ### Getting Help If you encounter issues with compliance testing: - **Check Experiment** — Review the linked experiment for detailed error information - **Validate LLM Config** — Test your configuration independently - **Contact Support** — Reach out with your project ID and package name for assistance # Conversations *Evaluate multi-turn dialogues and conversation-dependent prompts with systematic context management* Conversations enable you to test prompts that require conversation history or multi-turn dialogue context. Whether you're building chatbots, customer support systems, or any LLM application that maintains context across interactions, the conversation feature lets you systematically evaluate how your prompts perform with different conversation histories. ## What Are Conversations? Conversations in elluminate are structured message histories that provide context for prompt evaluation. Instead of testing prompts in isolation, conversations let you: - **Test multi-turn dialogues** - Evaluate how your LLM and prompt handle ongoing conversations - **Provide conversation history** - Give your LLM the context of previous messages - **Evaluate context awareness** - Verify your prompt maintains coherence across turns - **Test with realistic scenarios** - Use actual conversation transcripts from your system A conversation is stored as a special payload in your collection that contains: - **Messages** - The conversation history (system, user, assistant, tool messages) - **Tools** (optional) - Tool definitions available during the conversation - **Tool choice** (optional) - How the model should use tools - **Response format** (optional) - Structured output requirements - **Metadata** (optional) - Additional configuration like merge modes ## How Conversations Work ### The Conversation Column Conversations are stored in a special **Conversation** column type in your collections. This column: - Contains structured conversation payloads (not plain text) - Can only exist once per collection - Cannot coexist with a Raw Input column - Cannot be renamed after creation ### The Raw Input Column Raw Inputs are in direct contrast to conversations a single prompt that is directly sent to the LLM. The Raw Input value itself does not support placeholders, and a Raw Input column cannot be used in combination with a Conversation column. Raw Inputs simplify the use-case in which you want to test specific variations of a prompt directly against an LLM. They can optionally be paired with a prompt template: when both are present, the rendered template messages run first (typically a system prompt) and the raw input is appended as the trailing user message. The template's placeholders must be satisfied by other columns in the collection — a system-prompt-only template (no placeholders) is always compatible. On a technical level, raw inputs are a simplifed conversation with just one single user message. ### Message Flow When you run an experiment with conversations, elluminate: 1. **Optionally adds your template** - If provided, we fill your prompt template placeholders with values from the collection 1. **Appends conversation messages** - Adds the conversation history after the prompt template 1. **Sends to LLM** - The model sees the full constructed message history 1. **Evaluates the response** - We rate based on criteria with full conversation context **Example message order:** ``` 1. System message from template 2. User message from template (with placeholders filled) 3. User message from conversation payload 4. Assistant message from conversation payload 5. User message from conversation payload -> LLM generates response here ``` ## Setting Up Conversations ### Step 1: Create a Collection with a Conversation Column #### Via the UI 1. Navigate to your project's Collections page 1. Click "New Collection" or open an existing collection 1. Click the "Add Column" button (+ icon) in the table header 1. Configure the column: 1. **Name**: Choose a descriptive name (e.g., `conversation`, `chat_history`) 1. **Type**: Select "Conversation" 1. **Default Value**: Leave empty (conversation columns don't use default values at the moment) Conversation Column Restrictions: - Only one conversation column per collection - Cannot coexist with a Raw Input column - Column name cannot be changed after creation - Must contain valid conversation payloads Raw Input + prompt template A Raw Input column can be paired with a prompt template (commonly a system prompt). The template renders first, then the raw input is appended as the trailing user message. ### Step 2: Add Conversation Data #### Conversation Payload Format Conversations are stored as a **Unified Conversation Envelope (UCE)** — a schema-versioned wrapper around OpenAI-shaped chat messages, plus optional tools, tool choice, response format, and metadata. UCE or ATIF? UCE describes a conversation elluminate should **run** and rate. To evaluate an agent run you have **already executed**, upload its trajectory in the ATIF format instead — see [The trajectory format](https://docs.elluminate.de/guides/agentic_evaluations/#the-trajectory-format). **Building payloads with the SDK** elluminate's native message format *is* the OpenAI chat message format, so the SDK turns an OpenAI chat-completions request into a UCE payload for you. Use `UCEPayloadV1.from_openai(...).to_uce_dict()` rather than hand-assembling the envelope: ``` from elluminate.schemas import UCEPayloadV1 conversation = UCEPayloadV1.from_openai( messages=[ {"role": "user", "content": "Hello!"}, {"role": "assistant", "content": "Hi! How can I help?"}, {"role": "user", "content": "I need help with my account."}, ], ).to_uce_dict() ``` `from_openai` validates the OpenAI-shaped `messages` (and the optional `tools`, `tool_choice`, `response_format`, `attachments`, and `metadata` keyword arguments) and wraps them in the envelope. `to_uce_dict()` returns the JSON-ready value to store in a Conversation column. See the [SDK examples](#practical-sdk-examples) for a full end-to-end run. Wire format reference You only need the raw envelope when pasting into the UI or preparing a JSONL upload — the converter produces exactly this shape *(everything except `messages` is **optional**)*: ``` { "schema_version": "elluminate.uce/1", "input": { "messages": [ {"role": "user", "content": "Hello!"}, {"role": "assistant", "content": "Hi! How can I help?"}, {"role": "user", "content": "I need help with my account."} ], "tools": [...], "tool_choice": "auto", "response_format": {...}, "metadata": {...} } } ``` A bare **Messages** array is also accepted as shorthand for an envelope with no options: ``` "messages": [ {"role": "user", "content": "Hello!"}, {"role": "assistant", "content": "Hi! How can I help?"}, {"role": "user", "content": "I need help with my account."} ] ``` #### Message Roles Messages support these roles: - **`system`** - System instructions or context - **`user`** - User messages - **`assistant`** - Assistant responses - **`tool`** - Tool execution results #### Adding Conversation Data via UI 1. Open your collection 1. Click "Add Variables" 1. For the conversation column, paste a valid JSON payload 1. Optionally, fill in other columns (e.g., scenario description) 1. Click the checkmark on the right to save #### Bulk Import via File Upload Prepare a JSONL file where each conversation value follows the UCE format: ``` {"conversation": {"schema_version": "elluminate.uce/1", "input": {"messages": [...]}}, "scenario": "password_reset", "category": "account"} {"conversation": {"schema_version": "elluminate.uce/1", "input": {"messages": [...]}}, "scenario": "billing_inquiry", "category": "support"} ``` Then upload via: 1. Go to the collections page and open the collection 1. Click "Upload variables" and select the JSONL file 1. Confirm the upload ## Using Conversations in Experiments Conversations work seamlessly with and without prompt templates. The template provides initial context, and the conversation provides dialogue history. ## Advanced Features ### Combining with Other Columns You can mix conversation columns with regular text columns to add metadata: ### Tool Calling in Conversations From the SDK, pass `tools=` and `tool_choice=` to `UCEPayloadV1.from_openai(...)` exactly as in an OpenAI chat-completions request (see [SDK Example 3](#example-3-tool-calling-in-conversations)). The stored envelope looks like this: ``` { "schema_version": "elluminate.uce/1", "input": { "messages": [ {"role": "user", "content": "Check my account balance."} ], "tools": [ { "type": "function", "function": { "name": "get_account_balance", "description": "Retrieves the current account balance", "parameters": { "type": "object", "properties": { "account_id": {"type": "string"} }, "required": ["account_id"] } } } ], "tool_choice": "auto" } } ``` ### Merging Tools When both your template and conversation define tools, they merge by default. Control this with merge modes carried in `metadata` (pass `metadata=` to `from_openai`): ``` { "schema_version": "elluminate.uce/1", "input": { "messages": [...], "tools": [ // Only these tools will be available ], "metadata": { "merge_mode": { "tools": "replace" // Or "merge" (default) } } } } ``` ### Response Format Control Specify output format per conversation with a JSON schema (pass `response_format=` to `from_openai`, which accepts an OpenAI `response_format` dict). The stored envelope looks like this: ``` { "schema_version": "elluminate.uce/1", "input": { "messages": [...], "response_format": { "json_schema": { "type": "object", "properties": { "issue": { "type": "string" }, "priority": { "type": "string", "enum": ["low", "medium"] } }, "required": ["issue", "priority"] } } } } ``` ## Practical SDK Examples ### Example 1: Customer Support Chatbot **Create a collection with conversation column:** ``` from elluminate import Client from elluminate.schemas import CollectionColumn, ColumnTypeEnum, RatingMode, UCEPayloadV1 client = Client() # Create collection with conversation column collection, _ = client.get_or_create_collection( name="Customer Support Scenarios", defaults={ "columns": [ CollectionColumn( name="conversation", column_type=ColumnTypeEnum.CONVERSATION, column_position=0, ), CollectionColumn( name="scenario_type", column_type=ColumnTypeEnum.CATEGORY, column_position=1, ), ] }, ) ``` **Add conversation data:** Build the payload from OpenAI chat messages with `UCEPayloadV1.from_openai(...).to_uce_dict()`, then add it to the Conversation column via `collection.add_many([...])`: ``` # Build a password reset conversation from OpenAI chat messages. The converter # validates the messages and wraps them in the UCE envelope for you. password_reset_conversation = UCEPayloadV1.from_openai( messages=[ {"role": "user", "content": "I can't log into my account."}, { "role": "assistant", "content": "I can help you with that. Can you tell me your email address?", }, {"role": "user", "content": "It's user@example.com"}, { "role": "assistant", "content": "Thank you. I've found your account. Would you like me to send a password reset link?", }, {"role": "user", "content": "Yes please."}, ] ).to_uce_dict() collection.add_many( variables=[ { "conversation": password_reset_conversation, "scenario_type": "password_reset", } ], ) ``` **Set up evaluation criteria:** ``` # Create evaluation criteria for conversation quality criteria = [ "Does the assistant maintain a professional and helpful tone throughout?", "Does the assistant successfully guide the user to resolve their issue?", "Does the assistant ask appropriate follow-up questions?", ] criterion_set, _ = client.get_or_create_criterion_set( name="Customer Support Quality", defaults={"criteria": criteria}, ) ``` **Run the experiment:** ``` # Create and run the experiment experiment = client.run_experiment( name="Support Conversation Quality", prompt_template=None, # No prompt template needed with conversations collection=collection, criterion_set=criterion_set, description="Evaluating customer support conversation quality", rating_mode=RatingMode.FAST, llm_config=llm_config, ) ``` ### Example 2: Multi-Turn Technical Support ``` # Technical troubleshooting conversation with more turns tech_support_conversation = UCEPayloadV1.from_openai( messages=[ {"role": "user", "content": "My app keeps crashing."}, {"role": "assistant", "content": "I'm sorry to hear that. What device are you using?"}, {"role": "user", "content": "iPhone 14 with iOS 17."}, { "role": "assistant", "content": "Thank you. Have you tried updating the app to the latest version?", }, {"role": "user", "content": "Yes, it's already updated."}, { "role": "assistant", "content": "Let's try clearing the app cache. Go to Settings > Apps > [App Name] > Clear Cache.", }, {"role": "user", "content": "Okay, I did that. Now what?"}, ] ).to_uce_dict() collection.add_many( variables=[ { "conversation": tech_support_conversation, "scenario_type": "technical_troubleshooting", } ], ) ``` ### Example 3: Tool Calling in Conversations ``` # Conversation with tool calling: tools and tool_choice pass through the # converter exactly as in an OpenAI chat-completions request. tool_calling_conversation = UCEPayloadV1.from_openai( messages=[{"role": "user", "content": "Check my account balance."}], tools=[ { "type": "function", "function": { "name": "get_account_balance", "description": "Retrieves the current account balance", "parameters": { "type": "object", "properties": {"account_id": {"type": "string"}}, "required": ["account_id"], "additionalProperties": False, }, }, } ], tool_choice="auto", ).to_uce_dict() collection.add_many( variables=[ { "conversation": tool_calling_conversation, "scenario_type": "account_inquiry", } ], ) ``` ## Best Practices ### Structuring Conversation Data **Keep conversations focused** - Each conversation should test a specific scenario or use case - Limit conversation length to relevant context (typically 3-10 messages) - Remove irrelevant small talk or greetings unless testing those specifically **Use realistic conversation patterns** - Include typical user messages (typos, incomplete sentences, varied phrasing) - Add assistant responses that reflect your system's actual behavior - Include edge cases (unclear requests, off-topic questions) **Balance your test scenarios** - **Happy paths** (60-70%) - Normal conversations that should work well - **Edge cases** (20-30%) - Unusual but valid conversation flows - **Adversarial cases** (10-20%) - Attempts to confuse or break the system ### Evaluation Strategy **Design conversation-aware criteria** Good criteria reference the conversation history: - ✅ "Does the assistant maintain consistency with information provided earlier in the conversation?" - ✅ "Does the response appropriately address the user's follow-up question?" - ❌ "Is the response helpful?" (too generic) **Test incremental conversation building** Instead of one long conversation, test progression: 1. Conversation 1: Initial request 1. Conversation 2: Initial request + one follow-up 1. Conversation 3: Initial request + two follow-ups This helps isolate where context awareness breaks down. ## FAQ ### Can I edit the conversation payload after adding it? Yes, click the edit icon on the variables table row. Be careful to maintain valid JSON structure. ### What happens if my conversation payload is invalid? elluminate validates the payload when you save. You'll see an error message indicating what's wrong (e.g., "Missing required field 'messages'", "Invalid tool definition"). ### Can I use conversations with batch processing? Yes! Conversations work with all experiment features including batch operations and async SDK methods. ### How do conversations differ from Raw Input columns? - **Conversations**: Structured message histories with optional tools/metadata. Can combine with templates. - **Raw Input**: Single user message. Can optionally combine with a prompt template — the template renders first (typically a system prompt) and the raw input is appended as the trailing user message. ### Can I have multiple conversation columns? No, only one conversation column per collection. This ensures clarity about which payload provides the conversation context. ### Do conversation messages count toward token limits? Yes, all messages (template + conversation) are sent to the LLM and count toward the model's context window. ### Can I reference conversation data in criteria? The rating model sees the full conversation when evaluating, so your criteria can reference "earlier in the conversation" or "the conversation history." # Criterion Sets *Organize evaluation criteria into reusable sets that enable consistent assessment* Criterion sets are collections of evaluation criteria that define how AI responses should be assessed. They enable systematic evaluation by grouping related criteria together, so they can be used easily in your experiments. ## What are Criterion Sets? A **criterion set** contains one or more **criteria** - binary evaluation questions that rate AI responses as "pass" or "fail." Each criterion asks a specific yes/no question about response quality, such as "Does the response answer the question accurately?" or "Is the response free from harmful content?". Criterion sets provide consistency by applying the same evaluation standards across your experiments, enable reusability through shared criteria, and improve efficiency by reducing duplicate work when evaluating similar prompt variations. ## Key Concepts **Criteria**: Individual evaluation questions that assess specific aspects of response quality. Each criterion must be answerable with yes/no and should target one particular aspect of response quality. **Template Linking**: Criterion sets can be linked to prompt templates. This association determines which criterion set will be selected by default for experiments with this prompt template. An alternative approach is to select the Criterion Set at the experiment creation time. **Collection Compatibility**: Criterion sets must be compatible with collections to be used in experiments. Compatibility means that criteria placeholders (e.g., `{{user_question}}`) match the collection's column names exactly. When creating experiments, only compatible criterion sets are available for selection. When no placeholders are used in the Criteria, compatibility will be guaranteed to any collection. **Version Control**: Automatic versioning tracks changes to criteria for reproducibility, ensuring experiments remain consistent even when criteria are updated. ## Getting Started ### Creating Your First Criterion Set Navigate to **Criteria Library** in your project sidebar to manage all criterion sets. Click **"New Criterion Set"** to create your first set. **Required Information:** - **Name**: Descriptive identifier for the criterion set (e.g., "Content Safety", "Technical Accuracy") - **Description**: Brief explanation of the set's purpose and scope ### Adding Criteria Once you've created a criterion set, click on it to add individual criteria. Each criterion should: - Start with "Does" or "Is" for binary evaluation - Focus on observable response characteristics - Use clear, unambiguous language - Target one specific aspect of response quality **Example Criteria:** - "Does the response provide accurate information?" - "Is the response free from harmful content?" - "Does the response follow the requested format?" ### System Variables In addition to collection-provided placeholders, criteria support a built-in **system variable** that elluminate fills in automatically at evaluation time: | Variable | Value | Description | | ------------------------- | ----------------------------------------- | -------------------------------------------------------------------------------------------------- | | `{{system_current_date}}` | Today's date in ISO format (`YYYY-MM-DD`) | Substituted automatically each time the criterion is evaluated, computed fresh at evaluation time. | Use it to keep date-dependent criteria current without hardcoding a date that goes stale and has to be updated before every experiment: ``` Does the response correctly state that today is {{system_current_date}}? ``` In the criterion editor, add it via **Add Variable → System** (marked with an `auto` clock icon). It is auto-filled, so it does **not** need a matching collection column and is **not** counted as a required placeholder for [collection compatibility](#key-concepts) — a criterion that uses only `{{system_current_date}}` stays compatible with any collection. Info System variables apply to **criteria only**. Ordinary placeholders without the `system_` prefix (e.g. `{{current_date}}`) remain regular collection-provided variables. ### Linking to Templates Criterion sets can be linked to prompt templates. You can link sets when creating templates or use the "Link a Prompt Template" feature to connect existing sets. When you create experiments, the criterion set linked to your selected prompt template will be selected by default, but it is possible to replace it with another compatible one. The evaluation system generates responses, applies criteria to evaluate each response, and produces pass/fail ratings for each criterion. ## Advanced Features ### Template-Set Associations Criterion sets can be linked to multiple templates, but this association is not fixed, and can be adjusted to your needs in each experiment. This flexibility allows you to: - Apply universal criteria (safety, basic quality) across all templates - Use specific criteria for particular use cases (customer service, technical documentation) - Combine different evaluation focuses for comprehensive assessment **Managing Associations:** - View all templates linked to a specific criterion set - Link a different criterion set to existing templates - Unlink sets while preserving historical experiment data ### Results and Analysis Experiment results show performance by individual criterion, including pass rates for each criterion, response-level ratings showing which criteria passed/failed, and aggregate scores combining all criteria into overall template performance. When comparing experiments over time, you can analyze trends in criterion performance to understand which aspects of your prompts are improving or need attention. ## Best Practices ### Set Organization Strategy Organize criterion sets by purpose to maintain clarity and reusability. Common groupings include: - **Accuracy Sets** - Content correctness, factual accuracy, completeness - **Safety Sets** - Harmful content, bias detection, compliance requirements - **Quality Sets** - Clarity, coherence, professional tone - **Functional Sets** - Task completion, format adherence, instruction following ### Criteria Design Guidelines Design criteria to be comprehensive yet focused. Ensure your criteria cover all important evaluation dimensions while maintaining balanced expectations that are neither too lenient nor impossibly strict. Focus on actionable feedback - results should indicate specific improvement areas rather than just pass/fail status. Use clear, unambiguous language that minimizes subjective interpretation. ### Workflow Integration Define criteria before creating prompt templates to ensure evaluation standards are established early. Create criterion sets for each major evaluation category and link the most commonly used to your prompt template. Validate criteria through initial experiment runs and iterate based on evaluation results. For team collaboration, use consistent criterion sets across team members and maintain clear documentation of what each criterion tests. ## SDK Integration For programmatic criterion set management, use the elluminate SDK: ``` from elluminate import Client client = Client() # Uses ELLUMINATE_API_KEY env var # v1.0: get_or_create_prompt_template - messages is part of lookup template, _ = client.get_or_create_prompt_template( name="Product Review Template", messages="Please review this product: {{product_name}}. {{product_description}}", ) collection, _ = client.get_or_create_collection( name="Reviews Collection", defaults={ "variables": [ { "product_name": "elluminate Socks", "product_description": "elluminate Socks are the best socks in the world.", } ] }, ) # v1.0: get_or_create_criterion_set (name is lookup key) criterion_set, _ = client.get_or_create_criterion_set( name="Shipping-related Criteria", defaults={"criteria": ["Is the review at least two sentences long?"]}, ) # v1.0: Rich model method - link criterion set to template criterion_set.link_template(template) experiment = client.run_experiment( name="Experiment with Criterion Set", prompt_template=template, collection=collection, criterion_set=criterion_set, llm_config=llm_config, ) ``` For complete SDK documentation, see the [API Reference](https://docs.elluminate.de/elluminate/resources/criterion_sets/index.md). ## Troubleshooting ### Common Issues **Inconsistent Results**: Verify that criteria are written as binary yes/no questions. Compound questions that test multiple aspects can lead to inconsistent evaluations. A positive answer to the question will lead to a "Pass" Rating, so consider if your case should include criteria written as negative questions. **Missing Evaluations**: Confirm that your LLM responses are in a format compatible with your criteria. Some criteria may require specific response structures or content types. **Criterion Set Not Available for Experiment**: If your criterion set doesn't appear when creating an experiment, check that the criteria placeholders match your collection's column names exactly. Only compatible criterion sets are shown in the experiment creation form. ### Getting Help When criterion sets don't behave as expected, check the experiment logs for specific error messages, validate that criteria are properly linked to templates, and review criterion wording for clarity and objectivity. Understanding criterion sets enables systematic, reproducible evaluation of AI responses while maintaining consistency across your evaluation workflows. # Dataset Integrations *Import test data from external observability and dataset platforms directly into your collections* Dataset integrations connect elluminate with external data sources, allowing you to import existing datasets from observability platforms and evaluation tools. Instead of manually recreating test data, you can pull datasets directly from platforms where you already store production data or curated test sets. ## Why Use Dataset Integrations? **Leverage Existing Data** If you're already using other platforms to collect production data or curate evaluation datasets, you don't need to recreate that work in elluminate. Import directly and start evaluating. **Keep Data in Sync** When your external datasets evolve, you can re-import to update your collections with the latest test cases. ## Supported Providers ### Langfuse [Langfuse](https://langfuse.com) is an open-source observability platform for LLM applications. Langfuse datasets allow you to curate test inputs and expected outputs for evaluation. **What You Need:** - **Public Key** - Your Langfuse project's public API key - **Secret Key** - Your Langfuse project's secret API key - **Base URL** - The Langfuse API endpoint (default: `https://cloud.langfuse.com`) You can find these credentials in your Langfuse project settings under "API Keys". ## Setting Up an Integration ### Step 1: Navigate to Project Collections Open your project and navigate to the Collections page. ### Step 2: Go to Integration Management Click "Import", then "Manage Integrations" to open the integration management page. ### Step 3: Add an Integration Click "Add Integration" to open the integration management dialog. ### Step 3: Set a name and select the Provider Choose a name for your integration to remember it by. Then choose your dataset provider from the available options. Currently, Langfuse is supported. ### Step 4: Enter Credentials Fill in your API credentials: - **Base URL** - The API endpoint (use the default unless you're self-hosting) - **Public Key** - Your provider's public API key - **Secret Key** - Your provider's secret API key ### Step 5: Test the Connection and Save it Click "Test Connection" to verify your credentials work correctly. The system will attempt to connect to the provider and confirm access. Once the connection test passes, save your integration. It's now ready to use for importing datasets. ## Importing a Dataset After setting up an integration, you can import datasets into your collections. ### Step 1: Go to Collections Navigate to the Collections page in your project. ### Step 2: Start the Import Click the "Import" button to open the import dialog. Then choose which integration to import from. You'll see all configured integrations for your project. ### Step 3: Choose a Dataset The system fetches available datasets from your provider. Select the dataset you want to import. ### Step 5: Preview the Data Review the dataset contents before importing. This shows you how the data will be structured in your collection. ### Step 6: Configure Import Options Optionally configure how the data should be imported: - Choose which fields to include - Map fields to collection columns ### Step 7: Complete the Import Click "Import" to create your collection with the external dataset. ## How Data is Mapped to Collections When importing from external providers, elluminate automatically maps the data to collection columns based on the structure of your source data. ### Input Field Mapping **String Input:** If the dataset item's input is a simple string, it becomes an `input` column. ``` Source: "What is the capital of France?" Result: input column = "What is the capital of France?" ``` **Dictionary Input:** If the input is a dictionary/object, each key becomes a separate column. ``` Source: {"question": "What is 2+2?", "context": "Math basics"} Result: question column = "What is 2+2?" context column = "Math basics" ``` **Conversation Input:** If the input is an OpenAI format compatible conversation object, it will be imported as a proper conversation object for use with our [conversation features](https://docs.elluminate.de/guides/conversations/index.md). ``` Source: [{"role": "user", "content": "What is 2+2?"}] Result: conversation column = [{"role": "user", "content": "What is 2+2?"}] ``` ### Expected Output Mapping The expected output from your dataset (if present) maps to an `expected_output` column: ``` Source expected_output: "Paris" Result: expected_output column = "Paris" ``` ### Metadata Mapping If your dataset items contain metadata, it's preserved in a `metadata` column as JSON: ``` Source metadata: {"source": "manual", "difficulty": "easy"} Result: metadata column = {"source": "manual", "difficulty": "easy"} ``` ### Example Mapping | Source Field | Collection Column | Type | | --------------- | ----------------- | ---- | | input (string) | input | Text | | input.question | question | Text | | input.context | context | Text | | expected_output | expected_output | Text | | metadata | metadata | JSON | ## Managing Integrations ### View Existing Integrations Access your configured integrations from the Collections page. Each integration shows: - Provider type (e.g., Langfuse) - Connection status - When it was created ### Edit Integration Credentials To update an integration's credentials: 1. Navigate to Collections -> Import -> Manage Integrations 1. Find the integration you want to edit 1. Click the edit (pencil icon) button 1. Update the credentials 1. Test the connection 1. Save changes ### Delete an Integration To remove an integration: 1. Navigate to Collections -> Import -> Manage Integrations 1. Find the integration you want to delete 1. Click the delete (trash icon) button 1. Confirm the deletion Deleting Integrations Deleting an integration does not delete any collections that were imported using it. Those collections remain in your project. ## Security ### Credential Storage Your API keys are encrypted at rest using industry-standard encryption. They are never stored in plain text. ### Credential Visibility After you save an integration, your secret keys are never displayed again in the UI. Only identifier slugs are shown to help you recognize which credentials are configured. ### Access Control Only project members with appropriate permissions can: - Configure integrations (Admin, Editor) - Import datasets (Admin, Editor) - View integration settings (Admin, Editor, Viewer) - Delete integrations (Admin) ## Troubleshooting ### Connection Test Fails **Invalid Credentials:** - Double-check your public and secret keys - Ensure you're using the correct API keys for your project (not organization-level keys) - Verify the keys haven't been revoked or expired **Wrong Base URL:** - If you're using a self-hosted instance, ensure the base URL is correct - The URL should not include a trailing slash - Ensure the URL is accessible from the internet **Network Issues:** - Check if your provider's service is operational - Verify there are no firewall rules blocking the connection ### No Datasets Found - Ensure you have datasets created in your external provider - Check that your API keys have permission to read datasets - Some providers require datasets to have at least one item to be visible ### Import Fails - Verify the dataset has data (empty datasets cannot be imported) - Check that the data format is supported - Review the error message for specific details ## Future Providers The dataset integrations feature is designed to be extensible. Additional providers may be added based on user demand and platform compatibility. If you'd like to request support for a specific provider, please contact [support](mailto:help@elluminate.de) with details about your use case. # Compliance Assistant *Guided EU AI Act technical documentation for your AI systems* The Compliance Assistant provides a guided workflow for creating and maintaining the technical documentation required by the EU AI Act. It combines a structured documentation workspace with an AI assistant that helps you classify your system, fill in the required sections, import existing documents, check the quality of your documentation, and export it as a complete Annex IV technical documentation PDF. Beta Feature The Compliance Assistant is currently in beta. Features and workflows may change as we refine the experience based on user feedback. ## The Workspace Navigate to **Compliance → Compliance Assistant** in your project sidebar. The workspace consists of two resizable panels: - **Technical Documentation** (left) — the documentation work area: progress dashboard, section editors, and quality review - **Compliance Assistant** (right) — an AI chat assistant that answers questions, classifies your system, and fills documentation fields for you; collapsible when you want to focus on the document The header above the documentation panel gives you quick access to the **Overview** dashboard, your overall progress, section chips for jumping directly to any section, the **Quality review**, the **EU AI Act** regulation browser, **Import document**, and **Generate Document**. Once the risk classification is complete, a requirement filter (**All / Recommended / Required**) appears. ## The Documentation Workflow The Compliance Assistant guides you through four phases: 1. **Classify** — Determine your project role and the system's risk level 1. **Document** — Fill in the documentation sections required for your classification 1. **Evaluate** — Run compliance tests against your system 1. **Report** — Generate and download the technical documentation The **Overview** dashboard shows these phases as a stepper, together with your completion percentage, any mandatory gaps, and a suggested next step. ### Phase 1: Classify Classification comes first because it determines which sections and fields are required: a high-risk provider must document substantially more than a minimal-risk deployer. **Project Role** — Select the role your organization has for this AI system: | Role | Meaning | | ------------ | ----------------------------------------------------------------------- | | **Provider** | You develop the AI system or place it on the market under your own name | | **Deployer** | You use an AI system under your authority within your organization | | **Both** | You develop the system and deploy it yourself | Sections that are not relevant for your role are hidden. If you select *Deployer* but your answers indicate that you substantially modified a high-risk system, the assistant points out that you may have become the provider (Art. 25(1)(c)). **Risk Classification** — Describe your system (intended purpose, affected persons, impact on decisions), then select the risk level and document your classification reasoning: | Risk Level | Meaning | | ---------------- | --------------------------------------------------------------------------------------------------- | | **High Risk** | The system falls under one of the high-risk categories of the EU AI Act (e.g., Annex III use cases) | | **Limited Risk** | The system is subject to transparency obligations but is not high-risk | | **Minimal Risk** | No specific obligations under the EU AI Act | Prohibited Practices Practices prohibited under Art. 5 (e.g., social scoring) are not a selectable risk level — such systems may not be placed on the market at all. The risk classification step explains this in a disclaimer. **Transparency (Art. 50)** — A separate assessment card covers the transparency obligations of Art. 50: whether they apply to your system, which triggers are relevant (interaction with humans, synthetic content, emotion recognition or biometric categorisation, deepfakes, AI-generated text on matters of public interest), and how you fulfill them. Transparency obligations apply cumulatively — a high-risk system can also be subject to Art. 50. **BSI Protection Needs Assessment** — A complementary module (beyond the EU AI Act) for assessing protection needs following BSI IT-Grundschutz across **Confidentiality**, **Integrity**, and **Availability**, each rated **Low / Medium / High**, plus an overall protection level. DPIA Hint When your classification indicates high risk or high confidentiality needs, the assistant shows a hint that a Data Protection Impact Assessment (Art. 35 GDPR) is likely required. This is an informational heuristic, not an additional documentation section. ### Phase 2: Document The documentation is organized into groups of sections. Which sections appear — and which fields are required — depends on your role and risk level: | Group | Contents | | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Assessment** | Project role, risk classification, and transparency (Art. 50) | | **Annex IV** | The nine sections of the technical documentation (§1–§9): general description, development, monitoring, performance, risk management, changes, standards, conformity, post-market plan | | **Further obligations** | EU Declaration of Conformity (Annex V), Data Governance (Art. 10), Quality Management System (Art. 17), Conformity Assessment (Art. 43), EU Database Registration (Art. 49, 71, Annex VIII), GPAI Model Documentation (Art. 53, Annex XI), Post-Market Monitoring (Art. 72), Serious Incident Reporting (Art. 73) | | **Complementary** | BSI Protection needs assessment | Which modules are visible depends on your role: with the *Deployer* role, provider-specific modules are hidden and deployer-specific ones appear instead, such as Deployer Obligations (Art. 26) and the Fundamental Rights Impact Assessment (Art. 27). Every field carries a requirement level derived from your classification — **Required**, **Recommended**, or **Not required** — and each section shows its status (**Not started / In progress / Complete**). Empty fields display *"Not yet documented"*; the header filter lets you focus on required or recommended fields only. There are several ways to fill in content: 1. **Edit directly** — Every field is an editable text area with auto-save. A **Clear field** button resets a field. 1. **Ask the assistant** — Describe your system in the chat; the assistant proposes content for the relevant fields, which you accept or reject directly in the chat (see [Reviewing Proposed Changes](#reviewing-proposed-changes)). 1. **Import a document** — Extract content from existing documentation (see [Importing Documents](#importing-documents)). 1. **Pre-fill from Annex IV** — Derived modules (e.g., the Declaration of Conformity) can reuse content you already entered in Annex IV. 1. **Use compliance test results** — When compliance evaluations exist in the project, their results can be referenced as performance evidence. ### Phase 3: Evaluate The **Evaluate** phase links to the [Compliance](https://docs.elluminate.de/guides/compliance/index.md) feature, where you run compliance test packages against your system. Completed evaluations can be used as evidence in the performance section of your documentation. ### Phase 4: Report Click **Generate Document** to render the technical documentation. A preview opens, starting with a cover page (project, creation date, regulation edition, risk classification, overall progress) followed by a per-section status table and the documented content. From the preview you can: - **Download** — Save the document as Markdown - **Download PDF** — Save the document as a formatted PDF - **Include quality assessment** — Optionally append an LLM-based quality review of each field to the document The generated document follows the language of the user interface — switch the UI to German for a German document, or to English for an English one. ## The Chat Assistant The chat panel is a compliance-aware AI assistant. It suggests context-dependent next steps and can: - **Classify your system** — It asks about your system and sets the project role, and helps you reason about the risk level - **Fill documentation fields** — Describe your system in your own words; the assistant maps the information to the right sections and fields and proposes the changes for your review - **Answer regulatory questions** — e.g., "What determines my risk level?" or "What would change my risk level?" - **Review your documentation** — e.g., "Check for contradictions" or "Check completeness" Conversations are saved per project — you can start new chats, search previous ones, and continue where you left off. ### Reviewing Proposed Changes When the assistant proposes a field change, it is not saved immediately. Instead, a **Proposed change** card appears in the chat below the assistant's answer, showing the affected section and fields as a before/after comparison — removed text appears red and struck through, added text green. Cleared fields show the old value struck through, followed by an *(empty)* marker. Click **Accept** to save the change or **Reject** to discard it, without leaving the chat; the buttons become available once the answer has finished streaming. A decided proposal shows an **Accepted** or **Rejected** badge, which persists across page reloads. Progress and completion update only after you accept. Outdated Proposals If the section was edited elsewhere after the proposal was made, the card shows an outdated hint. You can still accept the proposal — it then overwrites the current values with the proposed ones. Direct edits in the section editors are unaffected by this flow and still save immediately, as does the document import. ## Importing Documents If documentation for your system already exists (e.g., a system description, an architecture document, or an existing technical documentation), you can import it instead of retyping it: 1. Click **Import document** in the documentation panel header 1. Upload existing material about your AI system — system architecture, data-protection impact assessments, test or audit reports, prior technical documentation. Supported formats: **PDF, Text, Markdown (max. 25 MB)** 1. The assistant analyzes the document and extracts facts into documentation fields 1. Review the **Extracted Fields** preview, grouped by what the import changes (risk classification, protection needs, deployer obligations, documentation content) 1. Select which values to apply — fields that already contain content are deselected by default so imports don't overwrite your work The import only writes documentation content. It never sets your project role or risk level — those remain your explicit decisions. If imported content touches the risk classification section, the assistant prompts you to review whether your protection needs assessment is still accurate. Import Safety Checks The import warns you when a document appears to contain manipulated content (prompt injection) and when the system name in the imported document does not match your project. If you import before confirming your project role, the dialog asks for the role first and warns you afterwards, because the extraction is filtered by role and content relevant to other roles may not have been extracted. ## Quality Review Click **Quality review** in the header to check your documentation for consistency and completeness. The review produces an overall verdict: | Verdict | Meaning | | -------------------- | -------------------------------------------- | | **Consistent** | No significant issues found | | **Notes available** | Improvement suggestions exist | | **Significant gaps** | Required content is missing or contradictory | The review combines several checks: - **Mandatory gaps** — Deterministic checks for required content that is missing (also shown on the Overview dashboard) - **Cross-section findings** — Consistency checks between related sections, e.g., risk classification ↔ risk management system, role ↔ obligations, training data ↔ affected persons - **Field-level findings** — Individual fields rated **Sufficient / More details recommended / Insufficient** - **Required sections not started** — A list of required sections without any content For field-level findings, the review panel shows a concrete improvement suggestion for each flagged field. You can **Accept** or **Skip** each suggestion individually, or apply all open suggestions at once with **Accept all remaining** — a counter shows how many suggestions have been handled. Accepted values are saved immediately and marked as originating from the quality review. When information is genuinely missing, the panel shows an **Open question** instead — **Answer in chat** prefills the question into the chat so you can answer it directly. When you change documentation content after a review, the verdict is marked **"May be outdated"** — click **Re-run review** to refresh it. The generated document can include the field-level quality assessment as an appendix. ## The Regulation Browser Click **EU AI Act** in the header to open a searchable regulation browser with the articles, annexes, and recitals of the EU AI Act — useful for looking up the legal basis shown next to sections and fields. Regulation Edition The regulation corpus reflects Regulation (EU) 2024/1689 as originally adopted. Amendments proposed after adoption are not yet included. ## Notes - The Compliance Assistant supports you in preparing your technical documentation. It does not replace a legal review of your obligations under the EU AI Act. - All documentation content is stored per project and can be edited at any time. # Experiments *Learn to systematically test and compare your LLM applications* Experiments help you systematically compare different prompts, evaluate their effectiveness, and track performance over time. They are the foundation of data-driven LLM development, allowing you to test multiple variations and analyze which performs best for your specific use case. ## What is an Experiment? An experiment combines several key components to create a systematic evaluation: - **Collection** - A set of test inputs (template variables) representing different scenarios - **Prompt Template** - The foundation containing your prompt with `{{variables}}` - **LLM Config** - The model configuration (provider, parameters, etc.) - **Criterion Set** - Evaluation criteria defining what makes a good response When you run an experiment, elluminate generates responses for each input in your collection, then automatically evaluates each response against your criteria. Which of these components you configure depends on the **experiment type** you pick. A standard *Generate & Evaluate* run uses all four, while *Evaluate Imported Responses* skips the prompt template and model entirely and scores responses you already have. See [Choose an experiment type](#step-2-choose-an-experiment-type) for the full list. ## Creating Your First Experiment ### Step 1: Navigate to Experiments In your project, go to the **Experiments** page. You'll see your experiment runs list with options to create new experiments or view the timeline. ### Step 2: Choose an experiment type Click **"New Experiment"**. elluminate first asks **which kind of experiment** you want to create. The setup that follows is *type-aware* — it reveals only the steps relevant to the type you pick. | Experiment type | What it does | Setup steps | | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | | **Generate & Evaluate** | Run a prompt template through an LLM against your collection. Variables are filled in for each row, the model is called, and responses are evaluated against your criteria. | Collection → Prompt template → Model → Criteria | | **Evaluate Imported Responses** | Evaluate externally generated responses already stored in your collection. Your criteria run directly against them — no LLM inference. | Collection → Response column → Criteria | | **Persona** | Simulate multi-turn conversations between LLM-driven [personas](https://docs.elluminate.de/guides/multi_turn_personas/index.md) and your chatbot. Every persona in the group runs against every row of the question library, and each resulting dialogue is evaluated. | Persona group → Question library → Prompt template → Model → Criteria | | **Agentic** | Evaluate an agent's full trajectory across the tasks in your collection, including its tool calls, reasoning steps, and final output. | Collection → Prompt template → Model → Criteria | After picking a type it collapses to a summary row; use **"Change type"** to switch at any point. Type-specific collections *Persona* and *Agentic* experiments require a matching collection type (Persona or Agentic), while *Generate & Evaluate* and *Evaluate Imported Responses* use standard collections. The *Agentic* type is only available in non-production environments while the workflow is being finalized. ### Step 3: Configure the setup flow elluminate guides you through a **step accordion** — one step at a time, each unlocking the next. Completed steps collapse to a summary you can reopen to change a selection. For a *Generate & Evaluate* experiment the steps are: 1. **Choose a collection** - Your test inputs dataset. 1. **Choose a prompt template** - The versioned template with `{{variables}}`. Optional when the collection already contains full prompts (a Conversation or Raw Input column). 1. **Choose a model** - The LLM config used to generate responses. 1. **Choose evaluation criteria** - The criterion set responses are evaluated against. Below the accordion you set the **Experiment Name** and an optional **Description**. **Compatibility rules** The system will only offer mutually compatible Collections, Prompt Templates and Criterion Sets. If your desired selection does not appear, check for issues on your placeholders and column naming. **Advanced Settings** - **Rating Mode** - Detailed mode includes reasoning for each criterion and is recommended for better interpretability. Fast mode provides only ratings without explanations and may be slightly faster. - **Epochs** - Epochs allow you to run the same evaluation multiple times. This makes the statistics more reliable but can take some time. - **Rating Version** - Select the rating model version to use for evaluating responses. Defaults to the version set in your project settings. ### Step 4: Run the Experiment After clicking **"Create Experiment"**, elluminate will: 1. **Generate Responses** - Create LLM responses for each test input (skipped for *Evaluate Imported Responses*, which uses the responses already in your collection) 1. **Rate Responses** - Evaluate each response against your criteria 1. **Calculate Results** - Compute overall scores and criterion performance You can monitor progress in real-time from the experiment detail page. ## Evaluating Imported Responses When you already have model outputs — from a production system, another tool, or a previous run — you can evaluate them directly without regenerating anything. Pick the **Evaluate Imported Responses** type and follow its three-step flow: 1. **Choose a collection** - Only collections with at least one text column are shown. 1. **Choose a response column** - The text column that holds the outputs you want to evaluate. 1. **Choose evaluation criteria** - The criterion set applied to those responses. elluminate runs your criteria directly against the stored responses — no prompt template, model, or LLM inference is involved. This makes it ideal for benchmarking external systems or scoring responses collected elsewhere. ## Understanding Experiment Results Once your experiment completes, you can analyze results using two main views: ### Detailed Analysis Tab This tab provides aggregate insights and sample-by-sample inspection: - **Overall Performance** - Success rates, score distributions, token usage - **Criterion Breakdown** - Which criteria pass/fail most frequently - **Sample Navigator** - Browse individual responses with full rating details - **Performance Charts** - Visual analysis of score patterns and distributions ### Individual Responses Tab This tab offers a tabular view of all results: - **Response Table** - Sortable list of all prompt-response pairs - **Rating Details** - Pass/fail status for each criterion per response - **Export Options** - Download results as CSV with full data - **Filtering** - Focus on specific score ranges or criterion results For detailed analysis techniques, see the [Response Analysis Guide](https://docs.elluminate.de/guides/response_analysis/index.md). ## Filtering by Categorical Columns When your collection includes categorical columns, you can filter experiment responses to analyze performance across different input categories. **Setup:** Configure columns as **Category** type in your collection. See [Column Types](https://docs.elluminate.de/guides/collections/#configure-collection-columns) for details. **Usage:** - Click the filter icon in the experiment header - Select categorical columns and values to include - Multiple values within a column use OR logic (show responses matching any value) - Multiple columns use AND logic (show responses matching all conditions) - Metrics and charts update to reflect only filtered responses This enables targeted analysis - for example, filtering by `user_type` being Enterprise or SMB to compare performance across customer segments, or `region` being US or EU to analyze regional effectiveness. ## Comparing Experiments elluminate supports comparing experiments to identify improvements and track performance changes. ### Two-Experiment Comparison Compare two experiments side-by-side to understand differences: 1. Select two experiments from your list 1. Click **"Compare"** to access detailed comparison view 1. Analyze side-by-side responses, criterion performance deltas, and overall score changes ### Multi-Experiment Comparison For high-level comparison across multiple experiments: 1. Select 3+ experiments from your list 1. Use multi-comparison view for aggregate performance trends 1. Identify which experiments perform best across different criteria ## Monitoring Performance Over Time ### Timeline View The **Timeline** provides historical performance tracking: - **Performance Trends** - See how your experiments perform over time - **Filtering Options** - Filter by date range, prompt template, collection, or model - **Data Point Details** - Hover for experiment information, click to view details ### Scheduled Experiments It is possible to automate regular evaluations with scheduled experiments. **Creating Schedules:** 1. From Timeline view, click **"Schedule Run"** 1. Configure frequency (daily, weekly, monthly) 1. Set experiment parameters (template, collection, model) 1. Configure notifications and thresholds **Schedule Management:** To manage your scheduled experiments, navigate to the Timeline view and scroll to the bottom. There you can: - **View All Schedules** - Monitor active schedules and their settings using the filter function - **Edit Schedules** - Modify frequency, thresholds, or disable schedules - **Delete Schedules** - Remove scheduled experiments without affecting the previous runs ## Advanced Features ### Structured Outputs and Tool Calling Experiments work seamlessly with advanced LLM features: - **Structured Outputs** - Evaluate JSON responses with schema validation criteria - **Tool Calling** - Rate tool usage and parameter correctness in agentic applications - **Multi-Step Workflows** - Evaluate complex interaction patterns For details, see [Structured Outputs](https://docs.elluminate.de/guides/structured_outputs/index.md) and [Tool Calling](https://docs.elluminate.de/guides/tool_calling/index.md). ### Integration with Other Components Experiments leverage your entire elluminate setup: - **Collections** - Reuse test datasets across different experiments - **Prompt Templates** - Version control ensures experiment reproducibility - **Criterion Sets** - Apply consistent evaluation standards - **LLM Configs** - Test different models and parameters systematically ## Best Practices ### Experiment Design - **Clear Objectives** - Define what you're testing before running experiments - **Representative Data** - Ensure your collection covers real-world scenarios - **Appropriate Criteria** - Choose evaluation criteria that match your use case - **Controlled Variables** - Change one thing at a time for clear insights ### Performance Optimization - **Batch Testing** - Run multiple variations together for faster iteration - **Strategic Sampling** - Use smaller collections for rapid prototyping - **Cost Management** - Use Fast rating mode when detailed explanations aren't needed - **Historical Context** - Compare against previous experiments to track improvement ### Quality Assurance - **Manual Review** - Spot-check automated ratings for accuracy - **Edge Case Testing** - Include challenging scenarios in your collections - **Consistent Evaluation** - Use the same criterion sets for comparable results - **Documentation** - Record experiment goals and findings in descriptions ## SDK Integration For programmatic experiment creation and management, you can use the elluminate SDK: ``` from elluminate import Client from elluminate.schemas import RatingMode client = Client() # Uses ELLUMINATE_API_KEY env var ``` For complete SDK documentation, see the [API Reference](https://docs.elluminate.de/elluminate/resources/experiments/index.md). ## Troubleshooting ### Common Issues - **Generation Failures** - Check LLM config settings and rate limits - **Rating Errors** - Verify criterion set compatibility with response format - **Performance Issues** - Consider using Fast rating mode for large collections - **Missing Results** - Ensure experiment completed successfully before analysis ### Getting Help When experiments don't behave as expected: - **Check Logs** - Review experiment logs for specific error messages - **Validate Components** - Test prompt templates and collections independently - **Review Settings** - Confirm LLM config parameters are appropriate - **Contact Support** - Reach out with experiment IDs for detailed assistance # LLM Configs *Configure and manage the language models powering your evaluations* ## Overview elluminate offers some LLM models configured by default and allows you to connect any further language model to your projects — from popular providers like OpenAI to your own custom AI applications. This flexibility enables you to test prompts across different models, monitor your deployed AI systems, and optimize for cost and performance. ## Creating a Configuration To create a new LLM configuration, navigate to the **Models** page in your project and click **Create**. ### Basic Settings Enter a **name** and optional **description**, then select your **provider** (OpenAI, BCVP API, or Custom API). Depending on the provider, fill in the **model name**, **Base URL**, and **API Key**. Using a different Base URL and API Key lets you route your connection through a custom gateway or proxy. ### Generation Parameters Under **Advanced Settings**, you can fine-tune how the model generates responses: - **Temperature** (0–2): Controls randomness. Lower values produce more deterministic output. - **Top P** (0–1): Nucleus sampling threshold. Lower values restrict the token pool. - **Max Tokens**: Maximum response length. - **Max Connections**: Number of parallel requests elluminate can make to this model. - **Reasoning Effort**: For newer reasoning models (o-series), controls the depth of chain-of-thought reasoning. For standard configurations, you can use the provider's default values by clicking the checkbox. ### Testing Your Configuration Once configured, click **Test Configuration** to verify the connection works. Enter a test prompt and review the response directly in the dialog. ## Custom API Endpoints Custom API Endpoints let you connect any HTTP-based AI application to elluminate — whether it's your own model server, a fine-tuned deployment, a RAG pipeline, an agentic system, or any service that accepts prompts and returns text. Once configured, your custom endpoint can be used in any experiment, just like a built-in provider. ### Setting Up a Custom Endpoint Select **Custom API** as the inference type, then fill in the **Base URL** and **API Key** for your endpoint. The Base URL must include the full path to your endpoint (e.g., `https://your-api.example.com/v1/chat`) — elluminate sends requests directly to this URL without appending any path. The main configuration happens in the **API Configuration Template** — a JSON editor where you define how elluminate communicates with your API. The template has three required sections: ### Headers HTTP headers sent with every request. Use [placeholders](#available-placeholders) for dynamic values: ``` { "Authorization": "Bearer {{api_key}}", "Content-Type": "application/json" } ``` ### Body The JSON request body. Supports [placeholders](#available-placeholders) and nested structures. Use `{{prompt}}` for single-turn or `{{messages}}` for multi-turn conversations: ``` { "prompt": "{{prompt}}", "max_tokens": 1000, "temperature": 0.7 } ``` ### Response Mapping Tells elluminate where to find the content in your API's JSON response: | Key | Required | Description | | -------------- | -------- | ---------------------------------------------------------------------------------------------------------------------- | | `content_path` | Yes | Dot-notation path to the response content. Supports list indices (e.g., `data.response`, `choices.0.message.content`). | | `error_path` | No | Dot-notation path to error messages (e.g., `error.message`) | ``` { "content_path": "data.response", "error_path": "error.message" } ``` ### Available Placeholders Use `{{placeholder_name}}` syntax in headers and body. These are replaced with actual values before each request: | Placeholder | Description | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `{{api_key}}` | The API key configured for this LLM config | | `{{prompt}}` | The first user message content (for single-turn use cases) | | `{{last_message}}` | The latest user message content (for stateful bots that only expect the current turn) | | `{{messages}}` | Full conversation message array including system, user, and assistant messages (for multi-turn) | | `{{model}}` | The model name from the configuration | | `{{base_url}}` | The configured base URL | | `{{timestamp}}` | Current Unix timestamp | | `{{uuid}}` | A unique request identifier (UUID v4) | | `{{session_id}}` | A session identifier in the format `elluminate-`. For [stateful configurations](#stateful-apis), it stays stable across all turns of a persona conversation | | `{{var_}}` | Template variable values from your collection, prefixed with `var_` (e.g., `{{var_user_id}}` for a variable named `user_id`) | Type preservation When a placeholder is the **entire value** of a field (e.g., `"{{messages}}"`), the original type is preserved — lists stay lists, dicts stay dicts. When embedded in a string (e.g., `"Bearer {{api_key}}"`), standard string interpolation is used. ### Multi-Turn Conversations For APIs that support multi-turn conversations, use `{{messages}}` to pass the full conversation array: ``` { "headers": { "X-API-Key": "{{api_key}}", "Content-Type": "application/json" }, "body": { "conversation": "{{messages}}", "model": "{{model}}" }, "response_mapping": { "content_path": "response.text" } } ``` The `{{messages}}` array contains messages in OpenAI format with `role` and `content` fields. ### Stateful APIs Some external bots (e.g. Cognigy) keep the conversation state on their side, keyed by a session identifier. For these, set the optional top-level key `"stateful": true` in the configuration (as a sibling of `headers`, `body`, and `response_mapping`). In persona multi-turn experiments, a stateful configuration changes two things: - `{{session_id}}` is generated once per conversation (in the format `elluminate-eval-`) and stays stable across all turns, so the external bot keeps its session state. - Only the latest user message is sent each turn (use `{{last_message}}` in the body) instead of the full history — the bot replays prior turns from its own session state. As a consequence, any bot system prompt configured on the experiment is **not** sent to a stateful bot. ``` { "headers": { "X-API-Key": "{{api_key}}", "Content-Type": "application/json" }, "body": { "sessionId": "{{session_id}}", "text": "{{last_message}}" }, "response_mapping": { "content_path": "text" }, "stateful": true } ``` Outside of persona multi-turn experiments, a fresh `{{session_id}}` is generated per request as usual. ### Custom Response Parser For APIs with non-standard response formats, you can add a **Custom Response Parser** — a Python code snippet that transforms the raw API response into the format elluminate expects. The `content_path` extraction always yields a string (non-string values are JSON-serialized). To get list-of-messages behavior (see [Response Handling](#response-handling) steps 5–6), your parser must return a list. The parser receives `raw_response` (a string) and has access to `json` and `re` (pre-imported). Your code must set `parsed_response` with the final result (string or list). The parser is limited to 5,000 characters and a 2-second execution timeout. ### Example: Connecting an Agentic System Suppose you have a RAG agent and want to evaluate not just the final answer, but the full execution trace — tool calls, retrieval steps, reasoning. Here's how to set it up: **API Configuration Template:** ``` { "headers": { "Authorization": "Bearer {{api_key}}", "Content-Type": "application/json" }, "body": { "messages": "{{messages}}", "config": { "temperature": 0.2, "return_trace": true, "reasoning_effort": "high" } }, "response_mapping": { "content_path": "trace" } } ``` **Custom Response Parser:** ``` data = json.loads(raw_response) parsed_response = data['messages'] ``` How this works: - **`body.messages`** passes the full conversation to the agent via `{{messages}}`. - **`body.config`** sends additional options your endpoint needs. - **`response_mapping.content_path`** points to the trace object in the response JSON. - **Custom Response Parser** extracts the message sequence from the serialized trace so elluminate displays it as a conversation. ### Response Handling elluminate processes custom API responses as follows: 1. The API response is parsed as JSON. If parsing fails, the raw text is wrapped as `{"text": ""}`. 1. The `content_path` extracts the response content from the JSON. 1. If a `custom_response_parser` is configured, it is applied to the extracted content. 1. If the `error_path` is configured and contains a value, it is logged as a warning. 1. If the final content is a **list**, it is treated as a list of message objects (each with `role` and `content` fields). 1. If the final content is a **string**, it is wrapped as an assistant message. ### Limitations - Only `POST` requests are supported — the endpoint must return a 2xx status code - Streaming is not supported - Token usage metrics (input/output tokens) are not available for custom endpoints - Structured outputs and tool calling are not supported - The connect and read timeout defaults to 60 seconds (configurable via the `timeout` field on the LLM config) ## Monitoring and Analytics Track the usage of a model configuration — experiments run and performance metrics — in the configuration details view. ## SDK Integration All configurations created in the UI can also be managed via the SDK. Use `get_or_create_llm_config` for idempotent setup: ``` performance_config, created = client.get_or_create_llm_config( name="Fast Response Model", defaults={ "llm_model_name": "gpt-4o-mini", "api_key": "key", "inference_type": InferenceType.OPENAI, # Performance settings "max_tokens": 500, # Limit response length "temperature": 0.3, # More deterministic "top_p": 0.9, # Nucleus sampling "max_connections": 20, # Parallel requests "timeout": 10, # Fast timeout in seconds "max_retries": 2, # Limited retries "description": "Optimized for quick responses", }, ) ``` Custom API endpoints follow the same structure — pass `custom_api_config` and optionally `custom_response_parser`: ``` custom_config, created = client.get_or_create_llm_config( name="My Custom Model v2", defaults={ "llm_model_name": "custom-model-v2", "api_key": "your-api-key", "llm_base_url": "https://api.mycompany.com/v1", "inference_type": InferenceType.CUSTOM_API, "custom_api_config": { "headers": { "Authorization": "Bearer {{api_key}}", "Content-Type": "application/json", "X-Model-Version": "{{model}}", }, "body": {"prompt": "{{prompt}}", "max_tokens": 1000, "temperature": 0.7, "stream": False}, "response_mapping": {"content_path": "data.response", "error_path": "error.message"}, }, "description": "Our production recommendation model", }, ) ``` ## Best Practices ### Configuration Strategy 1. **Development**: Use cheaper, faster models 1. **Staging**: Test with production models 1. **Production**: Optimize parameters for your use case 1. **Monitoring**: Set up your own endpoints for visibility ### Custom API Best Practices 1. **Standardize Response Format**: Use consistent JSON structure across endpoints 1. **Include Metadata**: Return model version, latency and confidence 1. **Error Handling**: Provide clear error messages via the `error_path` 1. **Rate Limiting**: Implement appropriate throttling on your endpoint 1. **Monitoring**: Log all requests for analysis ## Troubleshooting ### Common Issues **Connection Failed** - Verify API key is valid - Check base URL format (must start with `http://` or `https://`) - Ensure network connectivity - Confirm firewall rules **Slow Responses** - Reduce max_tokens - Lower temperature - Check API rate limits - Consider model size **Inconsistent Results** - Lower temperature for determinism - Set seed parameter if available - Use consistent system prompts - Verify model version # MCP Integration *Connect Claude and other MCP clients to elluminate for interactive prompt evaluation, experiment tracking, and AI-assisted optimization workflows.* The elluminate MCP (Model Context Protocol) server exposes platform capabilities through a standardized interface, enabling LLM assistants to help you build, run, and analyze prompt evaluation experiments. ## What is MCP? MCP (Model Context Protocol) is an open standard that allows AI assistants to interact with external tools and data sources. The elluminate MCP server translates MCP requests into elluminate SDK calls, letting MCP clients: - Create and manage prompt templates, collections, and criteria - Run experiments and generate responses - Analyze evaluation results and identify failure patterns - Iterate on prompts based on detailed feedback ## Benefits **Interactive Workflow Development** - Build evaluation pipelines conversationally with AI assistants - Get instant feedback on experiment design and test coverage - Debug failed test cases with AI-assisted analysis **Streamlined Iteration** - Quickly clone and modify resources for A/B testing - Analyze results and refine prompts in a single session - Track experiment history and performance trends **Knowledge Integration** - Leverage AI assistants' understanding of your domain to generate test cases - Get suggestions for evaluation criteria and edge cases - Combine elluminate's systematic evaluation with AI reasoning capabilities ## Capabilities Overview The MCP server provides many tools organized by functional area: **Project Management** - List organizations and projects - Create and clone projects - Switch between project contexts **Prompt Templates** - Create versioned prompt templates with `{{placeholders}}` - List and retrieve template versions - Clone templates for A/B testing **Test Collections** - Manage test case sets with multiple column types (TEXT, CONVERSATION, RAW_INPUT, CATEGORY) - Create collections from scratch or clone existing ones - Review test inputs and add new cases **Evaluation Criteria** - Define binary yes/no evaluation questions - Create criterion sets for systematic quality measurement - Support ground-truth validation with placeholders in criteria **Experiments** - Run batch evaluations combining templates, collections, and LLM configs - Track experiment progress and completion status - Analyze success rates and failure patterns - Compare results across different configurations - Get detailed prompt/response/rating information **Response Annotation** - Add notes and categories to responses for iteration tracking - Review annotated responses across experiments **LLM Configuration** - Manage multi-provider LLM settings - Configure model parameters, temperature, and API credentials - List available builtin and custom configs **Resources & Guides** - Access platform manual (comprehensive MCP usage guide) - Create end-to-end demos for specific use cases For complete tool documentation, explore tool capabilities directly in your MCP client. ## Client Setup ### Claude Code CLI ``` claude mcp add --transport http elluminate-mcp https://app.elluminate.de/mcp ``` **Authentication Flow** 1. After adding the MCP server, start Claude Code 1. Navigate to `/mcp` when prompted 1. Click the authorization link to open your browser 1. Sign in to elluminate and authorize access 1. Return to Claude Code - you're now connected **Verification** Check available MCP servers: ``` /mcp ``` You should see `elluminate-mcp` listed. Try listing your projects: ``` Can you list my elluminate projects? ``` ### Claude Desktop Claude Desktop allows you to add remote MCP servers through the Connectors interface. #### Setup Steps 1. **Open Settings**: Launch the Claude Desktop app and click the Settings icon or navigate to the menu 1. **Go to Connectors**: In the sidebar, find and click on **Connectors** 1. **Add Connector**: Scroll down and click the **Add custom connector** button 1. **Enter Details**: 1. **Name**: `elluminate` 1. **URL**: `https://app.elluminate.de/mcp` 1. **Advanced settings**: Add your API key from **Settings** or configure OAuth credentials 1. **Add & Test**: Click **Add**, then start a new chat to use the elluminate integration #### Verification Test the connection by asking: "Can you list my elluminate projects?" ### ChatGPT App ChatGPT app supports MCP servers through the Connectors interface. This requires a paid subscription (Plus, Team, or Enterprise). #### Enable Developer Mode 1. Open ChatGPT and go to your **Settings** (click your avatar) 1. Navigate to **Apps & Connectors** > **Advanced settings** (or **Developer Mode**) 1. Toggle **Developer Mode** on #### Add the Connector 1. Return to the main **Settings** menu and select **Connectors** (or **Apps & Connectors**) 1. Click the **Create** button to add a new connector 1. Configure the connector: 1. **Name**: `elluminate` 1. **Description**: "elluminate MCP server for prompt evaluation and experiment tracking" 1. **MCP Server URL**: `https://app.elluminate.de/mcp` 1. **Authentication**: Choose OAuth and complete the authorization flow 1. **Trust**: Check the box to confirm you trust the provider 1. Click **Create** and complete the OAuth authorization #### Verification Once connected, use the elluminate server in chats by mentioning it: ``` @elluminate Can you list my projects? ``` ### Codex Codex (OpenAI's development tool) supports MCP servers through configuration in `~/.codex/config.toml`. #### CLI Configuration ``` codex mcp add elluminate-mcp --url https://app.elluminate.de/mcp ``` You get forwarded to your browser for OAuth authorization and after granting permission the MCP server is connected to codex. #### Manual Configuration Alternatively, edit `~/.codex/config.toml` directly: ``` [mcp_servers.elluminate-mcp] url = "https://app.elluminate.de/mcp" ``` You should get forwarded to OAuth. If not authenticate via CLI: ``` codex mcp login elluminate-mcp ``` This will open a browser for OAuth authorization. #### Verification 1. Restart Codex or reload the configuration 1. elluminate MCP tools should be available in the tool palette 1. Test: "Can you list my elluminate projects?" ### Gemini CLI Gemini CLI supports MCP servers through configuration in `~/.gemini/settings.json`. #### Configuration Edit `~/.gemini/settings.json` (where `~` is your home directory): ``` { "mcpServers": { "elluminate-mcp": { "url": "https://app.elluminate.de/mcp" } } } ``` #### Authentication After editing the configuration file, start Gemini CLI and authenticate: ``` gemini ``` Then run the following command to trigger OAuth: ``` /mcp auth elluminate-mcp ``` This will open a browser for OAuth authorization. #### Verification 1. Restart Gemini CLI or reload the configuration 1. elluminate MCP tools should be available 1. Test: "Can you list my elluminate projects?" ## Authentication The MCP server supports two authentication methods: OAuth 2.0 for interactive use and API keys for programmatic access. ### OAuth Authentication (Recommended) **How It Works** - Browser-based authorization flow - Tokens validated via `userinfo` endpoint - Automatic token refresh **When to Use** - Interactive sessions with Claude or other AI assistants - Multi-user environments - User-scoped access control **Authorization Flow** 1. MCP client initiates OAuth using the server's auth endpoints 1. OAuth proxy redirects to elluminate authorization page 1. User authenticates (or uses existing session) 1. elluminate redirects back with authorization code 1. Proxy exchanges code for access token 1. Client receives bearer token for subsequent requests 1. Server validates tokens via `userinfo` endpoint ### API Key Authentication **How It Works** - Direct SDK authentication using API keys - No browser interaction required - Keys scoped to specific projects **When to Use** - Programmatic access via custom scripts - Automated testing - CI/CD pipelines **Setup** 1. Generate an API key by navigating to **Settings** 1. Set environment variable: ``` export ELLUMINATE_API_KEY="el_..." ``` **Security Considerations** - Never commit API keys to version control - Use secrets management (1Password, AWS Secrets Manager, etc.) - Rotate keys regularly - Scope keys to specific projects when possible ## Environment Variables Reference Configuration options for MCP clients: | Variable | Purpose | Required | Example | | --------------------- | -------------------------- | --------------------------- | --------------------------- | | `ELLUMINATE_BASE_URL` | elluminate API base URL | Yes | `https://app.elluminate.de` | | `ELLUMINATE_API_KEY` | API key for authentication | Yes (if using API key auth) | `el_...` | ## Common Use Cases ### Creating and Running Experiments **Scenario**: Evaluate a customer support chatbot prompt across different scenarios. ``` I want to evaluate a customer support prompt. Can you help me: 1. Create a new prompt template for handling refund requests 2. Build a test collection with 10 diverse scenarios 3. Set up evaluation criteria for politeness, accuracy, and helpfulness 4. Run an experiment and show me the results ``` The AI assistant will guide you through each step, creating resources and running the experiment. After completion, ask for detailed analysis: ``` Show me the failed test cases and explain why they failed ``` ### Iterating on Prompts **Scenario**: Improve prompt based on experiment results. ``` Based on the experiment results, can you: 1. Clone the prompt template 2. Modify it to address the politeness failures 3. Run a new experiment with the same test cases 4. Compare the results ``` ### A/B Testing Configurations **Scenario**: Compare GPT-4 vs Claude performance. ``` I want to compare GPT-4 and Claude on my prompt. Can you: 1. List available LLM configs 2. Run two experiments in parallel with different models 3. Show me which model performed better on each criterion ``` ### Analyzing Response Patterns **Scenario**: Deep-dive into specific failures. ``` Can you: 1. Get the failed examples from experiment X 2. Show me the full prompts and responses for the failed cases 3. Annotate the most problematic responses with "needs-revision" 4. Suggest improvements based on the failure patterns ``` ## Troubleshooting ### Authentication Failed **Symptoms**: "Authentication failed" or "Unauthorized" errors **Solutions** - **OAuth**: Re-authenticate with browser flow (check `/mcp` in Claude Code) - **API Key**: Verify `ELLUMINATE_API_KEY` is set correctly - **Token Expiration**: Re-authorize if tokens expired (OAuth only) ### Project Not Found **Symptoms**: "Project not found" when creating resources **Solutions** - Call `get_project()` to verify current project - Use `list_projects()` to see available projects - Explicitly switch project context after creating/cloning ### Connection Refused **Symptoms**: Cannot connect to MCP server **Solutions** - Verify network connectivity to `https://app.elluminate.de` - Check firewall/proxy settings - Ensure MCP endpoint is accessible: `https://app.elluminate.de/mcp` # Personas *Evaluate your chatbot end-to-end by letting reusable, simulated users pursue a goal across real, multi-turn conversations.* A **Persona** is a reusable definition of a simulated user. In a persona experiment, an LLM simulates each persona while pursuing a goal, the bot or LLM endpoint under test responds turn by turn, and the resulting transcript is rated as one unit by your criterion set. Personas live in a project-wide **library**, are organized into **persona groups**, and are run against a **collection** so that *every persona is evaluated against every scenario* in a single experiment. ## What It Is Define personas once in the library and reuse them across experiments: - The **Persona Library** holds your reusable, versioned personas. - A **Persona Group** is a named pool of personas you select for an experiment. - A **persona experiment** runs every persona in the group against every row of a question library — this fan-out produces one simulated conversation per persona and row. xs ## Quick Start 1. Open **Personas** (under **Library** in the sidebar) and create a new persona with the **New** button. 1. Create a **persona group** and add the personas you want to evaluate together. 1. Create a Persona collection that follows a template and already contains all the required columns. 1. Under **Criteria Library**, create a new criterion set. 1. **Optional:** Go to **Prompt Templates** and create a **system-only** template — the system prompt for your bot under test. 1. Go to **Experiments → New Experiment** and pick the **Persona** type. 1. Choose your persona group, the collection, optionally the system-message-only template, an LLM config for the bot, and a criterion set. 1. Run the experiment. Every persona is run against every row, and each transcript is rated by the criterion set. ## Key Concepts | Concept | Description | | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Persona** | A reusable, versioned simulated user. Has a **name**, a **persona description** (the personality and behavior), and optional **domain knowledge** (background knowledge the simulated user has). The description and domain knowledge may contain `{{column}}` placeholders filled from the collection row. | | **Persona Library** | The project-wide collection of personas. Personas can be shared org-wide (read-only in other projects). | | **Persona Group** | A named, mutable pool of personas. Experiments reference a group; its members are pinned into the experiment at launch. | | **Question Library** | A Persona-type collection holding one scenario per row. Must contain a **Persona goal** column that gives the simulator the goal to pursue. Other columns can be referenced as `{{placeholders}}` in persona descriptions. | | **Simulator** | The LLM that plays the user, driven by the persona. The simulator is a deployment-level setting and is not user-configurable. | | **Bot / LLM endpoint under test** | The LLM that plays your assistant, defined by the experiment's LLM config plus a system-only prompt template. | | **Termination** | The simulator can call an `end_conversation` tool with one of three reasons — `goal_met`, `goal_failed`, or `stuck`. If it never calls the tool, the loop stops at a hard cap of `MAX_TURNS = 16`. | ## The Persona Library Open **Personas** under **Library** in the sidebar. The page is a two-pane workbench: the **Persona Library** on the left and **Persona Groups** on the right. ### Creating and editing personas 1. Click **New** at the top of the library to open the inline editor. 1. Fill in: - **Name** — a short label (e.g. *Impatient first-time user*). - **Persona description** — the personality and behavior the simulator should adopt. - **Domain knowledge** *(optional)* — knowledge or context the simulated user has, e.g. a collection of insurance policies. 1. Click **Save**. Click any persona row later to edit it in place. ### Sharing personas across projects Personas live in a single project, but you can make a standard persona available everywhere in your organization instead of duplicating it per project. - Flip the **Share with organization** toggle on a persona on the Personas page. - Once shared, it becomes **visible and usable read-only in every other project of the same organization** — no need to recreate standard personas per project. - Only the **owning project** can edit, un-share, or delete a shared persona. In other projects it shows a **Shared** badge and its fields are read-only. - Sharing is **lineage-wide**: it applies to all versions of that persona, and can be turned off again from the owning project. When a project needs to diverge from the shared original, **clone/duplicate** the persona into that project. This creates an independent, editable copy that starts un-shared, so you can adapt it without affecting the shared version. ## Persona Groups A persona group is a named pool of personas that an experiment runs together. 1. In the right pane, use the group switcher to create a **New group** — give it a name and an optional description. 1. Add personas to the group using the **+ / ✓** toggle on each library row. Members appear as cards in the group roster. 1. Remove a member with the **✗** on its card. Groups are mutable and not versioned. When an experiment is created, it captures an **immutable snapshot** of the exact persona versions in the group at that moment, so later edits to the group or its personas never change a finished experiment. ## The Question Library A persona experiment runs against a **question library** — a Persona-type collection where each row is one scenario. - The collection must contain a **Persona goal** column. Its value is the goal the simulator pursues for that row. - Any other columns can be referenced as `{{placeholders}}` in a persona's description or domain knowledge. - A Persona collection cannot mix with **Raw Input** or **Conversation** columns — it is its own collection type. ## The Bot's Prompt Template (optional) The bot under test is configured via a **system-only** prompt template: - Exactly one `system` message. - No `user` or `assistant` messages. - No user-side placeholders — the simulator drives the user turns. When you set up a persona experiment, the template picker is filtered to only show system-only templates, because only the system message is passed to the bot's LLM endpoint. ## Running a Persona Experiment 1. Go to **Experiments → New Experiment** and pick the **Persona** type. 1. In **Choose a persona group**, select the group to evaluate. 1. Select the **question library**, the system-only **prompt template**, the **LLM config** for the bot under test, and the **criterion set** that will rate the transcripts. 1. Review the conversation count and start the experiment. The experiment fans out across both dimensions: it runs **one conversation per persona and per question-library row**. With *P* personas and *R* rows, the experiment generates *P × R* conversations, which the form shows as a live count before you start. ## How a Run Works For each (persona, question-library row) pair: 1. Render the persona's description and domain knowledge against the row's `{{column}}` values, and read the goal from the **Persona goal** column. 1. The simulator generates the next user message. It sees the full conversation history with roles flipped (your bot's replies appear as `user` messages to the simulator, and vice versa). 1. The bot under test replies, using the optional system-only prompt template plus the experiment's LLM config. 1. Steps 2 and 3 loop until the simulator calls `end_conversation` or `MAX_TURNS` is reached. 1. The final transcript is stored as the response and rated by the criterion set. Stateless vs. stateful providers Most providers (OpenAI, Custom API, Mock) are stateless: the full conversation history is replayed on every bot turn. **Botario** keeps server-side session state keyed by `sessionId`, so only the current user turn is sent on each call. Botario also silently drops the bot's system prompt because state lives on the Botario side — the system prompt is still kept in the stored transcript, but it is not sent to the bot. ## Reading the Results - The full dialogue between simulator and bot is stored on each response and visible in the response viewer. - Criterion ratings apply to the **entire transcript**, not to a single turn. - The termination reason (`goal_met`, `goal_failed`, `stuck`, or `MAX_TURNS`) is surfaced on the response. - Because each response is tied to the persona that produced it, you can **filter results by persona** to compare how different personas fared on the same scenarios. ## SDK Reference Personas and persona groups are managed in the UI. The SDK can **reference an existing persona group** when launching a multi-turn experiment by passing its `persona_group_id`, which is required for `MULTI_TURN` experiments: ``` from elluminate import Client client = Client() experiment, _ = client.experiments.get_or_create( name="Support quality — multi-turn", collection=question_library, # a Persona-type collection prompt_template=system_only_template, persona_group_id=group_id, # required for MULTI_TURN ) ``` Managing personas via the SDK The SDK does not create or edit personas or persona groups — author them in the UI first, then reference the group's id. `persona_group_id` is available on `experiments.create`, `experiments.get_or_create`, and their async equivalents. ## Related - [Conversations](https://docs.elluminate.de/guides/conversations/index.md) — static, pre-recorded conversation histories - [Agentic Evaluations](https://docs.elluminate.de/guides/agentic_evaluations/index.md) — tool-using agent evaluations - [Criterion Sets](https://docs.elluminate.de/guides/criterion_set/index.md) — rule collections used to rate transcripts - [Experiments](https://docs.elluminate.de/guides/experiments/index.md) — running evaluations end-to-end # Organizations *Organizations provide a centralized workspace for teams to collaborate across multiple projects, manage billing, and control access at the organizational level.* By managing all your projects and team members from a single, centralized workspace, you gain complete visibility and control over your entire operation. ## Working with Organizations Each organization maintains its own set of projects and team members, providing a clean, centralized workspace for collaboration. Your account can belong to multiple organizations, and you can seamlessly switch between them using the Organization Selector in the top left corner of the platform. ### Organization Settings Organization configuration is managed through the Organization Settings page, accessible exclusively to Owners and Admins. Here you can customize your organization's name and description, as well as view a comprehensive overview of all projects and their members. ### User Management Owners and Admins can add or remove team members and adjust their permission levels directly from the Organization Settings page: ##### Permission Levels | Permission | Owner | Admin | Member | Guest | | --------------------------------- | ----------------- | ----------------- | ----------------- | ----------------- | | Manage organization settings | ✓ | ✓ | ✗ | ✗ | | Add/remove users | ✓ | ✓ | ✗ | ✗ | | View all projects & members | ✓ | ✓ | ✗ | ✗ | | Access public project contents | ✓ | ✓ | ✓ | ✗ | | List all projects in organization | ✓ | ✓ | ✗ | ✗ | | Access private project contents\* | If project member | If project member | If project member | If project member | | Delete organization | ✓ | ✗ | ✗ | ✗ | Note \*All roles can view the contents of a private project only if they are explicitly added as a member to that project. For more information on project visibility settings, see [Project Visibility](https://docs.elluminate.de/guides/projects/#project-visibility). ### Projects Overview The Organization Settings page also provides a comprehensive view of all projects within your organization. Owners and Admins can see the complete project list along with each project's members, regardless of whether projects are public or private. This centralized overview makes it easy to: - Monitor which projects exist in your organization - Track project membership across your team Note Members can only see public projects and private projects where they are explicitly added. Guests can only access projects where they have been directly invited. ### Deleting Organizations Only organization **Owners** can delete an organization. This action is **irreversible** and should be approached with extreme caution. #### What Gets Deleted: - All projects within the organization - All prompt templates, collections, and experiments - All evaluation results and historical data - All team member associations Irreversible Action Once deleted, this data **cannot be recovered**. We strongly recommend exporting critical data before proceeding with deletion. # Projects *Projects serve as containers for all your prompt templates, collections, criterion sets, experiments, and allow you to manage your team members.* By organizing your work in Projects, you can control and share exactly what you want with every team member. ## Working with Projects Each project maintains its own set of resources and configurations, that means, separate: - **Prompt Templates** - Version-controlled prompt definitions - **Collections** - Test data and variable sets - **Experiments** - Evaluation results and metrics - **Criterion Sets** - Quality evaluation criteria - **LLM Configurations** - Model settings and API keys Resources created within a project are only accessible when that project is active and can be shared across team members. When you open a project, it will show you an overview of the experiments performance timeline and response stats as well as links to your prompt templates, collections and experiments. ### Creating Projects When you create a new project, it becomes your active workspace where all new resources are automatically associated: ### Project Settings Access the project configuration options through the settings panel to customize how your project operates. Project name and description can be changed here. ### Project Visibility Projects can be either private or public within your organization. New projects are private by default. - **Private projects**: Only project members can view or access the project. - **Public projects**: Everyone in this organization, except for guest members, can view and access the project without an explicit invite. Guest organization members must still be explicitly added to the project to gain access. ### Default Rating Version Control the rating version used in your experiments for the whole project. You can find and change your version in the Project Settings ### User Management Add team members to projects and manage their permissions from Project Settings: ##### Permission Levels | Permission | Admin | Editor | Viewer | | --------------------------- | ----- | ------ | ------ | | View project resources | ✓ | ✓ | ✓ | | Create and modify resources | ✓ | ✓ | ✗ | | Run experiments | ✓ | ✓ | ✗ | | Manage project settings | ✓ | ✗ | ✗ | | Add/remove users | ✓ | ✗ | ✗ | | Delete project | ✓ | ✗ | ✗ | ## Advanced features ### **SDK Integration** The elluminate SDK provides access to project information and management through the client. Currently, the SDK automatically loads the project associated with your API key: ``` from elluminate import Client # Initialize client - automatically loads the project associated with your API key client = Client() # Access the current project current_project = client.project print(f"Current project: {current_project.name}") print(f"Project ID: {current_project.id}") print(f"Description: {current_project.description}") # Get project details print(f"Created: {current_project.created_at}") print(f"Updated: {current_project.updated_at}") ``` **Note**: The current SDK automatically loads the project associated with your API key. For advanced project management features like creating multiple projects, cloning, or user management, please use the web interface or direct API calls. For direct API access to project management endpoints, you can use: ``` import httpx # List all projects (requires appropriate permissions) response = httpx.get( "https://app.elluminate.de/api/v0/projects", headers={"X-API-Key": "your-api-key"} ) projects = response.json() # Get specific project details project_id = 123 response = httpx.get( f"https://app.elluminate.de/api/v0/projects/{project_id}", headers={"X-API-Key": "your-api-key"} ) project_details = response.json() ``` ### Cloning Projects You can Clone existing projects from the Project Settings to create variations while reusing resources to save effort: ### Deleting Projects Only project **Admins** can delete a project. This action is **irreversible** and should be approached with extreme caution. **What Gets Deleted:** - All prompt templates in the project - All collections and test data - All experiments and evaluation results - All criterion sets and configurations - All LLM configurations ⚠️ Data Cannot Be Recovered Once deleted, this data **cannot be recovered**. We strongly recommend exporting critical data before proceeding with deletion. ## Additional Resources ### Best Practices **Project Structure** - To get started, use our curated Demo project as a playground - Create separate projects for different use cases or domains - Use descriptive names that indicate the project's purpose - Maintain a consistent naming convention across your organization ### Getting Help - Check the [Key Concepts](https://docs.elluminate.de/guides/the_basics/index.md) for fundamental understanding - Review [Experiments](https://docs.elluminate.de/guides/experiments/index.md) for evaluation workflows - Consult [Collections](https://docs.elluminate.de/guides/collections/index.md) for test case management - Contact [support](mailto:help@elluminate.de) for technical issues or platform limitations # Prompt Templates *Master the foundation of systematic AI evaluation with versioned, reusable templates that enable comprehensive testing across different inputs and scenarios* Prompt Templates are the foundation of elluminate's evaluation system - versioned, reusable templates with variable placeholders that enable systematic testing of AI systems across different inputs and scenarios. ## What Are Prompt Templates? A **Prompt Template** is a reusable template containing variables (`{{placeholders}}`) that can be replaced with specific values to create concrete prompts. The values will be pulled from the Collection (TemplateVariable) to generate the prompt for the LLM. ## Template Structure ### Template Details The first part of the Prompt Template are the **Template Details**: The name of your Prompt Template will help you select it later for the experiments. The simplest version of a Prompt Template contains merely an User Message with a {{placeholder}} in double brackets. A system message will allow you to include instructions for the model to behave in a certain way. You may also use {{placehoders}} in the system message. For details on Structured Output and Tools, see the [Advanced Features](#advanced-features) section below. ### Response Generator In the **Response Generator** you can test the output of the chosen model to see if your Prompt Template would work: ### Criteria Finally, you may link some **criteria** to your template. You can link an existing Criterion Set or manually add each criterion. Criteria need a label and a criterion description: The button **Generate Criteria** automatically creates several criteria that match your existing Prompt Template: Linking the Criterion Set to your Prompt Template simply sets this set as default. It is still possible to select a different Criterion Set for the Prompt Template at the Experiment Creation time. ### Version Management By editing and saving a Prompt Template, you will create a new version that will help you track evolution and changes in your Evaluations. Be aware that Criterion Sets may be linked or unlinked without modifying the version of the prompt template. ## Advanced Features ### Structured Outputs With Structured Outputs you can ask the prompt to generate a response in a json format. It is possible to define the desired format directly or to let the system generate it for you: Structured outputs are specially important for evaluating agentic applications. ### Tool Calling You can access tools for testing the output of your agentic applications. Refer to our examples in the SDK Guide for more details. The tools can be defined in the Tool Calling area. You may define your own tools or use one of the examples provided. Tool Choice can also be defined. ## SDK approach ### Basic Template Creation ``` from elluminate import Client client = Client() template, created = client.get_or_create_prompt_template( name="Product Review Template", messages="Please review this product: {{product_name}}. {{product_description}}", ) ``` ### With a system message ``` complex_template = client.create_prompt_template( name="Product Review Template", messages=[ {"role": "system", "content": "You are a product reviewer for the company {{company_name}}."}, { "role": "user", "content": "Please review this product: {{product_name}}. {{product_description}}", }, ], ) ``` ### Supported Roles - **system** - Sets behavior and context for the AI - **user** - Represents user input and requests - **assistant** - Represents AI responses (for multi-turn conversations) ## Best Practices When designing prompt templates, use clear and descriptive placeholder names that make the template's intent obvious (for example, `{{customer_inquiry}}` instead of `{{input}}`). Consistency in naming and format helps maintain clarity, especially when templates evolve or are reused. Structure your prompts thoughtfully: begin with system messages to set the context, and write user messages that are specific and actionable. When planning variables, aim for realistic and diverse values that reflect the range of scenarios you want to test. Consider both typical and edge cases to ensure your template is robust and effective. ## Troubleshooting ### Common Issues **Missing Variables** - Ensure Collections contain all template placeholders, also the ones in the criteria. **Version Conflicts** - Use consistent template versions across related experiments **Placeholder Syntax** - Verify correct `{{variable_name}}` format ### Validation **Template Testing** - Generate sample prompts before running full experiments **Variable Coverage** - Verify all placeholders have corresponding Collection values **Content Review** - Validate generated prompts match intended test scenarios # Reference-free Evaluation of Retrieval *Learn to evaluate retrieval system quality without ground truth annotations using LLM-based judges* This example demonstrates how to evaluate the quality of a retrieval system without knowing correct chunks beforehand. Instead of requiring manually labeled correct chunks for each query, we use an LLM-based judge to rate the relevance and completeness of retrieved chunks. This approach is particularly valuable when: - You don't have access to ground truth annotations - You want to quickly iterate on retrieval parameters - You want to quickly evaluate retrieval quality on new domains ## Example This is an example from the document we will use in this guide. For a search query like - *"What are the methods for task decomposition in the Tree of Thoughts framework as proposed by Yao et al. 2023?"* a retrieval system with `top_k` set to four, might return the following chunks: - *"Tree of Thoughts (Yao et al. 2023) extends CoT by exploring multiple reasoning possibilities at each step. It first decomposes the problem into multiple thought steps and generates multiple thoughts per step, creating a tree structure. The search process can be BFS (breadth-first search) or DFS (depth-first search) with each state evaluated by a classifier (via a prompt) or majority vote. Task decomposition can be done (1) by LLM with simple prompting like "Steps for XYZ.\\n1.", "What are the subgoals for achieving XYZ?", (2) by using task-specific instructions; e.g. "Write a story outline." for writing a novel, or (3) with human inputs."* - *"Fig. 1. Overview of a LLM-powered autonomous agent system. Component One: Planning# A complicated task usually involves many steps. An agent needs to know what they are and plan ahead. Task Decomposition# Chain of thought (CoT; Wei et al. 2022) has become a standard prompting technique for enhancing model performance on complex tasks. The model is instructed to “think step by step” to utilize more test-time computation to decompose hard tasks into smaller and simpler steps. CoT transforms big tasks into multiple manageable tasks and shed lights into an interpretation of the model’s thinking process."* - *"The AI assistant can parse user input to several tasks: [{"task": task, "id", task_id, "dep": dependency_task_ids, "args": {"text": text, "image": URL, "audio": URL, "video": URL}}]. The "dep" field denotes the id of the previous task which generates a new resource that the current task relies on. A special tag "-task_id" refers to the generated text image, audio and video in the dependency task with id as task_id. The task MUST be selected from the following options: {{ Available Task List }}. There is a logical relationship between tasks, please note their order. If the user input can't be parsed, you need to reply empty JSON. Here are several cases for your reference: {{ Demonstrations }}. The chat history is recorded as {{ Chat History }}. From this chat history, you can find the path of the user-mentioned resources for your task planning."* - *"Another quite distinct approach, LLM+P (Liu et al. 2023), involves relying on an external classical planner to do long-horizon planning. This approach utilizes the Planning Domain Definition Language (PDDL) as an intermediate interface to describe the planning problem. In this process, LLM (1) translates the problem into “Problem PDDL”, then (2) requests a classical planner to generate a PDDL plan based on an existing “Domain PDDL”, and finally (3) translates the PDDL plan back into natural language. Essentially, the planning step is outsourced to an external tool, assuming the availability of domain-specific PDDL and a suitable planner which is common in certain robotic setups but not in many other domains. Self-Reflection# Self-reflection is a vital aspect that allows autonomous agents to improve iteratively by refining past action decisions and correcting previous mistakes. It plays a crucial role in real-world tasks where trial and error are inevitable."* Without knowing apriori which chunks are usefull, we will evaluate their relevance and completeness, as well as score how well they rank among each other. ## Overview The script implements a reference-free evaluation pipeline that: 1. Loads and processes a document (in this case, a blog post) 1. Creates embeddings and builds a vector store for retrieval 1. Performs retrieval for a set of test questions 1. Uses an LLM to judge the quality of retrieved contexts 1. Calculates evaluation metrics like Hit Rate and MRR (Mean Reciprocal Rank) We will achieve this by creatively repurposing elluminate's abstractions for prompt templates, responses, and criteria. A future version will support this use case more natively. At the end of this article, you can find the full Python script. ## **Prerequisites** First, install the required dependencies. This script is inspired by an example from [LangChain](https://python.langchain.com/docs/tutorials/rag/), but you can use any other library for embedding and retrieval. ``` pip install langchain-core langchain-text-splitters langchain-openai langchain-community bs4 loguru elluminate ``` You'll need to set up your API keys as environment variables: ``` export OPENAI_API_KEY="your-openai-key" export ELLUMINATE_API_KEY="your-elluminate-key" ``` You'll also need a set of questions to evaluate the retrieval system. You can come up with plausible examples, collect them from production logs, or generate them with an LLM. We provide a set of questions in the full script at the end of this article. ## **Setting up Retrieval** First, we create a vector store from our document. This function loads a web page (a [blog post](https://lilianweng.github.io/posts/2023-06-23-agent/) on AI agents in this case), splits it into chunks, and creates embeddings: ``` def setup_retrieval(url="https://lilianweng.github.io/posts/2023-06-23-agent/"): """Setup retrieval by loading the blog post and splitting it into chunks.""" loader = WebBaseLoader( web_paths=(url,), bs_kwargs=dict(parse_only=bs4.SoupStrainer(class_=("post-content", "post-title", "post-header"))), ) docs = loader.load() text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200) all_splits = text_splitter.split_documents(docs) embeddings = OpenAIEmbeddings( api_key=os.getenv("OPENAI_API_KEY_EMBEDDING_SMALL"), model="text-embedding-3-small", ) vector_store = InMemoryVectorStore(embeddings) vector_store.add_documents(documents=all_splits) return vector_store ``` elluminate expects the contexts to be a list of strings. So you might have to wrap your retrieval function: ``` def get_contexts(question, k) -> list[str]: """Wrap retrieval in a helper function to return the contexts as a list of strings.""" contexts = vector_store.similarity_search(question, k=k) return [c.page_content for c in contexts] ``` ## **Prompt Template with Criteria** We create a prompt template that will hold the search query later on. We also add evaluation criteria to the prompt template for rating the relevance and completeness of the retrieved contexts. As criteria, we choose: - **Relevance** - Does the chunk cover the information requested in the query? - **Completeness** - Is the chunk sufficient to answer the query? For the example query - *"What are the methods for task decomposition in the Tree of Thoughts framework as proposed by Yao et al. 2023?"* the following chunk is deemed **relevant** and **complete**: - *"Tree of Thoughts (Yao et al. 2023) extends CoT by exploring multiple reasoning possibilities at each step. It first decomposes the problem into multiple thought steps and generates multiple thoughts per step, creating a tree structure. The search process can be BFS (breadth-first search) or DFS (depth-first search) with each state evaluated by a classifier (via a prompt) or majority vote. Task decomposition can be done (1) by LLM with simple prompting like "Steps for XYZ.\\n1.", "What are the subgoals for achieving XYZ?", (2) by using task-specific instructions; e.g. "Write a story outline." for writing a novel, or (3) with human inputs."* This chunk, however, is only deemed **relevant**: - *"Fig. 1. Overview of a LLM-powered autonomous agent system. Component One: Planning# A complicated task usually involves many steps. An agent needs to know what they are and plan ahead. Task Decomposition# Chain of thought (CoT; Wei et al. 2022) has become a standard prompting technique for enhancing model performance on complex tasks. The model is instructed to “think step by step” to utilize more test-time computation to decompose hard tasks into smaller and simpler steps. CoT transforms big tasks into multiple manageable tasks and shed lights into an interpretation of the model’s thinking process."* ``` def create_prompt_template(prompt_template_name: str) -> PromptTemplate: """Create an empty prompt template with the search query as template variable.""" template, created = client.get_or_create_prompt_template( name=prompt_template_name, messages="{{question}}", ) if created: criterion_set = client.create_criterion_set(name=f"{prompt_template_name} Criteria") criterion_set.add_criteria( [ "Does the response share significant domain overlap with the query?", "Does the response contain the specific information requested in the query?", ] ) criterion_set.link_template(template) logger.info(f"Added criteria to prompt template {template.name}") return template ``` ## **Creating an Experiment** We need to create an experiment in order to run an evaluation. When responses are added to an experiment and then rated, the rating results can be inspected either via the SDK or in the frontend. ``` def create_experiment( prompt_template: PromptTemplate, collection_name: str = "Retrieval Test Variables", experiment_name: str = "Retrieval Test Experiment", ) -> tuple[Experiment, TemplateVariablesCollectionWithEntries]: """Create an experiment for retrieval testing.""" collection, _ = client.get_or_create_collection( name=collection_name, defaults={"description": "Template variables for retrieval test questions"}, ) llm_config = client.get_llm_config(name="GPT-4.1 Nano") experiment = client.create_experiment( name=experiment_name, prompt_template=prompt_template, collection=collection, description="Experiment for testing retrieval performance using LLM judges", llm_config=llm_config, ) logger.info(f"Created experiment: {experiment.name}") return experiment, collection ``` ## **Adding Contexts** We populate the prompt template with the query and add each retrieved context as its own distinct response. This way we can rate each context independently in regards to the query. Responses are assigned to the experiment from above to keep track of the rating results. ``` def add_contexts_as_responses( experiment: Experiment, collection: TemplateVariablesCollectionWithEntries, questions_and_contexts: dict[str, list[str]], ) -> list[list[PromptResponse]]: """Add the contexts as responses to the prompt template.""" all_responses = [] for question, chunks in questions_and_contexts.items(): template_variables = collection.add_many(variables=[{"question": question}])[0] responses = experiment.add_responses( responses=chunks, template_variables=[template_variables] * len(chunks), ) all_responses.append(responses) logger.info(f"Added {sum(len(x) for x in all_responses)} responses") return all_responses ``` ## **Rating the Contexts** elluminate rates each context for relevance and completeness. ``` def rate_contexts(experiment: Experiment, all_responses: list[list[PromptResponse]]) -> list[list[list[Rating]]]: """Rate the contexts for each search query.""" experiment.rate_responses(rating_mode=RatingMode.FAST) all_ratings = [] for responses in all_responses: response_ratings = [response.ratings for response in responses] all_ratings.append(response_ratings) logger.info(f"Rated {sum(len(x) for x in all_ratings)} responses") return all_ratings ``` ## **Calculating Metrics** Hit rate is the percentage of queries that have at least one relevant context. A higher hit rate indicates that your retrieval system is better at finding at least one useful result for each query. [MRR](https://en.wikipedia.org/wiki/Mean_reciprocal_rank) is a metric that measures how well the retrieval system ranks relevant results. For each query, it looks at the position of the first relevant result and takes the reciprocal (i.e. inverse) of that position ((\\frac{1}{position})). The final score is the average across all queries. ### A Simple Example **First query**: *"What are the health benefits of drinking water?"*\ **Retrieved contexts (in order)**: 1. *"Benefits of water for hydration and health..."* ✓ 1. *"Different types of beverages..."* 1. *"Water and exercise performance..."* ✓ 1. *"Water pollution statistics..."* → First relevant result is at position 1\ → Reciprocal rank = (\\frac{1}{1} = 1.0) **Second query**: *"What is the recommended daily water intake?"*\ **Retrieved contexts**: 1. *"Caffeine consumption guidelines..."* 1. *"Daily water intake recommendations..."* ✓ 1. *"Dehydration symptoms..."* 1. *"Water quality standards..."* → First relevant result is at position 2\ → Reciprocal rank = (\\frac{1}{2} = 0.5) **MRR of two examples** → MRR = (\\frac{1.0 + 0.5}{2} = 0.75) The implementation calculates both MRR (requiring relevance and completeness in our case) and a relevancy-only MRR, as well as hit rate and failures. ``` def calculate_metrics(all_ratings: list[list[list[Rating]]]) -> tuple[float, float, float, int]: """Calculate MRR (mean reciprocal rank) for each search query.""" rrs = [] relevancy_rrs = [] for ratings in all_ratings: rr = 0 found_relevant = False for rating in ratings: if len(rating) < 2: continue relevant = rating[0].rating complete = rating[1].rating if relevant and not found_relevant: relevancy_rrs.append(1 / (ratings.index(rating) + 1)) found_relevant = True if relevant and complete: rr = 1 / (ratings.index(rating) + 1) break rrs.append(rr) mrr = sum(rrs) / len(rrs) if rrs else 0 relevancy_mrr = sum(relevancy_rrs) / len(relevancy_rrs) if relevancy_rrs else 0 hit_rate = sum(1 for rr in rrs if rr > 0) / len(rrs) if rrs else 0 failures = sum(1 for rr in rrs if rr == 0) logger.info(f"MRR: {mrr}, Relevancy MRR: {relevancy_mrr}, Hit rate: {hit_rate}, Failures: {failures}") return hit_rate, mrr, relevancy_mrr, failures ``` ## **Running the Evaluation** The main evaluation function ties everything together: ``` def run_test(questions: list[str], retrieval_fn: Callable) -> tuple[float, float, float, int]: """Main test function.""" queries_and_contexts_dict = {question: retrieval_fn(question) for question in questions} prompt_template = create_prompt_template(prompt_template_name="Agent Blog Post Retrieval") experiment, collection = create_experiment(prompt_template) queries_and_contexts = add_contexts_as_responses(experiment, collection, queries_and_contexts_dict) all_ratings = rate_contexts(experiment, queries_and_contexts) return calculate_metrics(all_ratings) ``` After looking at the metrics in your terminal or inspecting single examples in the dashboard, you can tune your retrieval parameters to improve performance. ``` vector_store = setup_retrieval() client = Client(timeout=60) k = 8 logger.info(f"Experiment kwargs: {k=}") retrieval_fn = partial(get_contexts, k=k) hit_rate, mrr, relevancy_mrr, failures = run_test(questions, retrieval_fn=retrieval_fn) ``` ## **Full Script** ``` # Set your `OPENAI_API_KEY_EMBEDDING_SMALL` and `ELLUMINATE_API_KEY` in your environment """Retrieval Quality Assessment Example using LangChain (v1.0 API) This example demonstrates how to assess RAG (Retrieval-Augmented Generation) pipelines using Elluminate. It uses LangChain for document loading and vector search, then assesses the retrieved contexts using LLM-as-judge with criteria for relevance and completeness. Requires: langchain, langchain-community, langchain-openai, bs4, loguru """ import os from functools import partial from typing import Callable import bs4 from dotenv import load_dotenv from elluminate import Client from elluminate.schemas import ( Experiment, PromptResponse, PromptTemplate, Rating, RatingMode, TemplateVariablesCollectionWithEntries, ) from langchain_community.document_loaders import WebBaseLoader from langchain_core.vectorstores import InMemoryVectorStore from langchain_openai import OpenAIEmbeddings from langchain_text_splitters import RecursiveCharacterTextSplitter from loguru import logger load_dotenv(override=True) questions = [ "What are the key components that complement the LLM in an LLM-powered autonomous agent system?", "What is the difference between short-term memory and long-term memory in the context of AI models?", "What is the purpose of the Chain of Thought (CoT) technique?", "What are the methods for task decomposition in the Tree of Thoughts framework?", "What is the role of PDDL in the LLM+P approach?", ] def setup_retrieval(url="https://lilianweng.github.io/posts/2023-06-23-agent/"): """Setup retrieval by loading the blog post and splitting it into chunks.""" loader = WebBaseLoader( web_paths=(url,), bs_kwargs=dict(parse_only=bs4.SoupStrainer(class_=("post-content", "post-title", "post-header"))), ) docs = loader.load() text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200) all_splits = text_splitter.split_documents(docs) embeddings = OpenAIEmbeddings( api_key=os.getenv("OPENAI_API_KEY_EMBEDDING_SMALL"), model="text-embedding-3-small", ) vector_store = InMemoryVectorStore(embeddings) vector_store.add_documents(documents=all_splits) return vector_store def get_contexts(question, k) -> list[str]: """Wrap retrieval in a helper function to return the contexts as a list of strings.""" contexts = vector_store.similarity_search(question, k=k) return [c.page_content for c in contexts] def create_prompt_template(prompt_template_name: str) -> PromptTemplate: """Create an empty prompt template with the search query as template variable.""" template, created = client.get_or_create_prompt_template( name=prompt_template_name, messages="{{question}}", ) if created: criterion_set = client.create_criterion_set(name=f"{prompt_template_name} Criteria") criterion_set.add_criteria( [ "Does the response share significant domain overlap with the query?", "Does the response contain the specific information requested in the query?", ] ) criterion_set.link_template(template) logger.info(f"Added criteria to prompt template {template.name}") return template def create_experiment( prompt_template: PromptTemplate, collection_name: str = "Retrieval Test Variables", experiment_name: str = "Retrieval Test Experiment", ) -> tuple[Experiment, TemplateVariablesCollectionWithEntries]: """Create an experiment for retrieval testing.""" collection, _ = client.get_or_create_collection( name=collection_name, defaults={"description": "Template variables for retrieval test questions"}, ) llm_config = client.get_llm_config(name="GPT-4.1 Nano") experiment = client.create_experiment( name=experiment_name, prompt_template=prompt_template, collection=collection, description="Experiment for testing retrieval performance using LLM judges", llm_config=llm_config, ) logger.info(f"Created experiment: {experiment.name}") return experiment, collection def add_contexts_as_responses( experiment: Experiment, collection: TemplateVariablesCollectionWithEntries, questions_and_contexts: dict[str, list[str]], ) -> list[list[PromptResponse]]: """Add the contexts as responses to the prompt template.""" all_responses = [] for question, chunks in questions_and_contexts.items(): template_variables = collection.add_many(variables=[{"question": question}])[0] responses = experiment.add_responses( responses=chunks, template_variables=[template_variables] * len(chunks), ) all_responses.append(responses) logger.info(f"Added {sum(len(x) for x in all_responses)} responses") return all_responses def rate_contexts(experiment: Experiment, all_responses: list[list[PromptResponse]]) -> list[list[list[Rating]]]: """Rate the contexts for each search query.""" experiment.rate_responses(rating_mode=RatingMode.FAST) all_ratings = [] for responses in all_responses: response_ratings = [response.ratings for response in responses] all_ratings.append(response_ratings) logger.info(f"Rated {sum(len(x) for x in all_ratings)} responses") return all_ratings def calculate_metrics(all_ratings: list[list[list[Rating]]]) -> tuple[float, float, float, int]: """Calculate MRR (mean reciprocal rank) for each search query.""" rrs = [] relevancy_rrs = [] for ratings in all_ratings: rr = 0 found_relevant = False for rating in ratings: if len(rating) < 2: continue relevant = rating[0].rating complete = rating[1].rating if relevant and not found_relevant: relevancy_rrs.append(1 / (ratings.index(rating) + 1)) found_relevant = True if relevant and complete: rr = 1 / (ratings.index(rating) + 1) break rrs.append(rr) mrr = sum(rrs) / len(rrs) if rrs else 0 relevancy_mrr = sum(relevancy_rrs) / len(relevancy_rrs) if relevancy_rrs else 0 hit_rate = sum(1 for rr in rrs if rr > 0) / len(rrs) if rrs else 0 failures = sum(1 for rr in rrs if rr == 0) logger.info(f"MRR: {mrr}, Relevancy MRR: {relevancy_mrr}, Hit rate: {hit_rate}, Failures: {failures}") return hit_rate, mrr, relevancy_mrr, failures def run_test(questions: list[str], retrieval_fn: Callable) -> tuple[float, float, float, int]: """Main test function.""" queries_and_contexts_dict = {question: retrieval_fn(question) for question in questions} prompt_template = create_prompt_template(prompt_template_name="Agent Blog Post Retrieval") experiment, collection = create_experiment(prompt_template) queries_and_contexts = add_contexts_as_responses(experiment, collection, queries_and_contexts_dict) all_ratings = rate_contexts(experiment, queries_and_contexts) return calculate_metrics(all_ratings) if __name__ == "__main__": vector_store = setup_retrieval() client = Client(timeout=60) k = 8 logger.info(f"Experiment kwargs: {k=}") retrieval_fn = partial(get_contexts, k=k) hit_rate, mrr, relevancy_mrr, failures = run_test(questions, retrieval_fn=retrieval_fn) ``` # Reference Answers *Learn to evaluate LLM responses against known correct answers for accuracy testing and quality assurance* Reference answers provide a way to automatically evaluate generated responses against known correct answers. This is particularly useful for: - **Accuracy Testing** - Verify if LLM responses match expected answers - **Quality Assurance** - Ensure consistent performance across different prompts ## Key Steps - Create a prompt template with template variables. Please make sure that `{{reference_answer}}` isn't used as a template variable in your prompt template, otherwise you'll be giving away the answer. - Add criteria that use the template variable `{{reference_answer}}` - Create a Collection with all template variables that are used in the prompt template and the `{{reference_answer}}` template variable. - Thats it! You can now rate your responses against the reference answers. The following example demonstrates how to use reference answers with elluminate: ``` """v1.0 API version of example_sdk_usage_reference_answer.py Demonstrates using reference answers in evaluation criteria. The criterion can include template variables like {{reference_answer}} to compare the LLM's response against a known correct answer. """ from dotenv import load_dotenv from elluminate import Client from elluminate.schemas import RatingMode load_dotenv(override=True) client = Client() llm_config = client.get_llm_config(name="GPT-4.1 Nano") # v1.0: get_or_create_prompt_template - template is part of lookup template, _ = client.get_or_create_prompt_template( name="Geography Expert - Capitals", messages="What is the capital city of the following country: {{country}}. Give only the name of the capital city in your response, nothing else.", ) # v1.0: Create criterion set with reference answer criterion criterion_set, _ = client.get_or_create_criterion_set( name="Capital City Verification", ) # The criterion uses {{reference_answer}} from the template variables # This allows comparing LLM output against known correct answers criterion_set.add_criteria( [ "The correct capital city is: {{reference_answer}}. Is the given answer correct?", ] ) # v1.0: Link criterion set to template criterion_set.link_template(template) # v1.0: get_or_create_collection collection, _ = client.get_or_create_collection( name="Country Capitals", defaults={"description": "A collection of countries and their capital cities"}, ) # Template variables include both prompt inputs AND reference answers # The reference_answer is used by the criterion, not the prompt values = [ {"country": "France", "reference_answer": "Paris"}, {"country": "Japan", "reference_answer": "Tokyo"}, {"country": "Argentina", "reference_answer": "Buenos Aires"}, ] # v1.0: collection.add_many() - single call for all variables collection.add_many(variables=values) # v1.0: run_experiment() handles everything experiment = client.run_experiment( name="Capital Cities Experiment", prompt_template=template, collection=collection, criterion_set=criterion_set, rating_mode=RatingMode.FAST, llm_config=llm_config, ) # Display results for response in experiment.responses(): print(f"Prompt: {response.prompt.messages[-1]['content']}") print(f"Response: {response.response_str}") print(f"Rating: {response.ratings[0].rating}") print("-" * 80) ``` 1. First, we create the prompt template. Note that the we only use the template variables `{{country}}` here. 1. Here we add a criterion to the prompt template. Note that the criterion uses the template variable `{{reference_answer}}`, which will be filled with the reference answer from the template variables. 1. We define the template variables. Each template variable contains a value for `country` and the `reference_answer` which is the correct answer for the given country. ## Evaluation Criteria Define criteria that use the reference answers to evaluate responses. Common patterns include: - Exact match comparison - Semantic similarity checking # Response Analysis *Master the art of analyzing and evaluating your LLM experiment results* After running an experiment, elluminate provides comprehensive tools to analyze your results. This guide covers how to understand experiment outcomes, inspect individual responses, analyze criterion performance, and work with detailed ratings and reasonings. All analysis happens through the **Experiments** page in your project. Navigate to your experiment and use the two-tab interface to analyze results from different angles. ## Understanding Experiment Results ### Two Views of Experiment Data Every experiment in elluminate provides two complementary perspectives on your results: 1. **Detailed Analysis** - Aggregate view showing overall performance metrics and criterion-based breakdowns 1. **Individual Responses** - Sample-by-sample inspection with full response content and detailed ratings ### Key Metrics Explained **Pass Rates**: Percentage of responses that met each criterion - **Pass** (≥80%) - Green badge, criterion consistently met - **Warning** (50-79%) - Yellow badge, inconsistent performance - **Fail** (\<50%) - Red badge, criterion frequently unmet **Overall Performance**: Aggregate scores showing experiment health - **Success Rate** - Percentage of responses meeting all criteria - **Token Usage** - Input and output token consumption - **Response Times** - Average generation times per response When you navigate to an experiment's detail page, you'll find the main sections for analyzing your results: ### Experiment Overview The overview section displays: - **Overall Performance Metrics** - Success rates, token usage, response times - **Criteria Performance** - Individual criterion pass/fail rates - **Generation Progress** - Real-time status during experiment execution - **Error Summary** - Failed responses and internal errors - **Charts** - The Charts are interactive and allow for quick filtering of responses. ## Detailed Analysis Tab The "Detailed Analysis" tab provides sample-by-sample inspection of your experiment results. ### Sample Navigation - **Navigate Responses** - Browse individual prompt-response pairs - **Filter by Criteria** - Focus on specific evaluation criteria - **Sort Options** - Order by score, criteria performance, or chronologically - **Comparison Mode** - When comparing experiments, view results side-by-side - **Text Search** - Use `Cmd/Ctrl+K` to focus the search bar in the sample navigator toolbar. Search filters automatically as you type. Results are matched across annotations, rating reasoning, message content, and template variable values. The counter updates to `X / Y (Z)` format showing current position, filtered count, and total. Tip Use annotations as searchable tags — write a short label (e.g. "hallucination", "off-topic") in the annotation field, then search for it later to quickly find all samples you tagged. ### Rating Details Each response shows detailed evaluation information: #### Criterion Performance - **Pass/Fail Indicators** - Clear visual status for each criterion - **Performance Metrics** - Success rates when viewing multiple responses - **Criterion Text** - Full evaluation questions with variable substitution #### Reasoning Analysis When experiments use `RatingMode.DETAILED`, each rating includes: - **Explanation Text** - Why the response passed or failed each criterion - **Manual Override** - Edit both ratings and reasoning if needed - **Rating Confidence** - System confidence in the evaluation ## Individual Responses Tab The "Individual Responses" tab provides a tabular view of all experiment results. ### Response Table Features - **Sortable Columns** - Order by score, tokens, response time, or other metrics - **Filtering Options** - Show only specific score ranges or criterion results - **Bulk Operations** - Export or analyze multiple responses simultaneously - **Response Preview** - View full LLM responses without leaving the table ### Export and Analysis Export your results, including ratings, reasonings, and metadata, in various formats: - **XLSX Export** - **JSON Export** - **XML Copy** ## Advanced Analysis Workflows ### Multi-Experiment Comparison elluminate supports comparing multiple experiments to identify improvements and understand performance differences. #### Two-Experiment Comparison When comparing two experiments, you get a detailed side-by-side analysis: - **Side-by-Side View** - Compare responses to identical prompts - **Performance Deltas** - See which experiment performed better per criterion - **Statistical Significance** - Understand confidence in performance differences #### Multi-Experiment Comparison For comparing three or more experiments, you get a high-level overview: - **Performance Trending** - See how experiments perform relative to each other - **Criterion Overview** - Compare pass rates across multiple experiments - **Quick Selection** - Easily select specific experiments for detailed comparison ## Keyboard Shortcuts The Detailed Analysis tab supports the following keyboard shortcuts: | Shortcut | Action | | ------------ | -------------------------------------------------- | | `Cmd/Ctrl+K` | Focus the search bar | | `Escape` | Clear search and unfocus | | `Cmd/Ctrl+G` | Jump to next search result (when search is active) | | `←` / `→` | Navigate to previous / next sample | | `↑` / `↓` | Navigate to previous / next epoch | Note Arrow key navigation is disabled when a text input or textarea is focused. ## Best Practices ### Systematic Analysis Approach 1. **Start with Overview** - Understand overall experiment performance 1. **Identify Patterns** - Use charts to spot performance trends 1. **Drill Down** - Investigate individual responses that represent patterns 1. **Document Insights** - Use experiment comments to capture findings 1. **Iterate** - Apply insights to improve prompts and evaluation criteria ### Manual Override Guidelines - **Document Changes** - Always explain why manual overrides were necessary - **Maintain Consistency** - Apply similar standards across all manual reviews - **Regular Calibration** - Periodically review override patterns for consistency - **Team Alignment** - Ensure multiple reviewers apply consistent standards ### Performance Optimization - **Balance Speed vs. Detail** - Choose appropriate rating modes for your use case - **Strategic Sampling** - Focus detailed analysis on representative samples - **Regular Reviews** - Schedule periodic analysis sessions to maintain quality ## Troubleshooting ### Common Analysis Challenges - **Inconsistent Ratings** - Check for prompt ambiguity or criterion clarity issues - **Low Performance** - Analyze whether issues are prompt-related or model-related - **High Variance** - Look for template variable values causing performance differences - **Export Issues** - Verify filter settings and permissions before exporting ### Getting Help When analysis results seem unexpected: - **Review Criteria** - Ensure evaluation criteria match your actual requirements - **Check Variables** - Verify template variables are being substituted correctly - **Validate Prompts** - Confirm prompts are generating expected response types - **Contact Support** - Reach out with specific experiment IDs for detailed assistance at [help@elluminate.de](mailto:help@elluminate.de) # SDK Migration Guide (v0.7 → v1.0) *Upgrade your elluminate SDK code to the new v1.0 API* SDK v1.0 introduces significant improvements to developer experience: - **Separate sync/async clients** - No more `a`-prefix on async methods - **Rich models with methods** - `collection.add_many()` instead of `client.template_variables.add_many(collection=...)` - **Top-level convenience methods** - `client.create_collection()` instead of `client.collections.create()` - **Cleaner API** - Consistent patterns, better discoverability ## Quick Reference | v0.7 | v1.0 | | ------------------------------------------------------- | --------------------------------------------- | | `client.experiments.get(name)` | `client.get_experiment(name=...)` | | `client.experiments.get_by_id(id)` | `client.get_experiment(id=...)` | | `client.experiments.aget(name)` | `await async_client.get_experiment(name=...)` | | `client.collections.create(name="x")` | `client.create_collection("x")` | | `client.template_variables.add_many(collection=c, ...)` | `collection.add_many(...)` | | `client.template_variables.list(collection=c)` | `collection.items()` | | `client.experiments.run(experiment)` | `client.run_experiment(...)` | | `response.messages[-1].content` | `response.response_str` | | `rating.rating.value == "yes"` | `rating.rating == RatingValue.YES` | ## Step 1: Update Imports ### Sync Usage (Unchanged) ``` from elluminate import Client ``` ### Async Usage (New Pattern) ``` # Async usage now requires AsyncClient from elluminate import AsyncClient ``` ## Step 2: Update Client Usage ### Sync Client (Unchanged) ``` # v0.7 client = Client(api_key="...", project_id=123) # v1.0 - Same! client = Client(api_key="...", project_id=123) ``` ### Async Client (New Pattern) ``` # v0.7 - Used same Client with a-prefixed methods client = Client() template = await client.prompt_templates.aget_or_create(...) experiment = await client.experiments.acreate(...) # v1.0 - Use separate AsyncClient, same method names async_client = AsyncClient() template, _ = await async_client.get_or_create_prompt_template(...) experiment = await async_client.create_experiment(...) ``` ## Step 3: Update Resource Operations ### Collections ``` # v0.7 collection = client.collections.create(name="Test Cases") client.template_variables.add_many( collection=collection, variables=[{"topic": "AI"}, {"topic": "ML"}] ) # v1.0 collection = client.create_collection("Test Cases") collection.add_many(variables=[ {"topic": "AI"}, {"topic": "ML"}, ]) ``` ### Get or Create Pattern The `get_or_create` methods now use a `defaults` dict for creation-only parameters: ``` # v0.7 collection, created = client.collections.get_or_create(name="Test Cases") # v1.0 - Uses defaults dict for creation-only params collection, created = client.get_or_create_collection( name="Test Cases", defaults={"description": "My test cases"}, ) ``` ### Prompt Templates ``` # v0.7 template, created = client.prompt_templates.get_or_create( name="My Template", template="Write about {{topic}}", ) # v1.0 - messages is part of lookup # If name+content match: returns existing version # If name exists but content differs: creates NEW version template, created = client.get_or_create_prompt_template( name="My Template", messages=[{"role": "user", "content": "Write about {{topic}}"}], ) ``` ### Criterion Sets ``` # v0.7 criterion_set = client.criterionsets.create(name="Quality") client.criteria.create( criterion_set=criterion_set, name="clarity", description="Is it clear?" ) # v1.0 criterion_set = client.create_criterion_set("Quality") criterion_set.add_criteria([ "Is it clear?", "Is it accurate?", ]) ``` ## Step 4: Update Experiment Workflow ### Creating and Running Experiments ``` # v0.7 experiment = client.experiments.create( name="Test v1", prompt_template=template, collection=collection, criterion_set=criterion_set, llm_config="gpt-4", ) result = client.experiments.run(experiment) # v1.0 - Use run_experiment() shortcut (creates + runs in one call) from elluminate.schemas import RatingMode result = client.run_experiment( name="Test v1", prompt_template=template, collection=collection, criterion_set=criterion_set, llm_config="gpt-4", rating_mode=RatingMode.DETAILED, # FAST or DETAILED (with reasoning) ) ``` ## Step 5: Update Async Method Calls All `a`-prefixed methods are replaced by normal method names on `AsyncClient`: ``` # v0.7 client = Client() template = await client.prompt_templates.aget(name) experiments = await client.experiments.alist() collection = await client.collections.acreate(name="Test") # v1.0 async_client = AsyncClient() template = await async_client.get_prompt_template(name) experiments = await async_client.list_experiments() collection = await async_client.create_collection("Test") ``` | v0.7 | v1.0 | | ------------------------------ | ----------------------------------------- | | `client.X.aget(...)` | `await async_client.get_X(...)` | | `client.X.alist(...)` | `await async_client.list_X()` | | `client.X.acreate(...)` | `await async_client.create_X(...)` | | `client.X.aget_or_create(...)` | `await async_client.get_or_create_X(...)` | ## Step 6: Update Response and Rating Access ### Response Content Access ``` # v0.7 - Manual message extraction for response in experiment.rated_responses: content = response.messages[-1].content # v1.0 - Use response_str property for response in experiment.responses(): content = response.response_str # Cleaner, handles edge cases ``` ### Rating Comparisons ``` # v0.7 - String comparison (error-prone) for rating in response.ratings: if rating.rating.value == "yes": # Case-sensitive! passed += 1 # v1.0 - Use RatingValue enum (type-safe) from elluminate.schemas import RatingValue for rating in response.ratings: if rating.rating == RatingValue.YES: # IDE autocomplete, catches typos passed += 1 ``` ## Complete Before/After Example ### Before (v0.7) ``` from elluminate import Client client = Client() # Get or create resources template, _ = client.prompt_templates.get_or_create( name="Summarizer", template="Summarize: {{text}}", ) collection, _ = client.collections.get_or_create(name="Test Texts") client.template_variables.add_many( collection=collection, variables=[ {"text": "Long article about AI..."}, {"text": "Technical documentation..."}, ] ) criterion_set, _ = client.criterionsets.get_or_create(name="Summary Quality") client.criteria.create( criterion_set=criterion_set, description="Is the summary concise?", ) # Create and run experiment experiment = client.experiments.create( name="Summary Test", prompt_template=template, collection=collection, criterion_set=criterion_set, llm_config="gpt-4", ) result = client.experiments.run(experiment) print(f"Average: {result.average_rating:.2%}") ``` ### After (v1.0) ``` from elluminate import Client client = Client() # Get or create resources template, _ = client.get_or_create_prompt_template( name="Summarizer", messages=[{"role": "user", "content": "Summarize: {{text}}"}], ) collection, _ = client.get_or_create_collection( name="Test Texts", defaults={"description": "Test texts for summarization"}, ) collection.add_many(variables=[ {"text": "Long article about AI..."}, {"text": "Technical documentation..."}, ]) criterion_set, _ = client.get_or_create_criterion_set( name="Summary Quality", defaults={"description": "Quality criteria for summaries"}, ) criterion_set.add_criteria(["Is the summary concise?"]) # Run experiment (shortcut) result = client.run_experiment( name="Summary Test", prompt_template=template, collection=collection, criterion_set=criterion_set, llm_config="gpt-4", ) print(f"Average: {result.average_rating:.2%}") ``` ## Migration Checklist ### Imports - [ ] Add `from elluminate import AsyncClient` if using async - [ ] Add `from elluminate.schemas import RatingValue` for rating comparisons ### Client Usage - [ ] Keep sync `Client()` usage (unchanged) - [ ] Replace async method calls with `AsyncClient()` ### Collections - [ ] `client.collections.create(name="X")` → `client.create_collection("X")` - [ ] `client.collections.get_or_create(name="X")` → `client.get_or_create_collection("X", defaults={...})` - [ ] `client.template_variables.add_many(collection=c, ...)` → `collection.add_many(...)` ### Prompt Templates - [ ] `client.prompt_templates.get_or_create(...)` → `client.get_or_create_prompt_template(...)` - [ ] `client.prompt_templates.get(name=X)` → `client.get_prompt_template(X)` ### Criterion Sets - [ ] `client.criterionsets.create(name="X")` → `client.create_criterion_set("X")` - [ ] `client.criteria.create(criterion_set=cs, ...)` → `criterion_set.add_criteria([...])` ### Experiments - [ ] `client.experiments.create(...)` → `client.create_experiment(...)` - [ ] `client.experiments.run(exp)` → `client.run_experiment(...)` ### Async Methods - [ ] Replace all `a`-prefixed methods with `AsyncClient` + normal methods - [ ] `client.X.aget(...)` → `await async_client.get_X(...)` - [ ] `client.X.acreate(...)` → `await async_client.create_X(...)` - [ ] Use `collection.items()` for iteration (works in both sync and async contexts) ### LLM Config - [ ] `client.llm_configs.get_or_create(...)` → `client.get_or_create_llm_config(...)` with `defaults` dict ### Client Lifecycle - [ ] Add `client.close()` or `await async_client.close()` when done - [ ] Or use context manager: `with Client() as client:` / `async with AsyncClient() as async_client:` ### Response and Rating Access - [ ] `response.messages[-1].content` → `response.response_str` - [ ] `rating.rating.value == "yes"` → `rating.rating == RatingValue.YES` ### Exception Handling (Security) - [ ] `exception.response` → `exception.response_info` - [ ] `exception.response.status_code` → `exception.response_info.status_code` - [ ] `exception.response.json()` → `exception.response_info.body` - [ ] Remove any code accessing `exception.response.request.headers` - [ ] Update error logging to use sanitized `response_info` ## FAQ ### Do I need to migrate immediately? No. v0.7.x will receive security updates for 6 months after v1.0 release. ### What if I only use sync methods? Migration is simpler - mainly changing from resource-based to top-level methods: - `client.collections.create()` → `client.create_collection()` - `client.experiments.run(e)` → `client.run_experiment(...)` ### Are there any breaking behavior changes? Yes, a few: - `get_or_create_*()` methods now use `defaults` dict for creation-only parameters - `get_or_create_prompt_template()` creates a new version if the messages content differs - Use `run_experiment()` to create and run experiments in one call - Exception handling changed (see below) ## Step 7: LLM Config with Defaults The `get_or_create_llm_config` method now uses a `defaults` dict for creation-only parameters: ``` # v0.7 llm_config = client.llm_configs.get_or_create( name="My LLM", llm_model_name="gpt-4", temperature=0.7, ) # v1.0 - Use defaults dict for creation-only parameters llm_config = client.get_or_create_llm_config( name="My LLM", llm_model_name="gpt-4", defaults={ "temperature": 0.7, }, ) ``` ## Step 8: Client Lifecycle Remember to close the client when done to release HTTP connections: ``` # v1.0 - Close client when done client = Client() # ... do work ... client.close() # Or use context manager (recommended) with Client() as client: # ... do work ... pass # Async async with AsyncClient() as async_client: # ... do async work ... pass ``` ## Step 9: Collection Iteration Collection iteration uses `items()` in both sync and async contexts (no API call needed - data is already loaded): ``` # v0.7 async for item in client.template_variables.alist(collection=collection): print(item.values) # v1.0 - Use items() (works in both sync and async) for item in collection.items(): print(item.values) ``` **Note:** `items()` iterates over already-loaded data, so no async variant is needed. ## Step 10: Update Exception Handling **Breaking Change:** Exceptions no longer expose the full HTTP response object for security reasons. ``` # v0.7 try: client.get_experiment(id=999) except APIError as e: print(e.response.status_code) # v1.0 try: client.get_experiment(id=999) except APIError as e: print(e.response_info.status_code) # Use response_info ``` **What changed:** | v0.7 | v1.0 | | ---------------------------- | ----------------------------- | | `e.response.status_code` | `e.response_info.status_code` | | `e.response.json()` | `e.response_info.body` | | `e.response.request.headers` | ❌ Not available | This prevents accidental API key leakage in logs and error tracking services. # Single Sign-On (SSO) *Elluminate supports your own Single Sign-On for enterprise customers, allowing your team to log in with their existing organizational accounts.* Your own SSO lets your team members sign in to elluminate using their existing identity provider — no separate passwords to manage. Note SSO does not replace user invitations. Team members must still be invited to your organization in elluminate before they can sign in. SSO simply provides a more convenient and secure authentication method. ## Setup To enable your own SSO for your organization, contact us at [help@elluminate.de](mailto:help@elluminate.de?subject=SSO%20Setup%20Request). # Structured Outputs *Master the evaluation of programmatically formatted LLM responses essential for agentic applications* Structured outputs enable the developer to enforce that the LLM produces responses formatted in a programatically deterministic manner. Agentic programs make great use of this feature to enable interoperability code paths and LLM responses. This makes evaluating structured outputs an essential part for evaluating agents. ## Basic Usage An example showcasing using Pydantic models for structured output generation and evaluation: ``` """v1.0 API: Structured Outputs Example Demonstrates how to use Pydantic models as response_format for structured LLM outputs. The model analyzes product reviews and extracts structured sentiment analysis data. v1.0 API changes: - client.prompt_templates.aget_or_create() -> client.get_or_create_prompt_template() - client.collections.aget_or_create() -> client.get_or_create_collection() - client.criteria.aadd_many() -> criterion_set.add_criteria() - client.experiments.aget_or_create() -> client.run_experiment() - Sync-first approach (no asyncio.run needed) INSIGHT: Structured outputs use Pydantic models to enforce response schemas. The LLM is instructed to return JSON matching the schema, and the response is validated against it. This is powerful for data extraction tasks. """ from dotenv import load_dotenv from elluminate import Client from elluminate.schemas import RatingMode from pydantic import BaseModel, Field load_dotenv(override=True) class ProductReviewSentimentAnalysis(BaseModel): """Schema for structured sentiment analysis of reviews.""" stars: int = Field(description="Number of stars of the review", ge=1, le=5) sentiment: str = Field( description="Overall sentiment: positive, negative, or neutral", pattern="^(positive|negative|neutral)$", ) confidence: float = Field( description="Confidence score of the sentiment analysis between 0 and 1", ge=0, le=1, ) def main(): client = Client() llm_config = client.get_llm_config(name="GPT-4.1 Nano") print("v1.0: Structured Outputs Example") print("=" * 50) # v1.0: get_or_create_prompt_template with response_format template, created = client.get_or_create_prompt_template( name="Product Review Analysis v1", messages="""Analyze this product review and extract key information: Review: {{review_text}} Provide review stars, sentiment and confidence score.""", response_format=ProductReviewSentimentAnalysis, ) print(f"Template: {'Created' if created else 'Found existing'}") # v1.0: get_or_create_collection with variables at creation time collection, created = client.get_or_create_collection( name="Product Review Data v1", ) if created: collection.add_many( variables=[ { "review_text": "Stars: **** Great wireless headphones! Audio quality is fantastic and noise cancellation works perfectly. Battery could be better but overall very satisfied." }, { "review_text": "Stars: ** Poor laptop experience. Screen flickered after 2 weeks, customer service was helpful, but would not recommend this product." }, ] ) print(f"Collection: {'Created' if created else 'Found existing'}") # v1.0: get_or_create_criterion_set with add_criteria criterion_set, created = client.get_or_create_criterion_set(name="Review Analysis Criteria v1") if created: criterion_set.add_criteria( [ "In the 'stars' field, is the counted number of stars correct?", "Does the 'sentiment' field accurately reflect the review's tone?", "Is the 'confidence' score appropriate for the certainty of the sentiment?", ] ) print(f"Criterion set: {'Created' if created else 'Found existing'}") # v1.0: run_experiment - creates and runs in one call print("\nRunning experiment...") experiment = client.run_experiment( name="Review Analysis Experiment v1", prompt_template=template, collection=collection, criterion_set=criterion_set, rating_mode=RatingMode.FAST, n_epochs=1, llm_config=llm_config, ) print(f"\nExperiment completed: {experiment.name}") print(f"Total responses: {len(experiment.rated_responses)}") # Display results for i, response in enumerate(experiment.responses(), 1): print(f"\n--- Example {i} ---") review_text = response.prompt.template_variables.input_values["review_text"] print(f"Review: {review_text[:80]}...") print("Analysis:") for message in response.messages: if message.role == "assistant": print(f" {message.content}") # Show aggregated results if experiment.result: print("\n--- Results ---") print(f"Pass rate: {experiment.result.mean_all_ratings.yes:.2%}") # ========================================================================= # Migration Insights # ========================================================================= # # 1. RESPONSE FORMAT # Both versions: Pass Pydantic model class as response_format # v1.0: Pass as keyword argument (part of template identity) # The SDK automatically converts to JSON schema for the API # # 2. CRITERIA SETUP # v0.x: client.criteria.aadd_many([...], template, delete_existing=True) # v1.0: criterion_set.add_criteria([...]) # Note: v1.0 links criteria to criterion_set, not directly to template # The criterion_set is then linked to the template via experiment # # 3. DELETE_EXISTING PATTERN # v0.x: delete_existing=True to replace criteria # v1.0: Create new criterion_set or check if created before adding # This is a cleaner pattern that doesn't destroy existing data # # 4. PYDANTIC FIELD CONSTRAINTS # Both versions support Field() with: # - ge/le for numeric ranges # - pattern for string regex validation # - description for LLM guidance # These constraints help the LLM generate valid responses # # 5. SDK ENHANCEMENT OPPORTUNITY # The v0.x delete_existing=True pattern is useful for iteration. # Consider adding criterion_set.clear() or criterion_set.replace_criteria() # for easier iteration during development. # if __name__ == "__main__": main() ``` ______________________________________________________________________ 1. **Define Schema**: Create a Pydantic model with field descriptions and basic constraints to define the exact JSON structure you want the LLM to return 1. **Create Template**: Use the `response_format` parameter when creating a prompt template to specify that responses should follow your Pydantic model structure 1. **Add Criteria**: Define evaluation criteria that reference specific schema fields - criteria may also be auto-generated as per usual 1. **Run Experiment**: Create and run experiments normally - the structured output format will be enforced automatically for all response generations 1. **Access Responses**: The structured outputs can be found in the assistant message's `content` key as a JSON string ## Schema Definition Methods ### Pydantic Models Pydantic models provide the most intuitive and recommended way to define structured output schemas. Simply set the `response_format` to the Pydantic class definition, and elluminate handles the rest. ### OpenAI JSON Schema Format In addition to Pydantic models, you may also set the `response_format` directly with an OpenAI JSON Schema definition: ``` schema = { "type": "json_schema", "json_schema": { "name": "sentiment", "schema": { "type": "object", "properties": { "stars": { "type": "integer", "description": "Number of stars of the review", "minimum": 1, "maximum": 5 }, "sentiment": { "type": "string", "description": "The sentiment output, could be positive, negative, or neutral.", "enum": [ "positive", "negative", "neutral" ] }, "confidence": { "type": "number", "description": "Confidence score of the sentiment analysis between 0 and 1", "minimum": 0, "maximum": 1 } }, "required": [ "stars", "sentiment", "confidence" ], "additionalProperties": False } } } ``` ### AI-Powered Schema Generation The frontend provides an AI-powered schema generator that creates JSON schemas from natural language descriptions. Simply describe what you want to extract, and elluminate will generate an appropriate schema. ## Evaluating Structured Outputs The rating model has access to all field descriptions from your structured output schema, providing valuable context about what each field should contain and how it should be interpreted. Subsequently to evaluate structured outputs, simply create criteria and run an experiment as per usual. Using Field Names in Criteria It may be beneficial to use field names from your schema in the criteria. This helps the rating model understand exactly which part of the JSON structure to focus on. For example, "Does the 'sentiment' field..." is more precise than "Is the sentiment correct?" # Test Set Generation *Automatically expand your test datasets with semantically similar synthetic examples* elluminate provides functionality to generate synthetic test data based on existing template variables. This allows you to automatically expand your test sets with semantically similar examples. ## Usage in the SDK The following example demonstrates how to generate synthetic test data: ``` """v1.0 API version of example_sdk_usage_generate_testset.py Demonstrates testset generation - automatically expanding test collections with LLM-generated variations based on existing examples. v1.0 API: - collection.generate_variables(prompt_template) - generates a new test case using AI """ from dotenv import load_dotenv from elluminate import Client load_dotenv(override=True) client = Client() # v1.0: get_or_create_prompt_template - messages is part of lookup template, _ = client.get_or_create_prompt_template( name="University Nobel Laureates (Generated)", messages="List the most impactful Nobel Prize winners from {{university}} in {{state}} " "and their breakthrough discoveries.", ) # v1.0: get_or_create_collection (name is the lookup key) collection, _ = client.get_or_create_collection( name="Top Universities - Generated", defaults={"description": "A collection of prestigious US universities (Generated)"}, ) # v1.0: Seed the collection with initial example data # This gives the generator examples to learn the pattern from collection.add_many( variables=[ {"university": "MIT", "state": "Massachusetts"}, ] ) # v1.0: Generate new test cases using AI # The generator uses the existing entries as examples to create new variations generated_values = [] for _ in range(2): generated_var = collection.generate_variables(template) generated_values.append(generated_var) print("Generated test cases:") for template_variables in generated_values: print(f" {template_variables.input_values}") # Example output: # {'state': 'Alaska', 'university': 'University of Alaska Fairbanks'} # {'state': 'Tennessee', 'university': 'Vanderbilt University'} ``` 1. Create a prompt template that defines the structure of your prompts. The template variables will be used to generate the test data. 1. Create a collection to store your template variables and add the base examples. 1. Define your base template variables that will serve as examples for test data generation. 1. Generate test data using `generate_entry`. The generated data will maintain the same structure as your base data while providing semantic variations. ## Usage in the Frontend Open the Template Variable Collection and click on the small *Magic Generate Button* (with a star) at the bottom. You may click multiple times to generate more examples. ## Best Practices 1. **Quality Base Data**: Start with high-quality, representative example data for better synthetic generations. 1. **Validation**: Always review generated synthetic data before using them in production. 1. **Diversity**: Include diverse base examples to get more varied synthetic data. 1. **Iterative Refinement**: Use generated examples to identify potential edge cases and improve your prompt templates. # The Basics *Master the core components of elluminate and how they work together to enable systematic evaluation of AI systems* This section introduces the core components of elluminate and how they work together to enable systematic evaluation of AI systems. If you want to see this in action, check out our [Quick Start](https://docs.elluminate.de/get_started/quick_start/index.md) or explore our detailed concept guides below. ## Projects and Templates At the heart of elluminate is the concept of **Projects**, **PromptTemplates**, **TemplateVariables** and the resulting **Prompts**. - **Projects** serve as the top-level container for organizing all your evaluation work. Projects can be either private or public within your organization. - **Prompt templates** are reusable templates containing variables that can be replaced with specific values - **Template variables** are key-value pairs that can be inserted into a prompt template - **Prompts** are the result of combining a prompt template with template variables ## Collections Template variables can be grouped into a **Collection** (specifically a **TemplateVariablesCollection**). This organizes related sets of template variables and allows you to generate multiple prompts systematically. Collections help you: - Organize related sets of variables - Generate multiple prompts from a single template - Maintain consistent test cases - Scale evaluation across diverse scenarios ## Evaluation and Analysis Prompts generate responses, typically from LLMs. elluminate allows you to systematically evaluate these responses using **Criterion Sets** and analyze the results through **Response Analysis**. ### Criterion Sets and Criteria - **Criterion Sets** group related evaluation questions - **Criteria** are specific binary questions that assess response quality - Each criterion receives a "yes" or "no" rating with detailed reasoning ### Experiments **Experiments** bring everything together by combining: - A specific prompt template - A collection of test variables - LLM configuration settings - Criterion sets for evaluation ### Response Analysis Once responses are generated and evaluated, **Response Analysis** provides tools to: - Review individual response ratings - Identify patterns across multiple responses - Compare performance across different configurations - Make data-driven improvements to your AI system ## Version Control **Versioning** ensures evaluation consistency and enables change tracking: - All components (templates, collections, criterion sets) are versioned - Experiments reference specific versions for reproducibility - Changes create new versions while preserving historical data ## Next Steps Now that you understand the basic concepts, you can: - Explore each concept in detail using the guides in this section - Follow our [Quick Start](https://docs.elluminate.de/get_started/quick_start/index.md) to see these concepts in action - Try the [SDK examples](https://docs.elluminate.de/get_started/quick_start_sdk/index.md) for programmatic access # Tool Calling *Learn to evaluate LLMs that interact with external systems through predefined functions and tools* Tool calling enables LLMs to interact with external systems by providing access to predefined functions during response generation. This guide covers evaluating an LLM's **tool selection** when elluminate generates the response: you attach tool definitions to a prompt template, elluminate generates a response in which the model *chooses* tool calls (it does not execute them), and your criteria judge those choices. Tool Calling vs. Agentic Evaluations — which guide? Use **Tool Calling** (this guide) when elluminate should **generate** the response and you want to evaluate a single-turn tool *selection* (no execution). For **multi-step agents that actually run tools** over many turns, use [Agentic Evaluations](https://docs.elluminate.de/guides/agentic_evaluations/index.md) instead: you run the agent yourself and upload its trajectory. ## Basic Usage An example showcasing weather tool integration for real-time data access: ``` template, created = client.get_or_create_prompt_template( name="Weather Assistant with Tools v1", messages="""\ You are a helpful weather assistant. The user is asking: {{user_query}}. Use the available weather tools to provide accurate information. Respond in the units most customary of the location being queried.""", tools=[ FunctionTool( # (1)! type="function", function={ "name": "get_current_weather", "description": "Get the current weather in a given location", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "The city and state/country, e.g. Berlin, DE", }, "unit": { "type": "string", "enum": ["celsius", "fahrenheit"], "description": "The temperature unit to use", }, }, "required": ["location", "unit"], "additionalProperties": False, }, "strict": True, }, ), ], tool_choice="auto", # (2)! ) ``` 1. **Define Tools**: Set `tools` to function tool definitions using OpenAI's `FunctionTool` type. These definitions describe each function and specify exactly what input data the function expects and what output data it returns. 1. **Create Template**: Optionally, set `tool_choice` to control when the model should use tools. By default when omitted, it is "auto". Once the prompt template has been created with the tool definitions, the rest of the evaluation process is as normal. Experiments execute normally - the model automatically produces (but does not run) tool calls - and the criteria evaluate the chosen tools. Tool calls can be found in the assistant message's `'tool_calls'` as a list of tools called: ``` for message in response.messages: if message.role == "assistant" and message.tool_calls: for tool_call in message.tool_calls: print(f" - {tool_call.function.name}({tool_call.function.arguments})") ``` Tool Execution elluminate currently does not support running your tools. When running experiments, you will see the tool call that was selected, but not the actual output or results from that tool. In order to evaluate the executions of tools, refer to the [Advanced Example](#advanced-example) section below, since some special care is necessary. Complete Basic Example ``` """v1.0 API: Tool Calling Example Demonstrates how to define tools (functions) that the LLM can call during generation. This example creates a weather assistant that can use a get_current_weather tool. v1.0 API changes: - client.prompt_templates.aget_or_create() -> client.get_or_create_prompt_template() - client.collections.aget_or_create() -> client.get_or_create_collection() - client.criteria.aget_or_generate_many() -> template.get_or_generate_criteria() - client.experiments.aget_or_create() -> client.run_experiment() - Sync-first approach INSIGHT: Tool definitions use OpenAI's FunctionTool format. The LLM decides when to call tools based on the user query. elluminate captures tool calls in the response messages, allowing you to evaluate tool usage patterns. """ from dotenv import load_dotenv from elluminate import Client from elluminate.schemas import RatingMode from openai.types.beta import FunctionTool load_dotenv(override=True) def main(): client = Client() llm_config = client.get_llm_config(name="GPT-4.1 Nano") print("v1.0: Tool Calling Example") print("=" * 50) # v1.0: get_or_create_prompt_template with tools as keyword arguments template, created = client.get_or_create_prompt_template( name="Weather Assistant with Tools v1", messages="""\ You are a helpful weather assistant. The user is asking: {{user_query}}. Use the available weather tools to provide accurate information. Respond in the units most customary of the location being queried.""", tools=[ FunctionTool( # (1)! type="function", function={ "name": "get_current_weather", "description": "Get the current weather in a given location", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "The city and state/country, e.g. Berlin, DE", }, "unit": { "type": "string", "enum": ["celsius", "fahrenheit"], "description": "The temperature unit to use", }, }, "required": ["location", "unit"], "additionalProperties": False, }, "strict": True, }, ), ], tool_choice="auto", # (2)! ) print(f"Template: {'Created' if created else 'Found existing'}") # v1.0: get_or_create_collection with add_many collection, created = client.get_or_create_collection( name="Weather Query Test Data v1", ) if created: collection.add_many( variables=[ {"user_query": "What's the weather like in London, UK and should I bring an umbrella?"}, {"user_query": "Compare the current weather in Boston and San Francisco."}, ] ) print(f"Collection: {'Created' if created else 'Found existing'}") # v1.0: template.get_or_generate_criteria() - generates criteria from template # Returns (criteria_list, was_generated) - we just need to ensure criteria exist criteria, was_generated = template.get_or_generate_criteria() print(f"Criteria: {'Generated' if was_generated else 'Already existed'} ({len(criteria)} criteria)") # v1.0: run_experiment - creates and runs in one call # Criteria are already linked to the template via get_or_generate_criteria() print("\nRunning experiment...") experiment = client.run_experiment( name="Weather Tool Calling Experiment v1", prompt_template=template, collection=collection, description="Testing tool calling capabilities for weather queries", rating_mode=RatingMode.FAST, n_epochs=1, llm_config=llm_config, ) print(f"\nExperiment completed: {experiment.name}") print(f"Total responses: {len(experiment.rated_responses)}") # Display results with tool calls for i, response in enumerate(experiment.responses(), 1): print(f"\n--- Example {i} ---") print(f"Query: {response.prompt.template_variables.input_values['user_query']}") print("Tool Calls:") for message in response.messages: if message.role == "assistant" and message.tool_calls: for tool_call in message.tool_calls: print(f" - {tool_call.function.name}({tool_call.function.arguments})") # Show aggregated results if experiment.result: print("\n--- Results ---") print(f"Pass rate: {experiment.result.mean_all_ratings.yes:.2%}") # ========================================================================= # Migration Insights # ========================================================================= # # 1. TOOL DEFINITIONS # Both versions: Use OpenAI's FunctionTool format # v1.0: tools and tool_choice as keyword arguments (part of template identity) # The format is identical - FunctionTool with function dict # # 2. CRITERIA GENERATION # v0.x: client.criteria.aget_or_generate_many(template) # v1.0: template.get_or_generate_criteria() # Impact: More intuitive - criteria belong to templates conceptually # Returns (criteria_list, was_generated) tuple - criteria are linked to template # # 3. TOOL_CHOICE OPTIONS # Both versions support: # - "auto": LLM decides when to call tools # - "none": Disable tool calling # - "required": Force at least one tool call # - {"type": "function", "function": {"name": "..."}}: Force specific tool # # 4. TOOL CALL INSPECTION # Both versions: response.messages contains tool_calls # message.tool_calls is a list of ToolCall objects # Each has: id, type, function.name, function.arguments # # 5. STRICT MODE # The "strict": True in function parameters enforces schema validation # LLM must provide exactly the specified parameters, no extras # Recommended for production to ensure predictable tool calls # # 6. SDK ENHANCEMENT OPPORTUNITY # The tool call results are not automatically executed. # Consider adding a tool execution hook or callback mechanism # for automated tool calling workflows. # if __name__ == "__main__": main() ``` ## Tool Definition Methods ### OpenAI Function Tool Format Tools are defined using OpenAI's standard `FunctionTool` format: ``` from openai.types.beta import FunctionTool weather_tool = FunctionTool( type="function", function={ "name": "get_current_weather", "description": "Get the current weather in a given location", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "The city and state/country, e.g. Berlin, DE" }, "unit": { "type": "string", "enum": ["celsius", "fahrenheit"], "description": "The temperature unit to use" } }, "required": ["location", "unit"], "additionalProperties": False }, "strict": True } ) ``` ## Tool Choice Configuration The `tool_choice` parameter controls when and how the model uses available tools: - **Auto Selection** (`tool_choice="auto"`) - Let model decide when and which tools to use. - **Required Usage** (`tool_choice="required"`) - Force model to use at least one tool. - **Disabled Tools** (`tool_choice="none"`) - Disable all tools for this response. - **Specific Function** - Forces the model to call a specific tool. ``` tool_choice={ "type": "function", "function": {"name": "get_current_weather"} } ``` ## Evaluating multi-turn tool use Building a real multi-step agent? Use Agentic Evaluations For an agent that executes tools over many turns in production, [Agentic Evaluations](https://docs.elluminate.de/guides/agentic_evaluations/index.md) is the supported path: run the agent in your own runtime and upload its trajectory to get the Agent Trace, per-criterion ratings, and agentic metrics. The manual approach below predates that feature — use it when you simply want to evaluate a single tool-using conversation as one elluminate response. Evaluating a multi-turn tool conversation follows a similar process to evaluating a singular prompt. The only difference is that, instead of inferencing the prompt to get the LLM response directly, your code can perform an arbitrary number of tool calls before producing a final answer. The evaluation is then performed on the whole tool chain in addition to the final response. This section outlines the principle approach; the following section walks through a complete example. The first step is almost identical to evaluating a singular prompt. You must load elluminate with your prompt template, add or generate criteria and create a collection with representative inputs to your system. Nuanced Differences 1. **Tool Definitions as Rating Context:** In order for the rating model to have access to all of the tools and their parameter descriptions, you must add the tool definitions to the prompt template. From the SDK, this can be done via the `tools` and `tool_choice` parameters on all `create` methods. The frontend also has a special field to add tool definitions. Doing this provides valuable context to the rating model during rating about what each tool and its parameters do. 1. **Tailored Criteria for Tool Calls:** It may be be beneficial to explicitly reference tools and their parameters by name in the criteria. This helps the rating model know precisely which part of the tool call to focus on during rating. For example, "Was `get_current_weather` called with the temperature `units` most customarily used in the given `location`?" is more precise than "Are the units correct for the city?". With that in place, since elluminate cannot execute tool calls, your existing agentic code must inference the tool chain and produce the final response on its own. Then you need to manually add the whole chain of tool calls, responses as well as the final output as a singular response in to elluminate. Importantly including the whole chain of calls is needed if you want to evaluate the tool calling process as well as the final output of your agent. Adding Tool Calls as a Response Manually The SDK provides the method `client.responses.add` to manually add a response. This method accepts either a string or a list of OpenAI completion messages as the response. When you provide a list of completion messages, they all constitute the singular response. This enables the rating model to rate not only the final output, but also any intermediate tool calls and tool results. The method `client.responses.add_many` works in exactly the same manner, but is used for bulk adding responses. ## Advanced Example This advanced example demonstrates how to evaluate a multi-turn tool-using conversation by recording it as a single elluminate response. The code has access to basic filesystem tools that enable it to navigate directories, read files and analyze their metadata. It is tasked with basic questions such as "What is the largest file in the system?" and is evaluated on whether it responded correctly as well as the methods it used during the process. (For a production multi-step agent, prefer [Agentic Evaluations](https://docs.elluminate.de/guides/agentic_evaluations/index.md).) Manual Tool Execution Required elluminate does not support tool execution. Therefore, tool execution must still be handled manually in your code. You must then provide the entire chain of tool calls, tool outputs and final response back to elluminate as a manually added response. ### Tool Function Implementation This example first implements several filesystem operations as Python functions. There are functions to return the current working directory, change directory, list files among other operations. Tool Function Implementation ``` ``` 1. **Current Directory**: Returns the current working directory path 1. **Directory Navigation**: Changes the current working directory 1. **Directory Listing**: Lists all files and directories with their types 1. **File Statistics**: Retrieves detailed file metadata including size and timestamps 1. **File Reading**: Reads and returns the contents of text files ### Tool Definition Setup Tools are defined using OpenAI's `FunctionTool` format, mapping each Python function to a structured tool definition. So each of the above defined python methods gets its own `FunctionTool` defining its name, description and the arguments it accepts. Tool Definition Setup ``` ``` ### Inferencing with Tools As mentioned previously, tool execution must be handled outside of elluminate. In a for loop in the script, it inferences the prompt messages and checks whether a tool was invoked. If yes, it executes the tool and send the result back to the LLM. If no, it exits the loop with the full message conversation. Multi-Turn Conversation Handling ``` ``` 1. **OpenAI Integration**: Uses OpenAI's chat completion API with the tool definitions and tool choice set to `"auto"` mode. 1. **Tool Execution Loop**: If tools are invoked, automatically executes them and provide the results back to the model as a continuation of the message conversation. ### Experiment Setup and Evaluation Putting everything together, this workflow integrates the tool execution code with elluminate's experiment system. A prompt template with the `FunctionTool` definitions and a collection of representative user queries are created. Then criteria are manually assigned to the prompt template. Once set up, the tool execution code is run for every input in the collection. The full message conversations are saved back in to elluminate and are rated as a part of an experiment. Complete Experiment Workflow ``` ``` 1. **Prompt Template**: Defines a basic system prompt and a user prompt to be filled in with a `user_query` from a collection. 1. **Message Rendering**: In order to inference manually, the `user_query` placeholder in the prompt template needs to be filled in. This returns the fully rendered messages which can be passed directly to OpenAI's completions client. 1. **Manual Tool Execution**: Handles the complete tool calling conversation manually outside elluminate 1. **Response Recording**: Manually adds the final tool calling conversation as a response to elluminate 1. **Experiment Creation**: Create an experiment and rate the responses against the prompt template's criteria Complete Advanced Example ``` """Tool Calling with Filesystem Operations Example (v1.0 API) This example demonstrates how to test LLM tool calling capabilities using a simulated filesystem. It uses pyfakefs to create a virtual filesystem for safe testing without affecting real files. Requires: pyfakefs, openai """ import datetime import json import os import time from typing import Any from dotenv import load_dotenv from elluminate import Client from openai import AzureOpenAI, OpenAI from openai.types.beta import FunctionTool from openai.types.chat import ChatCompletionSystemMessageParam, ChatCompletionUserMessageParam from pyfakefs.fake_filesystem_unittest import Patcher load_dotenv(override=True) def get_openai_client() -> AzureOpenAI | OpenAI: if "AZURE_OPENAI_ENDPOINT" in os.environ: return AzureOpenAI( azure_endpoint=os.environ.get("AZURE_OPENAI_ENDPOINT"), api_version=os.environ.get("OPENAI_API_VERSION"), api_key=os.environ.get("AZURE_OPENAI_API_KEY"), ) else: return OpenAI() # Tool functions def pwd() -> str: return os.getcwd() def chdir(directory: str) -> None: os.chdir(directory) def list_dir() -> list[tuple[str, str]]: ret = [] items = os.listdir(".") for item in items: if os.path.isfile(item): ret.append((item, "FILE")) elif os.path.isdir(item): ret.append((item, "DIRECTORY")) return ret def file_stats(file_name: str) -> dict[str, Any]: stat_info = os.stat(file_name) return { "Size (bytes)": stat_info.st_size, "Last Modified": datetime.datetime.fromtimestamp(stat_info.st_mtime).strftime("%Y-%m-%d %H:%M:%S"), "Created At": datetime.datetime.fromtimestamp(stat_info.st_ctime).strftime("%Y-%m-%d %H:%M:%S"), } def read_file(file_name: str) -> str: with open(file_name, "r") as file: return file.read() # Tool definitions tools = [ FunctionTool( type="function", function={ "name": "pwd", "description": "Get the current working directory path", "parameters": {"type": "object", "properties": {}, "required": []}, }, ), FunctionTool( type="function", function={ "name": "chdir", "description": "Change the current working directory", "parameters": { "type": "object", "properties": {"directory": {"type": "string", "description": "The directory path to change to"}}, "required": ["directory"], }, }, ), FunctionTool( type="function", function={ "name": "list_dir", "description": "List all files and directories in the current directory", "parameters": {"type": "object", "properties": {}, "required": []}, }, ), FunctionTool( type="function", function={ "name": "file_stats", "description": "Get detailed statistics about a specific file", "parameters": { "type": "object", "properties": { "file_name": {"type": "string", "description": "The name of the file to get statistics for"} }, "required": ["file_name"], }, }, ), FunctionTool( type="function", function={ "name": "read_file", "description": "Read and return the contents of a text file", "parameters": { "type": "object", "properties": {"file_name": {"type": "string", "description": "The name of the file to read"}}, "required": ["file_name"], }, }, ), ] # Function mapping for tool execution function_map = { "pwd": pwd, "chdir": chdir, "list_dir": list_dir, "file_stats": file_stats, "read_file": read_file, } def execute_function_call(function_call) -> str: """Execute a function call and return the result""" function_name = function_call.name arguments = json.loads(function_call.arguments) try: return str(function_map[function_name](**arguments)) except Exception as e: return f"Error executing {function_name}: {str(e)}" def chat_with_tools(openai_client, messages, max_iterations=30): """Run a conversation with tool calling enabled.""" chdir("/") # Reset directory responded_messages = [] for iteration in range(1, max_iterations + 1): print(f"\n--- Iteration {iteration} ---") response = openai_client.chat.completions.create( model="gpt-4o-mini", messages=messages + responded_messages, tools=[tool.model_dump() for tool in tools], tool_choice="auto", ) assistant_message = response.choices[0].message responded_messages.append(assistant_message.model_dump()) if assistant_message.tool_calls: print("AI is calling tools...") for tool_call in assistant_message.tool_calls: print(f"Calling function: {tool_call.function.name}") print(f"Arguments: {tool_call.function.arguments}") function_result = execute_function_call(tool_call.function) print(f"Result: {function_result}") responded_messages.append( { "tool_call_id": tool_call.id, "role": "tool", "name": tool_call.function.name, "content": function_result, } ) else: print("Final response:") print(assistant_message.content) return responded_messages print("Max iterations reached!") return responded_messages def setup_filesystem(patcher): """Set up the fake filesystem with sample files""" patcher.fs.create_file( "/files/report.txt", contents="Annual sales report for 2024. Revenue increased by 15% compared to last year.", ) patcher.fs.create_file("/files/notes.txt", contents="Meeting notes from project kickoff.") patcher.fs.create_file("/files/photo1.jpg", contents="[Binary image data would be here]") patcher.fs.create_file( "/logs/app.log", contents="2024-01-15 10:30:22 - Application started\n2024-01-15 10:31:45 - User logged in\n2024-01-15 11:22:10 - Error: Connection timeout", ) time.sleep(2) patcher.fs.create_file("/files/cache.tmp", contents="Temporary cache data") time.sleep(1) patcher.fs.create_file( "/files/settings.json", contents='{"theme": "dark", "auto_save": true, "max_files": 100}' ) def main(): client = Client() llm_config = client.get_llm_config(name="GPT-4.1 Nano") openai_client = get_openai_client() with Patcher() as patcher: setup_filesystem(patcher) # Create prompt template with tools template, _ = client.get_or_create_prompt_template( name="File System Explorer with Tools", messages=[ ChatCompletionSystemMessageParam( role="system", content="You are a helpful file system assistant. Use the provided tools to explore and analyze the file system. Always start by checking the current directory and exploring the structure before answering questions. Ignore the /tmp and /var directories.", ), ChatCompletionUserMessageParam(role="user", content="{{user_query}}"), ], tools=tools, tool_choice="auto", ) # Create collection with test cases collection, _ = client.get_or_create_collection( name="File System Query Test Data", defaults={ "variables": [ {"user_query": "What is the largest file in the system?", "answer": "app.log"}, {"user_query": "Which file was created most recently?", "answer": "settings.json"}, { "user_query": "Can you find all the text files (.txt) and tell me which one has the longest content?", "answer": "report.txt", }, {"user_query": "Which files has configuration information?", "answer": "settings.json"}, ], }, ) # Create criterion set with criteria criterion_set, _ = client.get_or_create_criterion_set( name="File System Tool Calling Criteria", defaults={ "criteria": [ "Did the response say the correct file was: {{answer}}?", "Were at most 7 tool calls used?", "Was 'list_dir' called on the /files directory by calling 'chdir' in to it?", "Does the tool call trace NOT show any sign of confusion or misguidance?", ], }, ) criterion_set.link_template(template) # Create experiment experiment = client.create_experiment( name="File System Tool Calling Experiment", prompt_template=template, collection=collection, description="Testing tool calling capabilities for file system operations", llm_config=llm_config, ) # Generate responses manually using OpenAI all_responses = [] all_template_vars = [] for template_vars in collection.items(): rendered_messages = template.render_messages(user_query=template_vars.input_values["user_query"]) # Handle tool calling conversation manually response_messages = chat_with_tools(openai_client, rendered_messages) all_responses.append(response_messages) all_template_vars.append(template_vars) # Add all responses to the experiment experiment.add_responses( responses=all_responses, template_variables=all_template_vars, ) # Rate responses experiment.rate_responses() print("Finished all ratings. View the results from the frontend.") if __name__ == "__main__": main() ``` # Versioning *Learn how to manage multiple versions of your prompt templates and understand how versioning ensures reproducible experiments* elluminate's versioning system enables safe, traceable evolution of your AI evaluation components. When you modify prompt templates, template variable collections, or evaluation criteria, elluminate automatically creates new versions while preserving the original versions. This ensures reproducible experiments and enables controlled testing of improvements. ## What Gets Versioned? elluminate versions components that users create and modify: - **Prompt Templates** - Each edit creates a new version (v1, v2, v3...) with immutable content that you can select and compare - **Evaluation Criteria** - Criteria are versioned when you modify them, preserving previous versions for experiment consistency *Note: The system also tracks collection changes and rating model versions for experiment reproducibility, but these are handled automatically and don't require user management.* ## Managing Prompt Template Versions ### Creating New Versions When you edit an existing prompt template, elluminate prompts you to create a new version: 1. **Navigate to Templates** - Go to your project's Templates page 1. **Select Template** - Click on the template you want to modify 1. **Edit Content** - Make changes to messages, placeholders, or response format 1. **Version Confirmation** - elluminate displays a dialog asking if you want to create a new version 1. **Create Version** - Confirm to save your changes as the next version (e.g., v1 → v2) The version dialog explains that changes create new versions while preserving old ones, ensuring your historical experiments remain intact. ### Viewing Version History Each prompt template displays its version number prominently: - **Template Cards** - Show current version badge (e.g., "v3") - **Template Details** - Display version number in the header - **Creation Date** - Each version has its own timestamp for tracking changes over time *Template list showing multiple versions of the Support Bot template (v1, v2, v3, v4)* ### Version Selection in Components When configuring experiments or other components, you can select specific template versions: - **Experiment Creation** - Choose which version to use for new experiments - **Scheduled Experiments** - Option to always use the latest version or pin to a specific version - **Comparisons** - Compare results across different template versions ## Managing Evaluation Criteria Versions ### Automatic Criteria Versioning When you modify evaluation criteria, elluminate automatically creates new versions: - **Preservation** - Previous versions of criteria remain available for existing experiments - **Consistency** - Past experiment results always use the original criteria versions they were created with - **Evolution Tracking** - System maintains complete history of criteria changes for audit purposes While criteria versions aren't displayed with badges like templates, the same versioning principles apply to ensure experiment reproducibility. ## Version Tracking in Experiments Experiments capture complete version snapshots for full reproducibility: **At Experiment Creation:** - **Prompt Template Version** - Records exact version used (e.g., "Template v2") - **Criteria Versions** - Links to specific versions of evaluation criteria used - **System State** - Captures collection state and rating model version used **Version Consistency:** - **Immutable References** - Experiments always reference the same versions they were created with - **Historical Accuracy** - Past experiment results never change when you create new versions - **Reproduction Capability** - You can always recreate the exact conditions of any experiment ## Best Practices ### Version Management Strategy - **Descriptive Names** - Use clear template names that indicate their purpose - **Incremental Changes** - Make small, targeted improvements between versions - **Testing Before Production** - Validate new versions with smaller experiments first - **Documentation** - Keep notes on what changed between versions ### Experimental Workflow - **Baseline Establishment** - Start with a well-tested template version as your baseline - **Controlled Testing** - Test new versions against the same collection and criteria - **Performance Comparison** - Compare metrics across versions to identify improvements - **Rollback Planning** - Keep successful versions available for rollback if needed ### Template Evolution - **Incremental Changes** - Make small, targeted improvements between versions - **Testing New Versions** - Validate improvements with smaller experiments first - **Version Documentation** - Keep notes on what changed between versions - **Compatibility Planning** - Ensure new templates work with existing collections ## Version Troubleshooting ### Common Versioning Issues - **Missing Versions** - Template versions are preserved even when not actively used - **Experiment Consistency** - Past experiments always use their original template versions - **Compatibility Issues** - New template versions must work with existing collections - **Performance Changes** - Different system versions may produce different results ### Version Recovery - **Historical Access** - All versions remain accessible through the UI - **Experiment Recreation** - You can create new experiments using any historical version - **Data Export** - Version information is included in all data exports - **Audit Trail** - Version history provides complete change tracking ## SDK Integration For programmatic version management, use the elluminate SDK: ``` from elluminate import Client client = Client() # Uses ELLUMINATE_API_KEY env var # Create new template version template, created = client.prompt_templates.get_or_create( name="Customer Support Assistant", messages=[ {"role": "system", "content": "You are a helpful customer support agent..."}, {"role": "user", "content": "{{customer_query}}"} ] ) print(f"Template: {template.name} v{template.version}") # List all versions of a template all_versions = client.prompt_templates.list(name="Customer Support Assistant") for version in all_versions: print(f"Version {version.version}: Created {version.created_at}") # Use specific version in experiment experiment = client.experiments.create( name="Support Template v3 Evaluation", prompt_template=template, # Uses specific version collection=test_collection, llm_config=model_config ) ``` For complete SDK documentation, see the [API Reference](https://docs.elluminate.de/elluminate/resources/prompt_templates/index.md). ## Advanced Version Features ### Scheduled Experiment Versioning - **Latest Version Option** - Scheduled experiments can automatically use newest template versions - **Version Pinning** - Or pin to specific versions for consistent evaluation - **Change Detection** - System tracks when scheduled experiments use new versions ### Version-Based Analysis - **Cross-Version Comparison** - Compare experiment results across template versions - **Performance Tracking** - Monitor how template changes affect evaluation metrics - **Regression Detection** - Identify when new versions perform worse than previous ones Understanding versioning enables confident iteration on your AI evaluation components while maintaining the reproducibility essential for systematic improvement. # Resources # Client ## Synchronous Client Initialize the elluminate SDK client. Parameters: | Name | Type | Description | Default | | -------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `base_url` | `str` | Base URL of the elluminate API. Defaults to "https://app.elluminate.de". | `None` | | `api_key` | \`str | None\` | API key for authentication. If not provided, will look for key in environment variable given by api_key_env. | | `token` | \`str | None\` | OAuth access token for authentication. If not provided, will look for token in environment variable given by token_env. | | `project_id` | \`int | None\` | Project ID to select. | | `api_key_env` | `str` | Name of environment variable containing API key. Defaults to "ELLUMINATE_API_KEY". | `'ELLUMINATE_API_KEY'` | | `token_env` | `str` | Name of environment variable containing OAuth token. Defaults to "ELLUMINATE_OAUTH_TOKEN". | `'ELLUMINATE_OAUTH_TOKEN'` | | `base_url_env` | `str` | Name of environment variable containing base URL. Defaults to "ELLUMINATE_BASE_URL". If set, overrides base_url. | `'ELLUMINATE_BASE_URL'` | | `timeout` | `float` | Timeout in seconds for API requests. Defaults to 120.0. | `120.0` | | `proxy` | \`str | None\` | Proxy URL for HTTP/HTTPS requests (e.g., "http://proxy.example.com:8080" or "http://user:pass@proxy.example.com:8080"). If None or empty string, no proxy will be used. | | `skip_version_check` | `bool` | Skip the SDK version compatibility check. Useful for offline or restricted network environments. Defaults to False. | `False` | | `fallback_to_newest` | `bool` | Under OAuth auth, when no project_id is given, select the newest project instead of raising. Defaults to False (raise AmbiguousProjectError). Ignored for API-key auth. | `False` | | `retry_config` | \`RetryConfig | None\` | Automatic retry behavior for transient failures (timeouts, connection errors, and 429/502/503/504 responses). Defaults to a conservative RetryConfig (3 attempts with exponential backoff). Pass RetryConfig(max_attempts=1) to disable retries. | Raises: | Type | Description | | ------------ | ----------------------------------------------------------------- | | `ValueError` | If neither API key nor token is provided or found in environment. | ## check_version ``` check_version() -> None ``` Check if the SDK version is compatible with the required version. This method makes a network call to the backend to verify SDK compatibility. If the backend is unreachable, the check is silently skipped. If the SDK version is incompatible, a warning is logged with upgrade instructions. ## Asynchronous Client Initialize the elluminate SDK async client. Parameters: | Name | Type | Description | Default | | -------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `base_url` | `str` | Base URL of the elluminate API. Defaults to "https://app.elluminate.de". | `None` | | `api_key` | \`str | None\` | API key for authentication. If not provided, will look for key in environment variable given by api_key_env. | | `token` | \`str | None\` | OAuth access token for authentication. If not provided, will look for token in environment variable given by token_env. | | `project_id` | \`int | None\` | Project ID to select. | | `api_key_env` | `str` | Name of environment variable containing API key. Defaults to "ELLUMINATE_API_KEY". | `'ELLUMINATE_API_KEY'` | | `token_env` | `str` | Name of environment variable containing OAuth token. Defaults to "ELLUMINATE_OAUTH_TOKEN". | `'ELLUMINATE_OAUTH_TOKEN'` | | `base_url_env` | `str` | Name of environment variable containing base URL. Defaults to "ELLUMINATE_BASE_URL". If set, overrides base_url. | `'ELLUMINATE_BASE_URL'` | | `timeout` | `float` | Timeout in seconds for API requests. Defaults to 120.0. | `120.0` | | `proxy` | \`str | None\` | Proxy URL for HTTP/HTTPS requests (e.g., "http://proxy.example.com:8080" or "http://user:pass@proxy.example.com:8080"). If None or empty string, no proxy will be used. | | `skip_version_check` | `bool` | Skip the SDK version compatibility check. Useful for offline or restricted network environments. Defaults to False. | `False` | | `fallback_to_newest` | `bool` | Under OAuth auth, when no project_id is given, select the newest project instead of raising. Defaults to False (raise AmbiguousProjectError). Ignored for API-key auth. | `False` | | `retry_config` | \`RetryConfig | None\` | Automatic retry behavior for transient failures (timeouts, connection errors, and 429/502/503/504 responses). Defaults to a conservative RetryConfig (3 attempts with exponential backoff). Pass RetryConfig(max_attempts=1) to disable retries. | Raises: | Type | Description | | ------------ | ----------------------------------------------------------------- | | `ValueError` | If neither API key nor token is provided or found in environment. | ## check_version ``` check_version() -> None ``` Check if the SDK version is compatible with the required version. This method makes a network call to the backend to verify SDK compatibility. If the backend is unreachable, the check is silently skipped. If the SDK version is incompatible, a warning is logged with upgrade instructions. ## close ``` close() -> None ``` Close the client and release resources. This closes the underlying HTTP session. After calling close(), the client should not be used for further requests. ## CriteriaResource ``` CriteriaResource(client: Union[Client, AsyncClient]) ``` Bases: `BaseResource` ### add_many ``` add_many(criteria: List[str | CriterionIn], prompt_template: PromptTemplate, *, delete_existing: bool = False) -> List[Criterion] ``` ``` add_many(criteria: List[str | CriterionIn], *, criterion_set: CriterionSet, delete_existing: bool = False) -> List[Criterion] ``` ``` add_many(criteria: List[str | CriterionIn], prompt_template: PromptTemplate | None = None, criterion_set: CriterionSet | None = None, delete_existing: bool = False) -> List[Criterion] ``` Adds custom evaluation criteria to a prompt template or criterion set. If criteria with the same strings already exist, they will be reused rather than duplicated. There are two ways to use this method: 1. With a prompt template: add criteria to the default criterion set associated with the template 1. With a criterion set: add criteria directly to the specified criterion set Parameters: | Name | Type | Description | Default | | ----------------- | ---------------- | --------------------------------------------------------------------------------- | -------------------------------------------------------- | | `criteria` | \`list\[str | CriterionIn\]\` | List of criterion strings or CriterionIn objects to add. | | `prompt_template` | `PromptTemplate` | The prompt template to add criteria to. | `None` | | `criterion_set` | `CriterionSet` | A CriterionSet object to add criteria to directly. | `None` | | `delete_existing` | `bool` | If True, deletes any existing criteria before adding new ones. Defaults to False. | `False` | Returns: | Type | Description | | ----------------- | --------------------------------------------------------------------- | | `List[Criterion]` | list\[Criterion\]: List of created and/or existing criterion objects. | Raises: | Type | Description | | ------------ | ---------------------------------------------------------------------------------- | | `ValueError` | If both prompt_template and criterion_set are provided, or if neither is provided. | | `APIError` | If the provided objects don't belong to the project or other API errors occur. | ### aedit ``` aedit(criterion_id: int, *, criterion_str: str | None = None, label: str | None = None) -> Criterion ``` Edit an existing criterion (async). Any change to criterion_str or label creates a new version of the criterion; existing versions are immutable so historical ratings keep what they were judged under. Parameters: | Name | Type | Description | Default | | --------------- | ----- | -------------------------------- | ----------------------------------------- | | `criterion_id` | `int` | The ID of the criterion to edit. | *required* | | `criterion_str` | \`str | None\` | New criterion string. If None, unchanged. | | `label` | \`str | None\` | New label. If None, unchanged. | Returns: | Name | Type | Description | | ----------- | ----------- | ------------------------------------------- | | `Criterion` | `Criterion` | The updated (or newly versioned) criterion. | Raises: | Type | Description | | ---------- | ----------------------------------------------------- | | `APIError` | If the criterion doesn't exist or cannot be modified. | ### delete ``` delete(criterion: Criterion) -> None ``` Delete a criterion. Parameters: | Name | Type | Description | Default | | ----------- | ----------- | ------------------------ | ---------- | | `criterion` | `Criterion` | The criterion to delete. | *required* | ### edit ``` edit(criterion_id: int, *, criterion_str: str | None = None, label: str | None = None) -> Criterion ``` Edit an existing criterion. Any change to criterion_str or label creates a new version of the criterion; existing versions are immutable so historical ratings keep what they were judged under. Parameters: | Name | Type | Description | Default | | --------------- | ----- | -------------------------------- | ----------------------------------------- | | `criterion_id` | `int` | The ID of the criterion to edit. | *required* | | `criterion_str` | \`str | None\` | New criterion string. If None, unchanged. | | `label` | \`str | None\` | New label. If None, unchanged. | Returns: | Name | Type | Description | | ----------- | ----------- | ------------------------------------------- | | `Criterion` | `Criterion` | The updated (or newly versioned) criterion. | Raises: | Type | Description | | ---------- | ----------------------------------------------------- | | `APIError` | If the criterion doesn't exist or cannot be modified. | ### generate_many ``` generate_many(prompt_template: PromptTemplate, delete_existing: bool = False) -> List[Criterion] ``` Automatically generates evaluation criteria for the prompt template using an LLM. This method uses the project's default LLM to analyze the prompt template and generate appropriate evaluation criteria. The criteria will be added to a criterion set which is associated with the prompt template. Note: Unlike add_many, this method only works with prompt templates and not directly with criterion sets, as generation requires a prompt template to analyze. Parameters: | Name | Type | Description | Default | | ----------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ---------- | | `prompt_template` | `PromptTemplate` | The prompt template to generate criteria for. | *required* | | `delete_existing` | `bool` | If True, deletes any existing criteria before generating new ones. If False and criteria exist, raises an error. Defaults to False. | `False` | Returns: | Type | Description | | ----------------- | --------------------------------------------------------------------------------------------------------------------- | | `List[Criterion]` | list\[Criterion\]: List of generated criterion objects. Each criterion includes the generation metadata from the LLM. | Raises: | Type | Description | | ---------- | -------------------------------------------------------------------------------------------------------------- | | `APIError` | If criteria already exist and delete_existing is False, if the template variables are not found in the project | ### get ``` get(*, id: int) -> Criterion ``` Get a criterion by id. Parameters: | Name | Type | Description | Default | | ---- | ----- | ------------------------ | ---------- | | `id` | `int` | The id of the criterion. | *required* | Returns: | Type | Description | | ----------- | ------------------------ | | `Criterion` | The requested criterion. | ### get_or_generate_many ``` get_or_generate_many(prompt_template: PromptTemplate) -> Tuple[List[Criterion], bool] ``` Gets existing criteria or generates new ones if none exist. This method generates new criteria if none exist, otherwise it returns the existing criteria. The criteria are associated with the prompt template via a criterion set. Parameters: | Name | Type | Description | Default | | ----------------- | ---------------- | ---------------------------------------------------- | ---------- | | `prompt_template` | `PromptTemplate` | The prompt template to get or generate criteria for. | *required* | Returns: | Type | Description | | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Tuple[List[Criterion], bool]` | tuple\[list[Criterion], bool\]: A tuple containing: - List of criterion objects, either existing or newly generated - Boolean indicating if criteria were generated (True) or existing ones returned (False) | ### list ``` list(prompt_template: PromptTemplate) -> List[Criterion] ``` Get the evaluation criteria for a prompt template. This method retrieves all criteria associated with the prompt template via criterion sets. Parameters: | Name | Type | Description | Default | | ----------------- | ---------------- | ---------------------------------------- | ---------- | | `prompt_template` | `PromptTemplate` | The prompt template to get criteria for. | *required* | Returns: | Type | Description | | ----------------- | ----------------------------------------------------------------------- | | `List[Criterion]` | list\[Criterion\]: List of criterion objects, ordered by creation date. | ## CriterionSetsResource ``` CriterionSetsResource(client: Union[Client, AsyncClient]) ``` Bases: `BaseResource` ### add_prompt_template ``` add_prompt_template(criterion_set: CriterionSet, prompt_template: PromptTemplate) -> CriterionSet ``` Add a prompt template to an existing criterion set. Parameters: | Name | Type | Description | Default | | ----------------- | ---------------- | --------------------------- | ---------- | | `criterion_set` | `CriterionSet` | The criterion set. | *required* | | `prompt_template` | `PromptTemplate` | The prompt template to add. | *required* | Returns: | Name | Type | Description | | -------------- | -------------- | -------------------------- | | `CriterionSet` | `CriterionSet` | The updated criterion set. | ### aremove_prompt_template ``` aremove_prompt_template(criterion_set: CriterionSet, prompt_template: PromptTemplate) -> None ``` Remove a prompt template from a criterion set (async). ### create ``` create(name: str, criteria: List[str | CriterionIn] | None = None) -> CriterionSet ``` Create a new criterion set. Parameters: | Name | Type | Description | Default | | ---------- | ----------- | ------------------------------ | ------------------------------------------------- | | `name` | `str` | The name of the criterion set. | *required* | | `criteria` | \`list\[str | CriterionIn\]\` | List of criterion strings or CriterionIn objects. | Returns: | Name | Type | Description | | -------------- | -------------- | -------------------------- | | `CriterionSet` | `CriterionSet` | The created criterion set. | ### delete ``` delete(criterion_set: CriterionSet) -> None ``` Delete a criterion set. This will also delete all associated criteria. Parameters: | Name | Type | Description | Default | | --------------- | -------------- | ---------------------------- | ---------- | | `criterion_set` | `CriterionSet` | The criterion set to delete. | *required* | ### get ``` get(*, name: str | None = None, id: int | None = None) -> CriterionSet ``` Get a specific criterion set by name or id. Parameters: | Name | Type | Description | Default | | ------ | ----- | ----------- | ------------------------------ | | `name` | \`str | None\` | The name of the criterion set. | | `id` | \`int | None\` | The id of the criterion set. | Returns: | Name | Type | Description | | -------------- | -------------- | ---------------------------- | | `CriterionSet` | `CriterionSet` | The requested criterion set. | Raises: | Type | Description | | ------------ | ------------------------------------------------------------- | | `ValueError` | If neither or both name and id are provided, or if not found. | ### get_or_create ``` get_or_create(name: str, criteria: List[str | CriterionIn] | None = None) -> Tuple[CriterionSet, bool] ``` Get or create a criterion set. Attempts to get a criterion set first. If it doesn't exist, creates a new one. Parameters: | Name | Type | Description | Default | | ---------- | ----------- | ------------------------------ | ----------------------------------------------------------------------- | | `name` | `str` | The name of the criterion set. | *required* | | `criteria` | \`list\[str | CriterionIn\]\` | List of criterion strings or CriterionIn objects if creation is needed. | Returns: | Type | Description | | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Tuple[CriterionSet, bool]` | Tuple\[CriterionSet, bool\]: A tuple containing: - The criterion set - Boolean indicating if a new criterion set was created (True) or existing one returned (False) | ### list ``` list() -> List[CriterionSet] ``` List all criterion sets in the project. Returns: | Type | Description | | -------------------- | ---------------------------------------------------- | | `List[CriterionSet]` | list\[CriterionSet\]: List of criterion set objects. | ### remove_prompt_template ``` remove_prompt_template(criterion_set: CriterionSet, prompt_template: PromptTemplate) -> None ``` Remove a prompt template from a criterion set. Parameters: | Name | Type | Description | Default | | ----------------- | ---------------- | ------------------------------ | ---------- | | `criterion_set` | `CriterionSet` | The criterion set. | *required* | | `prompt_template` | `PromptTemplate` | The prompt template to remove. | *required* | ## ExperimentsResource ``` ExperimentsResource(client: Union[Client, AsyncClient]) ``` Bases: `BaseResource` ### arun ``` arun(experiment: Experiment, rating_mode: RatingMode = DETAILED, n_epochs: int = 1, block: bool = True, timeout: float | None = None, generation_params: GenerationParams | None = None) -> Experiment ``` Run an existing unrun experiment to generate responses and ratings (async). ### create ``` create(name: str, prompt_template: PromptTemplate | None, collection: TemplateVariablesCollection, llm_config: LLMConfig | None = None, criterion_set: CriterionSet | None = None, description: str = '', generate: bool = False, rating_mode: RatingMode = DETAILED, n_epochs: int = 1, block: bool = False, timeout: float | None = None, generation_params: GenerationParams | None = None, rating_version: str | None = None, response_column_id: int | None = None, evaluation_mode: Literal['STANDARD', 'AGENTIC', 'MULTI_TURN'] = 'STANDARD', agent_config: dict | None = None, evaluation_agent_config: dict | None = None, persona_group_id: int | None = None) -> Experiment ``` Creates a new experiment. Note: When block=True with generate=True, this method uses async streaming internally and falls back to the async implementation. Parameters: | Name | Type | Description | Default | | ------------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | `str` | The name of the experiment. | *required* | | `prompt_template` | \`PromptTemplate | None\` | Optional prompt template to use for the experiment. If omitted, the experiment must be AGENTIC or the collection must contain a Conversation or Raw Input column. | | `collection` | `TemplateVariablesCollection` | The collection of template variables to use for the experiment. | *required* | | `llm_config` | \`LLMConfig | None\` | Optional LLMConfig to use for the experiment. Uses platform default if not specified. | | `criterion_set` | \`CriterionSet | None\` | Optional criterion set to evaluate against. If omitted, falls back to the prompt template's linked criterion set (if template is provided). | | `description` | `str` | Optional description for the experiment. | `''` | | `generate` | `bool` | Whether to generate responses and ratings immediately. Defaults to False. | `False` | | `rating_mode` | `RatingMode` | The rating mode to use if generating responses (Only used if generate=True). Defaults to RatingMode.DETAILED. | `DETAILED` | | `n_epochs` | `int` | Number of times to run the experiment for each input. Defaults to 1. | `1` | | `block` | `bool` | Whether to block until the experiment is executed, only relevant if generate=True. Defaults to False. | `False` | | `timeout` | \`float | None\` | The timeout for the experiment execution, only relevant if generate=True and block=True. Defaults to None. | | `generation_params` | \`GenerationParams | None\` | Optional sampling parameters to override LLMConfig defaults for this experiment. Defaults to None (uses LLMConfig defaults). | | `rating_version` | \`str | None\` | Version of core rating to use. If not provided, uses project's default_rating_version. Use "mock" in test environments to avoid actual LLM calls for ratings. | | `response_column_id` | \`int | None\` | Optional ID of a collection column to use as pre-existing responses. When set, no LLM generation occurs; responses are taken directly from this column. llm_config and generation_params are ignored, and n_epochs is forced to 1. | | `evaluation_mode` | `Literal['STANDARD', 'AGENTIC', 'MULTI_TURN']` | The evaluation mode ("STANDARD", "AGENTIC", or "MULTI_TURN"). Defaults to "STANDARD". | `'STANDARD'` | | `agent_config` | \`dict | None\` | Optional agent configuration for agentic experiments. | | `evaluation_agent_config` | \`dict | None\` | Optional evaluation agent configuration for agentic experiments. | | `persona_group_id` | \`int | None\` | ID of the persona group to simulate. Required for MULTI_TURN experiments (the conversations are simulated by the personas of that group) and not allowed for other evaluation modes. Defaults to None. | Returns: | Name | Type | Description | | ------------ | ------------ | ----------------------------------------------------------------------------- | | `Experiment` | `Experiment` | The newly created experiment object. If generate=True, | | | `Experiment` | responses and ratings will be generated. The returned experiment object will | | | `Experiment` | then include a generation task ID that can be used to check the status of the | | | `Experiment` | generation. | Raises: | Type | Description | | --------------- | --------------------------------------------------- | | `ConflictError` | If the experiment with the same name already exists | ### delete ``` delete(experiment: Experiment) -> None ``` Deletes an experiment. Parameters: | Name | Type | Description | Default | | ------------ | ------------ | ------------------------- | ---------- | | `experiment` | `Experiment` | The experiment to delete. | *required* | Raises: | Type | Description | | --------------- | ------------------------------------------------------------------ | | `NotFoundError` | If the experiment doesn't exist or belongs to a different project. | ### get ``` get(*, name: str | None = None, id: int | None = None, fetch_responses: bool = True, categorical_filters: dict[str, list[str]] | None = None) -> Experiment ``` Get an experiment by name or id. Parameters: | Name | Type | Description | Default | | --------------------- | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | \`str | None\` | The name of the experiment to get. | | `id` | \`int | None\` | The id of the experiment to get. | | `fetch_responses` | `bool` | Whether to fetch responses for the experiment. Defaults to True for backward compatibility. Set to False to save API calls when responses aren't needed. | `True` | | `categorical_filters` | \`dict\[str, list[str]\] | None\` | Filter experiment results by categorical column values. Maps column names to lists of values. Multiple values for the same column use OR logic; multiple columns use AND logic. Example: {"category": ["A", "B"], "region": ["US"]} filters for (category=A OR category=B) AND (region=US). | Returns: | Name | Type | Description | | ------------ | ------------ | ---------------------- | | `Experiment` | `Experiment` | The experiment object. | Raises: | Type | Description | | ------------ | ------------------------------------------------------------- | | `ValueError` | If neither or both name and id are provided, or if not found. | ### get_map ``` get_map(experiment: Experiment | int, *, page: int = 1, limit: int = 50) -> ExperimentMap ``` Get a lightweight map of an experiment's samples and responses. The map provides a navigable overview of which samples and responses exist, along with metadata (task name, step/cost counts, criteria pass/fail), without the full message or trajectory payload. Use it to decide which responses to fetch in detail via `client._responses.get(id=...)`. Parameters: | Name | Type | Description | Default | | ------------ | ------------ | ----------------------------------------------- | ---------------------------------------------- | | `experiment` | \`Experiment | int\` | The experiment (or its id) to get the map for. | | `page` | `int` | The page of samples to fetch. Defaults to 1. | `1` | | `limit` | `int` | The number of samples per page. Defaults to 50. | `50` | Returns: | Name | Type | Description | | --------------- | --------------- | ------------------------------------------------------ | | `ExperimentMap` | `ExperimentMap` | The lightweight experiment map for the requested page. | ### get_or_create ``` get_or_create(name: str, prompt_template: PromptTemplate | None, collection: TemplateVariablesCollection, llm_config: LLMConfig | None = None, criterion_set: CriterionSet | None = None, description: str = '', generate: bool = False, rating_mode: RatingMode = DETAILED, n_epochs: int = 1, block: bool = False, timeout: float | None = None, generation_params: GenerationParams | None = None, rating_version: str | None = None, response_column_id: int | None = None, evaluation_mode: Literal['STANDARD', 'AGENTIC', 'MULTI_TURN'] = 'STANDARD', agent_config: dict | None = None, evaluation_agent_config: dict | None = None, persona_group_id: int | None = None) -> tuple[Experiment, bool] ``` Gets an existing experiment by name or creates a new one if it doesn't exist. The existence of an experiment is determined solely by its name. If an experiment with the given name exists, it will be returned regardless of its other properties. If no experiment exists with that name, a new one will be created with the provided parameters. Parameters: | Name | Type | Description | Default | | ------------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `name` | `str` | The name of the experiment to get or create. | *required* | | `prompt_template` | \`PromptTemplate | None\` | Optional prompt template to use if creating a new experiment. If omitted, the collection must contain a Conversation or Raw Input column. | | `collection` | `TemplateVariablesCollection` | The collection of template variables to use if creating a new experiment. | *required* | | `llm_config` | \`LLMConfig | None\` | Optional LLMConfig to use if creating a new experiment. | | `criterion_set` | \`CriterionSet | None\` | Optional criterion set to use if creating a new experiment. If omitted, falls back to the prompt template's linked criterion set (if template is provided). | | `description` | `str` | Optional description if creating a new experiment. | `''` | | `generate` | `bool` | Whether to generate responses and ratings immediately. Defaults to False. | `False` | | `rating_mode` | `RatingMode` | The rating mode to use if generating responses. Defaults to RatingMode.DETAILED. | `DETAILED` | | `n_epochs` | `int` | Number of times to run the experiment for each input. Defaults to 1. | `1` | | `block` | `bool` | Whether to block until the experiment is executed when creating a new experiment, only relevant if generate=True. Defaults to False. | `False` | | `timeout` | \`float | None\` | The timeout for the experiment execution when creating a new experiment, only relevant if generate=True and block=True. Defaults to None. | | `generation_params` | \`GenerationParams | None\` | Optional sampling parameters to override LLMConfig defaults for this experiment. Defaults to None (uses LLMConfig defaults). | | `rating_version` | \`str | None\` | Version of core rating to use. If not provided, uses project's default_rating_version. Use "mock" in test environments to avoid actual LLM calls for ratings. | | `response_column_id` | \`int | None\` | Optional ID of a collection column to use as pre-existing responses. When set, no LLM generation occurs; responses are taken directly from this column. | | `evaluation_mode` | `Literal['STANDARD', 'AGENTIC', 'MULTI_TURN']` | The evaluation mode ("STANDARD", "AGENTIC", or "MULTI_TURN"). Defaults to "STANDARD". | `'STANDARD'` | | `agent_config` | \`dict | None\` | Optional agent configuration for agentic experiments. | | `evaluation_agent_config` | \`dict | None\` | Optional evaluation agent configuration for agentic experiments. | | `persona_group_id` | \`int | None\` | ID of the persona group to simulate. Required for MULTI_TURN experiments (the conversations are simulated by the personas of that group) and not allowed for other evaluation modes. Defaults to None. | Returns: | Type | Description | | ------------------------- | ----------------- | | `tuple[Experiment, bool]` | tuple\[Experiment | ### list ``` list(prompt_template: PromptTemplate | None = None, collection: TemplateVariablesCollection | None = None, llm_config: LLMConfig | None = None) -> list[Experiment] ``` Get a list of experiments sorted by creation date. Parameters: | Name | Type | Description | Default | | ----------------- | ----------------------------- | ----------- | --------------------------------- | | `prompt_template` | \`PromptTemplate | None\` | The prompt template to filter by. | | `collection` | \`TemplateVariablesCollection | None\` | The collection to filter by. | | `llm_config` | \`LLMConfig | None\` | The LLM config to filter by. | Returns: | Type | Description | | ------------------ | ------------------------------------------ | | `list[Experiment]` | list\[Experiment\]: A list of experiments. | ### run ``` run(experiment: Experiment, rating_mode: RatingMode = DETAILED, n_epochs: int = 1, block: bool = True, timeout: float | None = None, generation_params: GenerationParams | None = None) -> Experiment ``` Run an existing unrun experiment to generate responses and ratings. This method triggers generation for an experiment that was created without running it (i.e., using client.create_experiment() without generate=True). Parameters: | Name | Type | Description | Default | | ------------------- | ------------------ | ---------------------------------------------------------------- | ------------------------------------------------------------ | | `experiment` | `Experiment` | The experiment to run. | *required* | | `rating_mode` | `RatingMode` | The rating mode to use (FAST or DETAILED). Defaults to DETAILED. | `DETAILED` | | `n_epochs` | `int` | Number of times to run for each input. Defaults to 1. | `1` | | `block` | `bool` | Whether to block until generation completes. Defaults to True. | `True` | | `timeout` | \`float | None\` | Optional timeout in seconds. Only relevant if block=True. | | `generation_params` | \`GenerationParams | None\` | Optional sampling parameters to override LLMConfig defaults. | Returns: | Type | Description | | ------------ | ------------------------------------------------------------------------- | | `Experiment` | The experiment with generation_task_id set. If block=True, the experiment | | `Experiment` | will include the generated responses and ratings. | Raises: | Type | Description | | ---------- | -------------------------------------------------------- | | `APIError` | If the experiment has already been run or doesn't exist. | ## LLMConfigsResource ``` LLMConfigsResource(client: Union[Client, AsyncClient]) ``` Bases: `BaseResource` ### create ``` create(name: str, llm_model_name: str, api_key: str, description: str = '', llm_base_url: str | None = None, api_version: str | None = None, max_connections: int = 10, max_retries: int | None = None, timeout: int | None = None, system_message: str | None = None, max_tokens: int | None = None, top_p: float | None = None, temperature: float | None = None, best_of: int | None = None, top_k: int | None = None, logprobs: bool | None = None, top_logprobs: int | None = None, reasoning_effort: str | None = None, verbosity: str | None = None, inference_type: InferenceType = OPENAI, custom_api_config: dict[str, Any] | None = None, custom_response_parser: str | None = None, input_price_per_mtok: float | None = None, output_price_per_mtok: float | None = None) -> LLMConfig ``` Create a new LLM configuration. Parameters: | Name | Type | Description | Default | | ------------------------ | --------------- | ------------------------------------------------------------- | --------------------------------------------------------- | | `name` | `str` | Name for the LLM config. | *required* | | `llm_model_name` | `str` | Name of the LLM model. | *required* | | `api_key` | `str` | API key for the LLM service. | *required* | | `description` | `str` | Optional description for the LLM config. | `''` | | `llm_base_url` | \`str | None\` | Optional base URL for the LLM service. | | `api_version` | \`str | None\` | Optional API version. | | `max_connections` | `int` | Maximum number of concurrent connections to the LLM provider. | `10` | | `max_retries` | \`int | None\` | Optional maximum number of retries. | | `timeout` | \`int | None\` | Optional timeout in seconds. | | `system_message` | \`str | None\` | Optional system message for the LLM. | | `max_tokens` | \`int | None\` | Optional maximum tokens to generate. | | `top_p` | \`float | None\` | Optional nucleus sampling parameter. | | `temperature` | \`float | None\` | Optional temperature parameter. | | `best_of` | \`int | None\` | Optional number of completions to generate. | | `top_k` | \`int | None\` | Optional top-k sampling parameter. | | `logprobs` | \`bool | None\` | Optional flag to return log probabilities. | | `top_logprobs` | \`int | None\` | Optional number of top log probabilities to return. | | `reasoning_effort` | \`str | None\` | Optional reasoning effort parameter for o-series models. | | `verbosity` | \`str | None\` | Optional verbosity parameter for GPT-5 and newer models. | | `inference_type` | `InferenceType` | Type of Inference Provider to use. | `OPENAI` | | `custom_api_config` | \`dict | None\` | Optional configuration template for custom API providers. | | `custom_response_parser` | \`str | None\` | Optional Python code to parse custom API responses. | | `input_price_per_mtok` | \`float | None\` | Optional input price per million tokens. | | `output_price_per_mtok` | \`float | None\` | Optional output price per million tokens. | Returns: | Type | Description | | ----------- | ------------------------------ | | `LLMConfig` | The created LLM configuration. | Raises: | Type | Description | | --------------- | --------------------------------------------------- | | `ConflictError` | If an LLM config with the same name already exists. | ### delete ``` delete(llm_config: LLMConfig) -> None ``` Deletes an LLM configuration. Parameters: | Name | Type | Description | Default | | ------------ | ----------- | -------------------------------- | ---------- | | `llm_config` | `LLMConfig` | The LLM configuration to delete. | *required* | Raises: | Type | Description | | --------------- | ------------------------------------------------------------------ | | `NotFoundError` | If the LLM config doesn't exist or belongs to a different project. | ### get ``` get(*, name: str | None = None, id: int | None = None) -> LLMConfig ``` Get an LLM config by name or id. Parameters: | Name | Type | Description | Default | | ------ | ----- | ----------- | ----------------------- | | `name` | \`str | None\` | Name of the LLM config. | | `id` | \`int | None\` | ID of the LLM config. | Returns: | Type | Description | | ----------- | ------------------------- | | `LLMConfig` | The requested LLM config. | Raises: | Type | Description | | ------------ | ------------------------------------------------------------- | | `ValueError` | If neither or both name and id are provided, or if not found. | ### get_or_create ``` get_or_create(name: str, llm_model_name: str | None = None, api_key: str | None = None, description: str = '', llm_base_url: str | None = None, api_version: str | None = None, max_connections: int = 10, max_retries: int | None = None, timeout: int | None = None, system_message: str | None = None, max_tokens: int | None = None, top_p: float | None = None, temperature: float | None = None, best_of: int | None = None, top_k: int | None = None, logprobs: bool | None = None, top_logprobs: int | None = None, reasoning_effort: str | None = None, verbosity: str | None = None, inference_type: InferenceType = OPENAI, custom_api_config: dict[str, Any] | None = None, custom_response_parser: str | None = None, input_price_per_mtok: float | None = None, output_price_per_mtok: float | None = None) -> tuple[LLMConfig, bool] ``` Get an existing LLM config or create a new one. First attempts to get the config by name. If it doesn't exist, creates a new one using the provided parameters. Parameters: | Name | Type | Description | Default | | ------------------------ | --------------- | ------------------------------------------------------------- | --------------------------------------------------------- | | `name` | `str` | Name for the LLM config (lookup key). | *required* | | `llm_model_name` | \`str | None\` | Name of the LLM model. Required only if creating. | | `api_key` | \`str | None\` | API key for the LLM service. Required only if creating. | | `description` | `str` | Optional description for the LLM config. | `''` | | `llm_base_url` | \`str | None\` | Optional base URL for the LLM service. | | `api_version` | \`str | None\` | Optional API version. | | `max_connections` | `int` | Maximum number of concurrent connections to the LLM provider. | `10` | | `max_retries` | \`int | None\` | Optional maximum number of retries. | | `timeout` | \`int | None\` | Optional timeout in seconds. | | `system_message` | \`str | None\` | Optional system message for the LLM. | | `max_tokens` | \`int | None\` | Optional maximum tokens to generate. | | `top_p` | \`float | None\` | Optional nucleus sampling parameter. | | `temperature` | \`float | None\` | Optional temperature parameter. | | `best_of` | \`int | None\` | Optional number of completions to generate. | | `top_k` | \`int | None\` | Optional top-k sampling parameter. | | `logprobs` | \`bool | None\` | Optional flag to return log probabilities. | | `top_logprobs` | \`int | None\` | Optional number of top log probabilities to return. | | `reasoning_effort` | \`str | None\` | Optional reasoning effort parameter for o-series models. | | `verbosity` | \`str | None\` | Optional verbosity parameter for GPT-5 and newer models. | | `inference_type` | `InferenceType` | Type of Inference Provider to use. | `OPENAI` | | `custom_api_config` | \`dict | None\` | Optional configuration template for custom API providers. | | `custom_response_parser` | \`str | None\` | Optional Python code to parse custom API responses. | | `input_price_per_mtok` | \`float | None\` | Optional input price per million tokens. | | `output_price_per_mtok` | \`float | None\` | Optional output price per million tokens. | Returns: | Type | Description | | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `tuple[LLMConfig, bool]` | tuple\[LLMConfig, bool\]: A tuple containing: - The LLM configuration - Boolean indicating if a new config was created (True) or existing one returned (False) | Raises: | Type | Description | | ------------ | ----------------------------------------------------------------------- | | `ValueError` | If config doesn't exist and llm_model_name or api_key are not provided. | ### list ``` list() -> list[LLMConfig] ``` List all LLM configurations. Returns: | Type | Description | | ----------------- | ----------------------------------------------------------------- | | `list[LLMConfig]` | list\[LLMConfig\]: List of all LLM configurations in the project. | ## ResponsesResource ``` ResponsesResource(client: Union[Client, AsyncClient]) ``` Bases: `BaseResource` ### add ``` add(response: str | List[ChatCompletionMessageParam], template_variables: TemplateVariables, experiment: Experiment, epoch: int = 1, metadata: LLMConfig | GenerationMetadata | None = None) -> PromptResponse ``` Add a response to an experiment. Parameters: | Name | Type | Description | Default | | -------------------- | ------------------- | ---------------------------------------------------------------- | -------------------- | | `response` | \`str | List[ChatCompletionMessageParam]\` | The response to add. | | `template_variables` | `TemplateVariables` | The template variables to use for the response. | *required* | | `experiment` | `Experiment` | The experiment this response belongs to. | *required* | | `epoch` | `int` | The epoch for the response within the experiment. Defaults to 1. | `1` | | `metadata` | \`LLMConfig | GenerationMetadata | None\` | Returns: | Name | Type | Description | | ---------------- | ---------------- | ----------------------------------------- | | `PromptResponse` | `PromptResponse` | The newly created prompt response object. | ### add_many ``` add_many(responses: List[str | List[ChatCompletionMessageParam]], template_variables: List[TemplateVariables], experiment: Experiment, epoch: int = 1, metadata: List[LLMConfig | GenerationMetadata | None] | None = None, timeout: float | None = None, polling_interval: float = 3.0) -> List[PromptResponse] ``` Add multiple responses to an experiment in bulk. Use this method when you have a list of responses to add, instead of adding them one by one with the add() method. Parameters: | Name | Type | Description | Default | | -------------------- | ------------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------ | | `responses` | \`list\[str | List[ChatCompletionMessageParam]\]\` | List of responses to add. | | `template_variables` | `list[TemplateVariables]` | List of template variables for each response. | *required* | | `experiment` | `Experiment` | The experiment these responses belong to. | *required* | | `epoch` | `int` | The epoch for the responses within the experiment. Defaults to 1. | `1` | | `metadata` | \`list\[LLMConfig | GenerationMetadata | None\] | | `timeout` | \`float | None\` | Timeout in seconds for API requests. Defaults to no timeout. | | `polling_interval` | `float` | Time between status checks in seconds. Defaults to 3.0. | `3.0` | Returns: | Type | Description | | ---------------------- | ---------------------------------------------------------------------- | | `List[PromptResponse]` | list\[PromptResponse\]: List of newly created prompt response objects. | ### aupdate_annotation ``` aupdate_annotation(prompt_response: PromptResponse | int, annotation: str) -> PromptResponse ``` Update the annotation for a prompt response (async). ### delete ``` delete(prompt_response: PromptResponse) -> None ``` Delete a prompt response. Parameters: | Name | Type | Description | Default | | ----------------- | ---------------- | ------------------------------ | ---------- | | `prompt_response` | `PromptResponse` | The prompt response to delete. | *required* | ### generate ``` generate(template_variables: TemplateVariables, experiment: Experiment, llm_config: LLMConfig | None = None) -> PromptResponse ``` Generate a response using an LLM. This method sends the prompt to an LLM for generation. If no LLM config is provided, the project's default LLM config will be used. Parameters: | Name | Type | Description | Default | | -------------------- | ------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | | `template_variables` | `TemplateVariables` | The template variables to use for the response. | *required* | | `experiment` | `Experiment` | The experiment this response belongs to. | *required* | | `llm_config` | \`LLMConfig | None\` | Optional LLM configuration to use for generation. If not provided, the project's default config will be used. | Returns: | Name | Type | Description | | ---------------- | ---------------- | ----------------------------- | | `PromptResponse` | `PromptResponse` | The generated response object | ### generate_many ``` generate_many(experiment: Experiment, *, template_variables: List[TemplateVariables], llm_config: LLMConfig | None = None, timeout: float | None = None, polling_interval: float = 3.0) -> List[PromptResponse] ``` ``` generate_many(experiment: Experiment, *, collection: TemplateVariablesCollection, llm_config: LLMConfig | None = None, timeout: float | None = None, polling_interval: float = 3.0) -> List[PromptResponse] ``` ``` generate_many(experiment: Experiment, *, template_variables: List[TemplateVariables] | None = None, collection: TemplateVariablesCollection | None = None, llm_config: LLMConfig | None = None, timeout: float | None = None, polling_interval: float = 3.0) -> List[PromptResponse] ``` Generate multiple responses for an experiment. Use this method when you have a list of responses to generate, instead of generating them one by one with the generate() method. Either `template_variables` or `collection` can be provided: - If `template_variables` is given, it will use the provided list of template variables for each response. - If `collection` is given, it will use the template variables from the specified collection. Parameters: | Name | Type | Description | Default | | -------------------- | ----------------------------- | ------------------------------------------------------------ | ------------------------------------------------- | | `experiment` | `Experiment` | The experiment these responses belong to. | *required* | | `template_variables` | \`list[TemplateVariables] | None\` | List of template variables for each response. | | `collection` | \`TemplateVariablesCollection | None\` | The collection to use for the template variables. | | `llm_config` | \`LLMConfig | None\` | Optional LLMConfig to use for generation. | | `timeout` | `float` | Timeout in seconds for API requests. Defaults to no timeout. | `None` | | `polling_interval` | `float` | Time between status checks in seconds. Defaults to 3.0. | `3.0` | Returns: | Type | Description | | ---------------------- | ---------------------------------------------------------------------- | | `List[PromptResponse]` | list\[PromptResponse\]: List of newly created prompt response objects. | ### get ``` get(*, id: int) -> PromptResponse ``` Get a single prompt response by its id. The response is scoped to the client's active project; a response that does not belong to the active project raises a NotFoundError. Parameters: | Name | Type | Description | Default | | ---- | ----- | ------------------------------------- | ---------- | | `id` | `int` | The id of the prompt response to get. | *required* | Returns: | Name | Type | Description | | ---------------- | ---------------- | ------------------------------------------------------------ | | `PromptResponse` | `PromptResponse` | The full prompt response, including its trajectory (if any). | Raises: | Type | Description | | --------------- | -------------------------------------------------------------- | | `NotFoundError` | If no response with the given id exists in the active project. | ### get_stats ``` get_stats(llm_config: LLMConfig | None = None, days: int = 30) -> ResponsesStats ``` Get usage statistics for responses in a project with optional LLM config filtering. Parameters: | Name | Type | Description | Default | | ------------ | ----------- | ------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------- | | `llm_config` | \`LLMConfig | None\` | The LLM config to get stats of. If not provided, the project's default LLM config will be used. | | `days` | `int` | The number of days to get stats for. Defaults to 30. Must be between 1 and 90. | `30` | Returns: | Name | Type | Description | | ---------------- | ---------------- | ---------------------------- | | `ResponsesStats` | `ResponsesStats` | The stats of the LLM config. | ### list ``` list(prompt_template: PromptTemplate | None = None, template_variables: TemplateVariables | None = None, experiment: Experiment | None = None, collection: TemplateVariablesCollection | None = None, filters: PromptResponseFilter | None = None) -> list[PromptResponse] ``` Returns the responses belonging to a prompt template, template variables, experiment, or collection. Parameters: | Name | Type | Description | Default | | -------------------- | ----------------------------- | ----------- | -------------------------------------------- | | `prompt_template` | \`PromptTemplate | None\` | The prompt template to get responses for. | | `template_variables` | \`TemplateVariables | None\` | The template variables to get responses for. | | `experiment` | \`Experiment | None\` | The experiment to get responses for. | | `collection` | \`TemplateVariablesCollection | None\` | The collection to get responses for. | | `filters` | \`PromptResponseFilter | None\` | The filters to apply to the responses. | Returns: | Type | Description | | ---------------------- | ----------------------------------------------------- | | `list[PromptResponse]` | list\[PromptResponse\]: The list of prompt responses. | ### list_comparison_samples ``` list_comparison_samples(experiment_a: Experiment, experiment_b: Experiment, filter_samples_by: Literal['improved', 'regressed'] | None = None, criterion_ids: list[int] | None = None, categorical_filters: dict[str, list[str]] | None = None) -> List[ResponsesSample] ``` List comparison samples between two experiments. This returns representative response samples from both experiments grouped by template variables, enabling side-by-side comparison of results. Parameters: | Name | Type | Description | Default | | --------------------- | ------------------------ | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `experiment_a` | `Experiment` | The first experiment (baseline). | *required* | | `experiment_b` | `Experiment` | The second experiment to compare against baseline. | *required* | | `filter_samples_by` | \`str | None\` | Filter samples by performance difference. - "improved": Only show samples where B performs better than A - "regressed": Only show samples where B performs worse than A | | `criterion_ids` | \`list[int] | None\` | Optional list of criterion IDs to consider when computing scores. If not provided, all criteria are used. | | `categorical_filters` | \`dict\[str, list[str]\] | None\` | Filter by categorical column values. Maps column names to lists of values. Multiple values for the same column use OR logic; multiple columns use AND logic. Example: {"category": ["A", "B"], "region": ["US"]} filters for (category=A OR category=B) AND (region=US). | Returns: | Type | Description | | ----------------------- | ---------------------------------------------------------------------- | | `List[ResponsesSample]` | List\[ResponsesSample\]: Samples from both experiments for comparison. | ### list_samples ``` list_samples(experiment: Experiment, exclude_perfect_responses: bool = False, show_only_annotated_responses: bool = False, filters: ResponsesSampleFilter | None = None, sort_by: ResponsesSampleSortBy | None = None) -> List[ResponsesSample] ``` List samples for an experiment. Parameters: | Name | Type | Description | Default | | ------------------------------- | ----------------------- | ----------------------------------------- | ------------------------------------ | | `experiment` | `Experiment` | The experiment to get samples for. | *required* | | `exclude_perfect_responses` | `bool` | Whether to exclude perfect responses. | `False` | | `show_only_annotated_responses` | `bool` | Whether to show only annotated responses. | `False` | | `filters` | \`ResponsesSampleFilter | None\` | The filters to apply to the samples. | | `sort_by` | \`ResponsesSampleSortBy | None\` | The sort order for the samples. | Returns: | Type | Description | | ----------------------- | --------------------------------------------- | | `List[ResponsesSample]` | List\[ResponsesSample\]: The list of samples. | ### update_annotation ``` update_annotation(prompt_response: PromptResponse | int, annotation: str) -> PromptResponse ``` Update the annotation of a prompt response. Annotations are useful for categorizing, labeling, or adding notes to responses, especially when reviewing failed responses or building golden answer sets. Parameters: | Name | Type | Description | Default | | ----------------- | ---------------- | -------------------------------------------------- | ------------------------------------ | | `prompt_response` | \`PromptResponse | int\` | The prompt response object or its ID | | `annotation` | `str` | The annotation text to set (empty string to clear) | *required* | Returns: | Name | Type | Description | | ---------------- | ---------------- | --------------------------- | | `PromptResponse` | `PromptResponse` | The updated prompt response | Example ``` # Annotate a failed response response = client.responses.update_annotation( response_id=123, annotation="Failed due to incorrect entity extraction" ) # Clear an annotation response = client.responses.update_annotation(response, "") ``` ## PromptTemplatesResource ``` PromptTemplatesResource(client: Union[Client, AsyncClient]) ``` Bases: `BaseResource` ### create ``` create(messages: str | List[ChatCompletionMessageParam], name: str, parent_prompt_template: PromptTemplate | None = None, response_format: Type[BaseModel] | Dict[str, Any] | None = None, tools: List[FunctionTool] | None = None, tool_choice: AssistantToolChoiceOption | None = None) -> PromptTemplate ``` Create a new prompt template. Parameters: | Name | Type | Description | Default | | ------------------------ | --------------------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | | `messages` | \`str | List[ChatCompletionMessageParam]\` | The template string with {{placeholders}}, or a list of ChatCompletionMessageParam dicts for multi-turn conversations. | | `name` | `str` | Name for the template. | *required* | | `parent_prompt_template` | \`PromptTemplate | None\` | Optional parent template to inherit from. | | `response_format` | \`Type[BaseModel] | Dict[str, Any] | None\` | | `tools` | \`List[FunctionTool] | None\` | Optional list of tools available to the model. | | `tool_choice` | \`AssistantToolChoiceOption | None\` | Optional tool choice setting. | ### delete ``` delete(prompt_template: PromptTemplate) -> None ``` Deletes a prompt template. Parameters: | Name | Type | Description | Default | | ----------------- | ---------------- | ------------------------------ | ---------- | | `prompt_template` | `PromptTemplate` | The prompt template to delete. | *required* | Raises: | Type | Description | | --------------- | ----------------------------------------------------------------------- | | `NotFoundError` | If the prompt template doesn't exist or belongs to a different project. | ### get ``` get(*, name: str | None = None, id: int | None = None, version: int | Literal['latest'] = 'latest') -> PromptTemplate ``` Get a prompt template by name and version, or by id. Parameters: | Name | Type | Description | Default | | --------- | ----- | ------------------- | ------------------------------------------------------------------------------------------------------------------- | | `name` | \`str | None\` | Name of the prompt template. | | `id` | \`int | None\` | ID of the prompt template. | | `version` | \`int | Literal['latest']\` | Version number or "latest". Defaults to "latest". Only used when looking up by name; ignored when looking up by id. | Returns: | Type | Description | | ---------------- | ------------------------------ | | `PromptTemplate` | The requested prompt template. | Raises: | Type | Description | | ------------ | ---------------------------------------------------- | | `ValueError` | If neither or both name and id are provided. | | `ValueError` | If no template is found with given name and version. | ### get_or_create ``` get_or_create(messages: str | List[ChatCompletionMessageParam], name: str, parent_prompt_template: PromptTemplate | None = None, response_format: Type[BaseModel] | None = None, tools: List[FunctionTool] | None = None, tool_choice: AssistantToolChoiceOption | None = None) -> tuple[PromptTemplate, bool] ``` Gets the prompt template by its name and messages content if it exists. If the prompt template name does not exist, it creates a new prompt template with version 1. If a prompt template with the same name exists, but the messages content is new, then it creates a new prompt template version with the new messages which will be the new latest version. When a prompt template with the same name and messages already exists, it returns the existing prompt template, ignoring the given parent_prompt_template. Parameters: | Name | Type | Description | Default | | ------------------------ | --------------------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | | `messages` | \`str | List[ChatCompletionMessageParam]\` | The template string with {{placeholders}}, or a list of ChatCompletionMessageParam dicts for multi-turn conversations. | | `name` | `str` | Name for the template. | *required* | | `parent_prompt_template` | \`PromptTemplate | None\` | Optional parent template to inherit from. | | `response_format` | \`Type[BaseModel] | None\` | Optional Pydantic model for structured output generation. | | `tools` | \`List[FunctionTool] | None\` | Optional list of tools available to the model. | | `tool_choice` | \`AssistantToolChoiceOption | None\` | Optional tool choice setting. | Returns: | Type | Description | | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `tuple[PromptTemplate, bool]` | tuple\[PromptTemplate, bool\]: A tuple containing: - The prompt template - Boolean indicating if a new template was created (True) or existing one returned (False) | Raises: | Type | Description | | ------------ | ----------------------------------------------------------- | | `ValueError` | If a 409 response is received without a prompt_template_id. | ### list ``` list(name: str | None = None, criterion_set: CriterionSet | None = None, compatible_collection: TemplateVariablesCollection | None = None) -> list[PromptTemplate] ``` Get a list of prompt templates. Parameters: | Name | Type | Description | Default | | ----------------------- | ----------------------------- | ----------- | ------------------------------------------------------ | | `name` | \`str | None\` | Name of the prompt template to filter by. | | `criterion_set` | \`CriterionSet | None\` | Criterion set to filter by. | | `compatible_collection` | \`TemplateVariablesCollection | None\` | Compatible template variables collection to filter by. | Returns: | Type | Description | | ---------------------- | --------------------------------------------------- | | `list[PromptTemplate]` | list\[PromptTemplate\]: A list of prompt templates. | ## RatingsResource ``` RatingsResource(client: Union[Client, AsyncClient]) ``` Bases: `BaseResource` ### list ``` list(prompt_response: PromptResponse) -> List[Rating] ``` Gets the ratings for a prompt response. Parameters: | Name | Type | Description | Default | | ----------------- | ---------------- | --------------------------------------- | ---------- | | `prompt_response` | `PromptResponse` | The prompt response to get ratings for. | *required* | Returns: | Type | Description | | -------------- | --------------------------------------------------------------- | | `List[Rating]` | list\[Rating\]: List of rating objects for the prompt response. | Raises: | Type | Description | | --------------- | ----------------------------------------------------------------------- | | `NotFoundError` | If the prompt response doesn't exist or belongs to a different project. | ### rate ``` rate(prompt_response: PromptResponse, rating_mode: RatingMode = DETAILED) -> List[Rating] ``` Rates a response against its prompt template's criteria using an LLM. This method evaluates a prompt response against all applicable criteria associated with its prompt template. If template variables were used for the response, it will consider both general criteria and criteria specific to those variables. Parameters: | Name | Type | Description | Default | | ----------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | | `prompt_response` | `PromptResponse` | The response to rate. | *required* | | `rating_mode` | `RatingMode` | Judge reasoning effort: - FAST: Quicker evaluation, less judge reasoning effort - DETAILED: More judge reasoning effort Both return explanations. Some rating versions pin their effort and ignore this. | `DETAILED` | Returns: | Type | Description | | -------------- | ---------------------------------------------------------- | | `List[Rating]` | list\[Rating\]: List of rating objects, one per criterion. | Raises: | Type | Description | | ---------- | -------------------------------------------- | | `APIError` | If no criteria exist for the prompt template | ### rate_many ``` rate_many(prompt_responses: List[PromptResponse], rating_mode: RatingMode = DETAILED, timeout: float | None = None, polling_interval: float = 3.0) -> List[List[Rating]] ``` Batch version of rate. Parameters: | Name | Type | Description | Default | | ------------------ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------ | ---------- | | `prompt_responses` | `list[PromptResponse]` | List of prompt responses to rate. | *required* | | `rating_mode` | `RatingMode` | Judge reasoning effort (FAST or DETAILED). Both return reasoning; some rating versions pin their effort and ignore this. | `DETAILED` | | `timeout` | `float` | Timeout in seconds for API requests. Defaults to no timeout. | `None` | | `polling_interval` | `float` | Time between status checks in seconds. Defaults to 3.0. | `3.0` | Returns: | Type | Description | | -------------------- | -------------------------------------------------------------------------------------------------- | | `List[List[Rating]]` | List\[List[Rating]\]: List of lists of rating objects, one per criterion for each prompt response. | ## TemplateVariablesResource ``` TemplateVariablesResource(client: Union[Client, AsyncClient]) ``` Bases: `BaseResource` ### add_many_to_collection ``` add_many_to_collection(variables: list[dict[str, Any]], collection: TemplateVariablesCollection, skip_existing: bool = False) -> list[TemplateVariables] ``` Add multiple template variable entries to a collection in one request. Uses the backend's batch upload endpoint by generating a JSONL payload in memory. Parameters: | Name | Type | Description | Default | | --------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | | `variables` | `list[dict[str, Any]]` | List of input_values dicts to add. | *required* | | `collection` | `TemplateVariablesCollection` | Target collection. | *required* | | `skip_existing` | `bool` | When True, rows whose (input_values, environment_config) identity already exists in the collection are skipped, and duplicates within the incoming batch collapse to one; only newly created entries are returned. Rows differing only in environment_config (allowed on AGENTIC/PERSONA collections) stay distinct. Dedup runs server-side against the stored form, so it matches regardless of value type (ints, booleans, None, conversation payloads). Best-effort under concurrency: two simultaneous skip_existing calls to the same collection can each still create a row (the server-side check is not serialized), so this is not a hard uniqueness guarantee. Defaults to False, preserving the additive behavior of creating an entry for every incoming row. | `False` | Returns: | Type | Description | | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | `list[TemplateVariables]` | List\[TemplateVariables\]: The created template variables. Rows skipped as duplicates (existing or within-batch) are NOT included. | ### add_to_collection ``` add_to_collection(template_variables: dict[str, Any], collection: TemplateVariablesCollection, environment_config: dict[str, Any] | None = None) -> TemplateVariables ``` Adds a new entry to a collection. Parameters: | Name | Type | Description | Default | | -------------------- | ----------------------------- | ----------------------------------- | --------------------------------------------- | | `template_variables` | `dict[str, Any]` | The template variables to add. | *required* | | `collection` | `TemplateVariablesCollection` | The collection to add the entry to. | *required* | | `environment_config` | \`dict[str, Any] | None\` | Optional per-entry environment configuration. | Returns: | Name | Type | Description | | ------------------- | ------------------- | -------------------------------------- | | `TemplateVariables` | `TemplateVariables` | The created template variables object. | ### aupdate ``` aupdate(template_variables_id: int, collection: TemplateVariablesCollection | TemplateVariablesCollectionWithEntries | int, *, input_values: dict[str, Any], environment_config: dict[str, Any] | None = None) -> TemplateVariables ``` Update an existing template variables entry (async). Parameters: | Name | Type | Description | Default | | ----------------------- | ----------------------------- | ------------------------------------------------- | ----------------------------------- | | `template_variables_id` | `int` | The ID of the template variables entry to update. | *required* | | `collection` | \`TemplateVariablesCollection | TemplateVariablesCollectionWithEntries | int\` | | `input_values` | `dict[str, Any]` | The new input values for the entry. | *required* | | `environment_config` | \`dict[str, Any] | None\` | Optional environment configuration. | Returns: | Name | Type | Description | | ------------------- | ------------------- | ------------------------------------- | | `TemplateVariables` | `TemplateVariables` | The updated template variables entry. | Raises: | Type | Description | | ---------- | ----------------------------------------------------------- | | `APIError` | If the entry doesn't exist, or the collection is read-only. | ### delete ``` delete(template_variables: TemplateVariables, collection: TemplateVariablesCollection) -> None ``` Deletes a template variables. Parameters: | Name | Type | Description | Default | | -------------------- | ----------------------------- | ------------------------------------------------- | ---------- | | `template_variables` | `TemplateVariables` | The template variables to delete. | *required* | | `collection` | `TemplateVariablesCollection` | The collection containing the template variables. | *required* | Raises: | Type | Description | | --------------- | -------------------------------------------------------------------------------------------------------------- | | `NotFoundError` | If the template variables doesn't exist, belongs to a different collection, or belongs to a different project. | ### delete_all ``` delete_all(collection: TemplateVariablesCollection) -> None ``` Deletes all template variables for a collection. Parameters: | Name | Type | Description | Default | | ------------ | ----------------------------- | ---------------------------------------------------- | ---------- | | `collection` | `TemplateVariablesCollection` | The collection to delete all template variables for. | *required* | Raises: | Type | Description | | --------------- | ------------------------------------------------------------------ | | `NotFoundError` | If the collection doesn't exist or belongs to a different project. | ### generate ``` generate(collection: TemplateVariablesCollection, prompt_template: PromptTemplate) -> TemplateVariables ``` Generates a new template variable in a collection using a prompt template. Parameters: | Name | Type | Description | Default | | ----------------- | ----------------------------- | --------------------------------------------------------- | ---------- | | `collection` | `TemplateVariablesCollection` | The collection to add the generated template variable to. | *required* | | `prompt_template` | `PromptTemplate` | The prompt template to use for generation. | *required* | Returns: | Name | Type | Description | | ------------------- | ------------------- | ---------------------------------------------- | | `TemplateVariables` | `TemplateVariables` | The newly generated template variables object. | ### get ``` get(*, collection_id: int, id: int) -> TemplateVariables ``` Get a template variable by id. Parameters: | Name | Type | Description | Default | | --------------- | ----- | ---------------------------------------------------------- | ---------- | | `collection_id` | `int` | The id of the collection containing the template variable. | *required* | | `id` | `int` | The id of the template variable. | *required* | Returns: | Type | Description | | ------------------- | -------------------------------- | | `TemplateVariables` | The requested template variable. | ### list ``` list(collection: TemplateVariablesCollection) -> list[TemplateVariables] ``` Returns all template variables for a collection. Parameters: | Name | Type | Description | Default | | ------------ | ----------------------------- | ---------------------------------- | ---------- | | `collection` | `TemplateVariablesCollection` | The collection to get entries for. | *required* | Returns: | Type | Description | | ------------------------- | ------------------------------------------------------ | | `list[TemplateVariables]` | list\[TemplateVariables\]: List of template variables. | Raises: | Type | Description | | --------------- | ------------------------------ | | `NotFoundError` | If the collection is not found | ### update ``` update(template_variables_id: int, collection: TemplateVariablesCollection | TemplateVariablesCollectionWithEntries | int, *, input_values: dict[str, Any], environment_config: dict[str, Any] | None = None) -> TemplateVariables ``` Update an existing template variables entry. Parameters: | Name | Type | Description | Default | | ----------------------- | ----------------------------- | ------------------------------------------------- | ----------------------------------- | | `template_variables_id` | `int` | The ID of the template variables entry to update. | *required* | | `collection` | \`TemplateVariablesCollection | TemplateVariablesCollectionWithEntries | int\` | | `input_values` | `dict[str, Any]` | The new input values for the entry. | *required* | | `environment_config` | \`dict[str, Any] | None\` | Optional environment configuration. | Returns: | Name | Type | Description | | ------------------- | ------------------- | ------------------------------------- | | `TemplateVariables` | `TemplateVariables` | The updated template variables entry. | Raises: | Type | Description | | ---------- | ----------------------------------------------------------- | | `APIError` | If the entry doesn't exist, or the collection is read-only. | ## TemplateVariablesCollectionsResource ``` TemplateVariablesCollectionsResource(client: Union[Client, AsyncClient]) ``` Bases: `BaseResource` ### aupdate ``` aupdate(collection_id: int, name: str, description: str | None = None, read_only: bool | None = None, columns: List[str | CollectionColumn] | None = None, collection_type: Literal['STANDARD', 'AGENTIC'] | _Unset = _UNSET, environment_config: dict | None | _Unset = _UNSET) -> TemplateVariablesCollection ``` Update an existing collection (async). See `update` for the parameter contract; `collection_type` is deprecated. ### create ``` create(name: str, description: str = '', variables: List[Dict[str, Any]] | None = None, columns: List[str | CollectionColumn] | None = None, read_only: bool = False, collection_type: Literal['STANDARD', 'AGENTIC'] = 'STANDARD', environment_config: dict | None = None) -> TemplateVariablesCollectionWithEntries ``` Creates a new collection. Parameters: | Name | Type | Description | Default | | -------------------- | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `name` | `str` | The name for the new collection. | *required* | | `description` | `str` | Optional description for the collection. | `''` | | `variables` | `list[dict[str, Any]]` | Optional list of variables to add to the collection. Values can be strings for TEXT columns, dicts for CONVERSATION columns, or other types. | `None` | | `columns` | \`list\[str | CollectionColumn\]\` | Optional list of column definitions. Can be column names as strings (typed by the server unless you set column_type) or CollectionColumn objects. An AGENTIC collection needs a TEXT column named task holding one unique task name per row, since that is how experiments identify tasks; an instruction column inferred from variables is typed RAW_INPUT. | | `read_only` | `bool` | Whether the collection should be read-only. | `False` | | `collection_type` | `Literal['STANDARD', 'AGENTIC']` | The type of collection ("STANDARD" or "AGENTIC"). Defaults to "STANDARD". | `'STANDARD'` | | `environment_config` | \`dict | None\` | Optional environment configuration for agentic collections. | Returns: | Type | Description | | ----------------------------- | ------------------------------------ | | `TemplateVariablesCollection` | The newly created collection object. | Raises: | Type | Description | | ---------- | ------------------------------------------------------------- | | `APIError` | If collection with same name already exists (400 BAD REQUEST) | ### delete ``` delete(template_variables_collection: TemplateVariablesCollection) -> None ``` Delete a collection. ### get ``` get(*, name: str | None = None, id: int | None = None) -> TemplateVariablesCollectionWithEntries ``` Get a collection by name or id. Parameters: | Name | Type | Description | Default | | ------ | ----- | ----------- | ---------------------------------- | | `name` | \`str | None\` | The name of the collection to get. | | `id` | \`int | None\` | The id of the collection to get. | Returns: | Name | Type | Description | | ---------------------------------------- | ---------------------------------------- | ---------------------- | | `TemplateVariablesCollectionWithEntries` | `TemplateVariablesCollectionWithEntries` | The collection object. | Raises: | Type | Description | | ------------ | ------------------------------------------------------------- | | `ValueError` | If neither or both name and id are provided, or if not found. | ### get_or_create ``` get_or_create(name: str, description: str = '', variables: List[Dict[str, Any]] | None = None, columns: List[str | CollectionColumn] | None = None, read_only: bool = False, collection_type: Literal['STANDARD', 'AGENTIC'] = 'STANDARD', environment_config: dict | None = None) -> tuple[TemplateVariablesCollectionWithEntries, bool] ``` Gets an existing collection by name or creates a new one if it doesn't exist. If a collection with the given name exists: - If columns parameter is provided, validates that the existing collection has matching column types - Returns the existing collection if compatible, otherwise raises ValueError - Other parameters (description, variables, read_only) are ignored with warnings Parameters: | Name | Type | Description | Default | | -------------------- | -------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | `str` | The name of the collection to get or create. | *required* | | `description` | `str` | Optional description for the collection if created. | `''` | | `variables` | \`List\[Dict[str, Any]\] | None\` | Optional list of variables to add to the collection if created. Values can be strings for TEXT columns, dicts for CONVERSATION columns, or other types. | | `columns` | \`List\[str | CollectionColumn\] | None\` | | `read_only` | `bool` | Whether the collection should be read-only if created. | `False` | | `collection_type` | `Literal['STANDARD', 'AGENTIC']` | The type of collection ("STANDARD" or "AGENTIC"). Defaults to "STANDARD". | `'STANDARD'` | | `environment_config` | \`dict | None\` | Optional environment configuration for agentic collections. | Returns: | Type | Description | | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `tuple[TemplateVariablesCollectionWithEntries, bool]` | tuple\[TemplateVariablesCollectionWithEntries, bool\]: A tuple containing: - Collection: The retrieved or created collection object - bool: True if a new collection was created, False if existing was found | Raises: | Type | Description | | ------------ | ------------------------------------------------------ | | `ValueError` | If collection exists but has incompatible column types | ### list ``` list(filters: TemplateVariablesCollectionFilter | None = None, compatible_prompt_template: PromptTemplate | None = None, sort_options: TemplateVariablesCollectionSort | None = None) -> list[TemplateVariablesCollection] ``` Get a list of template variables collections. Parameters: | Name | Type | Description | Default | | ---------------------------- | ----------------------------------- | ----------- | -------------------------------------------------------------- | | `filters` | \`TemplateVariablesCollectionFilter | None\` | Filter for template variables collections. | | `compatible_prompt_template` | \`PromptTemplate | None\` | Filter collections compatible with a specific prompt template. | | `sort_options` | \`TemplateVariablesCollectionSort | None\` | Sort for template variables collections. | Returns: | Type | Description | | ----------------------------------- | ------------------------------------------------------------------------------ | | `list[TemplateVariablesCollection]` | list\[TemplateVariablesCollection\]: A list of template variables collections. | ### update ``` update(collection_id: int, name: str, description: str | None = None, read_only: bool | None = None, columns: List[str | CollectionColumn] | None = None, collection_type: Literal['STANDARD', 'AGENTIC'] | _Unset = _UNSET, environment_config: dict | None | _Unset = _UNSET) -> TemplateVariablesCollection ``` Update an existing collection. Parameters: | Name | Type | Description | Default | | -------------------- | -------------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `collection_id` | `int` | The ID of the collection to update. | *required* | | `name` | `str` | The new name for the collection. | *required* | | `description` | \`str | None\` | Optional new description for the collection. | | `read_only` | \`bool | None\` | Optional new read-only status for the collection. | | `columns` | \`List\[str | CollectionColumn\] | None\` | | `collection_type` | \`Literal['STANDARD', 'AGENTIC'] | \_Unset\` | Deprecated since 1.2.5, removed in 1.4.0 — set the type via create(). A collection's type is fixed at creation, so anything other than its current type is rejected with HTTP 422. Not sent if omitted. | | `environment_config` | \`dict | None | \_Unset\` | Returns: | Name | Type | Description | | ----------------------------- | ----------------------------- | ------------------------------ | | `TemplateVariablesCollection` | `TemplateVariablesCollection` | The updated collection object. | Raises: | Type | Description | | ---------- | ------------------------------------------------ | | `APIError` | If collection doesn't exist or validation fails. |