OpenEvals

repository·main·Indexed 22 days ago

https://github.com/langchain-ai/openevals

A library for writing evaluations (evals) for LLM applications, supporting reference-free checks and LLM-as-judge implementations. It provides tools for evaluating RAG applications across correctness, helpfulness, groundedness, and retrieval relevance, as well as prebuilt prompts for quality, safety, security, and multimodal content (image and voice). Available for Python and TypeScript.

Tokens
27.5K
Snippets
78
Records
89
Agent score
77%

What's inside openevals

  1. How multiturn simulation works

    main

    The simulation follows a loop:

    1. The user is called first to generate the initial input for the app.
    2. The app processes the input and returns a chat message.
    3. The returned message is passed back to the user.
    4. This continues until max_turns is reached or a stopping_condition returns True.

    All messages are collected into a trajectory. If a message lacks an id, the simulator generates one. Once the simulation ends, the final trajectory is passed to any provided trajectory_evaluators via an outputs keyword argument.

  2. Customize LLM-as-judge prompts

    main

    The prompt parameter for create_llm_as_judge is highly flexible. You can provide:

    1. A string (f-string/template string): Use placeholders like {inputs} or {outputs}. You can also include additional variables (e.g., {context}) and pass them when calling the evaluator.
    2. A LangChain prompt template: Allows for advanced formatting like mustache templates.
    3. A function: A function that accepts keyword arguments and returns a list of formatted messages.

    String Prompt Options:

    • system: A string to set a system prompt for the judge model.
    • few_shot_examples: A list of dictionaries containing inputs, outputs, reasoning, and score. These are appended to the end of the prompt to provide examples of good/bad behavior.
    MY_CUSTOM_PROMPT = """
    Use the following context to help you evaluate for hallucinations in the output:
    
    <context>
    {context}
    </context>
    
    <input>
    {inputs}
    </input>
    
    <output>
    {outputs}
    </output>
    """
    
    custom_prompt_evaluator = create_llm_as_judge(
        prompt=MY_CUSTOM_PROMPT,
        model="openai:gpt-5.4",
    )
    
    custom_prompt_evaluator(
        inputs="What color is the sky?",
        outputs="The sky is red.",
        context="It is early evening.",
    )
  3. Extract code from LLM outputs in OpenEvals

    main

    When evaluating LLM-generated code, the output often contains interleaved text and explanations. OpenEvals provides mechanisms to extract only the code blocks for evaluation.

    Extraction Strategies

    You can set the code_extraction_strategy parameter to:

    • "llm": Uses an LLM with a default prompt to directly extract the code. You can specify which model to use via the model or client parameter.
    • "markdown_code_blocks": Extracts content from triple-backtick markdown blocks, excluding shell languages like bash.
    • "none" (Default): Leaves the output content untouched.

    If extraction fails, the evaluator response will include metadata.code_extraction_failed: True.

    Custom Extraction

    For full control over how code is identified, pass a custom function to the code_extractor parameter. This function should take the LLM output as input and return a string containing only the code.

    # Example using markdown extraction
    evaluator = create_code_llm_as_judge(
        prompt=CODE_CORRECTNESS_PROMPT,
        model="openai:gpt-5.4",
        code_extraction_strategy="markdown_code_blocks",
    )
  4. Configure the app function for simulation

    main

    The app function is the entry point for your LLM application during simulation.

    Requirements:

    • Input Arguments: Must accept a chat message (a dictionary/object with role and content keys) and a thread_id (as a keyword argument in Python or part of the options object in TypeScript).
    • State Management: The app is stateless regarding the simulation; it only receives the next message from the user. You must use the thread_id to internally track and manage conversation history if your application requires it.
    • Output: Must return a chat message containing at least role and content keys.
  5. How simulated users work in multi-turn simulations

    main

    In OpenEvals, a user is a function that accepts the current conversation trajectory (and an optional thread_id/threadId) and returns a message with role="user". This message is then passed back to your application.

    There are two ways to implement a user:

    1. Prebuilt: Use create_llm_simulated_user to let an LLM drive the persona.
    2. Custom: Define your own function to implement specific logic, such as deterministic responses or complex state management.

    When running a simulation via run_multiturn_simulation (Python) or runMultiturnSimulation (TypeScript), the user function is called at each turn to provide the next input for your app.

  6. Evaluate RAG applications with OpenEvals

    main

    OpenEvals provides specific methodologies for evaluating Retrieval-Augmented Generation (RAG) pipelines across four key dimensions:

    1. Correctness: Measures how similar the generated answer is to a ground-truth reference. Requires reference_outputs. Uses CORRECTNESS_PROMPT.
    2. Helpfulness: Measures how well the response addresses the user's initial input. Does not require a reference. Uses RAG_HELPFULNESS_PROMPT.
    3. Groundedness: Measures if the response is supported by the retrieved context (vs. hallucinating). Compares outputs to context. Uses RAG_GROUNDEDNESS_PROMPT.
    4. Retrieval Relevance: Measures how relevant the retrieved context is to the user's query. Compares inputs to context. Uses RAG_RETRIEVAL_RELEVANCE_PROMPT.
  7. Configure the user function for simulation

    main

    The user represents the entity interacting with your application.

    Requirements:

    • Input Arguments: Must accept the current trajectory (a list of messages) and keyword arguments for thread_id and turn_counter.
    • Output: Must return a chat message, though it may also return a list of string or message responses.
    • Implementation: You can implement a custom function or use the built-in create_llm_simulated_user (Python) / createLLMSimulatedUser (TypeScript) which uses an LLM to drive the persona.
  8. How to create a custom evaluator

    main

    To ensure compatibility with the OpenEvals ecosystem, custom evaluators should follow these patterns:

    1. Interface Requirements

    Evaluators should accept a subset of these parameters (which can be any value, but typically a dictionary/object):

    • inputs: The inputs to your application.
    • outputs: The outputs from your application.
    • reference_outputs (Python) or referenceOutputs (TypeScript): The reference outputs to evaluate against.

    2. Factory Functions

    If your evaluator requires additional configuration (like a regex pattern or a specific model), use a factory function named create_<evaluator_name> (e.g., create_regex_evaluator).

    3. Return Format

    Evaluators must return a dictionary (or a list of dictionaries) containing:

    • key: A string representing the metric name.
    • score: A boolean or number representing the score.
    • comment: A string representing the comment/justification (optional).

    4. LangSmith Integration

    To ensure results are logged to LangSmith, wrap your internal logic in the _run_evaluator/_arun_evaluator (Python) or runEvaluator (TypeScript) method. This method accepts a scorer function that returns either a single score or a tuple of (score, comment).

    ### Custom Regex Evaluator Example (Python)
    ```python
    import json
    import re
    from typing import Any
    from openevals.types import EvaluatorResult, SimpleEvaluator
    from openevals.utils import _run_evaluator
    
    def create_regex_evaluator(*, regex: str) -> SimpleEvaluator:
        regex = re.compile(regex)
    
        def wrapped_evaluator(*, outputs: Any, **kwargs: Any) -> EvaluatorResult:
            if not isinstance(outputs, str):
                outputs = json.dumps(outputs)
    
            def get_score():
                return regex.match(outputs) is not None
    
            return _run_evaluator(
                run_name="regex_match",
                scorer=get_score,
                feedback_key="regex_match",
            )
        return wrapped_evaluator
    
    evaluator = create_regex_evaluator(regex=r"some string")
    result = evaluator(outputs="this contains some string")
  9. Quickstart: Run your first LLM-as-judge evaluation

    main

    To run an evaluation using an LLM-as-judge, you need to set your OPENAI_API_KEY environment variable. You can then use create_llm_as_judge (Python) or createLLMAsJudge (TypeScript) to initialize an evaluator with a specific prompt and model.

    LLM-as-judge evaluators take inputs and outputs (and potentially other parameters) and format them directly into the provided prompt to generate a score and comment.

    ```python
    from openevals.llm import create_llm_as_judge
    from openevals.prompts import CONCISENESS_PROMPT
    
    # Initialize the evaluator
    conciseness_evaluator = create_llm_as_judge(
        prompt=CONCISENESS_PROMPT,
        model="openai:gpt-5.4",
    )
    
    inputs = "How is the weather in San Francisco?"
    outputs = "Thanks for asking! The current weather in San Francisco is sunny and 90 degrees."
    
    # Run the evaluation
    eval_result = conciseness_evaluator(
        inputs=inputs,
        outputs=outputs,
    )
    
    print(eval_result)
    # Output example:
    # {
    #     'key': 'score',
    #     'score': False,
    #     'comment': 'The output includes an unnecessary greeting (
  10. Quickstart: Run your first LLM-as-judge evaluation (TypeScript)

    main

    To run an evaluation using an LLM-as-judge in TypeScript, ensure OPENAI_API_KEY is set in your environment. Use createLLMAsJudge to initialize the evaluator with a prompt and model, then call the resulting function with an object containing inputs and outputs.

    import { createLLMAsJudge, CONCISENESS_PROMPT } from "openevals";
    
    const concisenessEvaluator = createLLMAsJudge({
      prompt: CONCISENESS_PROMPT,
      model: "openai:gpt-5.4",
    });
    
    const inputs = "How is the weather in San Francisco?"
    const outputs = "Thanks for asking! The current weather in San Francisco is sunny and 90 degrees."
    
    const evalResult = await concisenessEvaluator({
      inputs,
      outputs,
    });
    
    console.log(evalResult);
  11. Run TypeScript type-checking in an E2B sandbox

    main

    You can use OpenEvals to run TypeScript type-checking on LLM-generated code within an E2B sandbox. The evaluator parses package names, installs them, and runs the TypeScript compiler. Errors are returned in the comment field.

    Setup

    1. Install the E2B peer dependency:
      npm install @e2b/code-interpreter
    2. Set your E2B API key:
      process.env.E2B_API_KEY="YOUR_KEY_HERE"
    3. Initialize a sandbox using @e2b/code-interpreter.

    Usage

    Pass the sandbox instance to createE2BTypeScriptEvaluator.

    import { Sandbox } from "@e2b/code-interpreter";
    import { createE2BTypeScriptEvaluator } from "openevals/code/e2b";
    
    const sandbox = await Sandbox.create();
    const evaluator = createE2BTypeScriptEvaluator({
      sandbox,
    });
    
    const CODE = `
    import { StateGraph } from '@langchain/langgraph';
    
    await StateGraph.invoke({})
    `;
    
    const evalResult = await evaluator({ outputs: CODE });
    console.log(evalResult);
  12. Run multi-turn simulations with LangGraph

    main

    You can simulate multi-turn interactions between an LLM-based agent and a simulated user. If your application (the app) is built using LangGraph and uses a checkpointer for persistence, you can pass a thread_id to the simulation to maintain state across turns. The thread_id provided to the simulation is used to populate config.configurable within your application's call.

    # Python Example
    from openevals.simulators import run_multiturn_simulation, create_llm_simulated_user
    from openevals.llm import create_llm_as_judge
    
    # Your app function must accept thread_id to support LangGraph persistence
    def app(inputs, *, thread_id, **kwargs):
        res = agent.invoke(
            {"messages": [inputs]}, 
            config={"configurable": {"thread_id": thread_id}}
        )
        return res["messages"][-1]
    
    user = create_llm_simulated_user(
        system="You are an angry user...",
        model="openai:gpt-5.4",
    )
    
    trajectory_evaluator = create_llm_as_judge(
        model="openai:gpt-5.4",
        prompt="Based on the below conversation, has the user been satisfied?\n{outputs}",
        feedback_key="satisfaction",
    )
    
    simulator_result = run_multiturn_simulation(
        app=app,
        user=user,
        trajectory_evaluators=[trajectory_evaluator],
        max_turns=5,
    )