ell - The Language Model Programming Library

repository·main·Indexed 26 days ago

https://github.com/madcowd/ell

A lightweight, functional prompt engineering framework that treats prompts as programs (Language Model Programs) rather than simple strings. ell provides tools for versioning, monitoring, and multimodal interaction with LLMs, including Ell Studio for visualization and prompt version control. It features a provider-agnostic API, caching via registered stores, and support for multi-step conversations through ell.chat.

Tokens
33.1K
Snippets
93
Records
161
Agent score
91%

What's inside ell

  1. Understand ell model fallback behavior

    main
    By default, ell automatically registers models from providers like OpenAI, Anthropic, Cohere, and Groq. If you attempt to use a model for which no specific client is registered, ell falls back to a default OpenAI client. This allows you to use new models immediately, provided they are accessible via the OpenAI API. If the fallback fails, you must manually register a client using ell.config.register_model or pass a client explicitly during the function call.
  2. Optimize prompts using FewShotOptimizer

    main

    To optimize a prompt using few-shot learning, you must explicitly mark the function as learnable. Standard @ell.function or @ell.simple definitions will raise a NotLearnableError if passed directly to an optimizer.

    Use ell.learnable() to wrap your function, making it compatible with ell.FewShotOptimizer().fit(learnable, x=x, y=y).

  3. Build ell-studio for production

    main
    Use npm run build to create a production-ready build in the build folder. This command bundles React in production mode and optimizes the build for performance. The output is minified and includes file hashes in the filenames, making it ready for deployment.
    npm run build
  4. Manual structured output for non-native models

    main

    For models that do not natively support Pydantic via response_format (such as gpt-3.5-turbo), you must manually prompt the model to return JSON. You can provide the schema by using MovieReview.model_json_schema() within a system prompt. Since automatic parsing is not yet available for these manual flows, you must use Model.model_validate_json() on the returned string to convert it back into a Pydantic object.

    from pydantic import BaseModel, Field
    
    class MovieReview(BaseModel):
        title: str = Field(description="The title of the movie")
        rating: int = Field(description="The rating of the movie out of 10")
        summary: str = Field(description="A brief summary of the movie")
    
    @ell.simple(model="gpt-3.5-turbo")
    def generate_movie_review_manual(movie: str):
        return [
            ell.system(f"""You are a movie review generator. Given the name of a movie, you need to return a structured review in JSON format.
    
    You must absolutely respond in this format with no exceptions.
    {MovieReview.model_json_schema()}
    """),
            ell.user("Review the movie: {movie}"),
        ]
    
    # Generate and manually parse
    unparsed = generate_movie_review_manual("The Matrix")
    parsed = MovieReview.model_validate_json(unparsed)
  5. Create a basic evaluation with ell.evaluation.Evaluation

    main

    An evaluation in ell is a structured suite used to measure a Language Model Program's (LMP) performance. It requires three components:

    1. A Dataset: A list of dictionaries representing input distributions (e.g., [{"input": {"key": "val"}, "expected_output": "target"}]).
    2. Metrics: A dictionary mapping metric names to functions. A metric function must accept (datapoint, output) and return a measurable quantity (e.g., a float).
    3. An LMP: A function decorated with @ell.simple that performs the task.

    To run the evaluation, instantiate ell.evaluation.Evaluation and call its .run() method, passing the LMP function as the argument.

    import ell
    ell.init(store="./logdir")  # Enable versioning and storage
    
    # 1. Define an LMP:
    @ell.simple(model="gpt-4o", max_tokens=10)
    def classify_sentiment(text: str):
        """You are a sentiment classifier. Return 'positive' or 'negative'.""
        return f"Classify sentiment: {text}"
    
    # 2. A small dataset:
    dataset = [
        {"input": {"text": "I love this product!"}, "expected_output": "positive"},
        {"input": {"text": "This is terrible."}, "expected_output": "negative"}
    ]
    
    # 3. A metric function that checks correctness:
    def accuracy_metric(datapoint, output):
        return float(datapoint["expected_output"].lower() in output.lower())
    
    # 4. Constructing the eval:
    eval = ell.evaluation.Evaluation(
        name="sentiment_eval",
        dataset=dataset,
        metrics={"accuracy": accuracy_metric}
    )
    
    # Run the eval:
    result = eval.run(classify_sentiment)
    print("Average accuracy:", result.results.metrics["accuracy"].mean())
  6. Choose between @ell.simple and @ell.complex

    main

    Use @ell.simple for straightforward text-in, text-out interactions. It optimizes for readability by returning strings.

    Use @ell.complex when you need:

    • Multiturn conversations
    • Tool use
    • Structured outputs
    • Multimodal outputs (e.g., generating images or audio)
    • Rich Message objects containing metadata and helper functions.
  7. Implement custom tool spec autogeneration

    main

    If you want to avoid the default prompt used by ell or require a specific generation logic, you can provide a custom generator function to the @ell.tool decorator. The generator should accept the tool's source code as a string and return a valid JSON tool specification.

    Example pattern for a custom generator:

    1. Define a function (e.g., using @ell.simple) that takes tool_source: str and returns the schema.
    2. Pass that function to @ell.tool(autogenerate=...).
    @ell.simple
    def my_custom_tool_spec_generator(tool_source: str):
        # User implements this once in their code base or repo
        ...
    
    @ell.tool(autogenerate=my_custom_tool_spec_generator)
    def search_twitter(query, n=7):
        ...
    
    @ell.complex(model="gpt-4o", tools=[search_twitter])
    def my_llm_program(message_history: List[Message]) -> List[Message]:
        ...
  8. Initialize ell for versioning and storage

    main

    To enable automatic versioning, serialization of prompts, and local storage of LMP calls, call ell.init() with a specified storage directory. This allows you to treat prompt engineering like machine learning checkpointing, saving the source code and invocation data to a local store.

    import ell
    
    ell.init(store='./logdir')  # Versions your LMPs and their calls
  9. Generate structured outputs using Pydantic models

    main

    You can ensure language model responses adhere to a specific schema by passing a Pydantic model to the response_format argument of the @ell.complex decorator. This is currently only supported for the gpt-4o-2024-08-06 model. When using this method, the resulting message object contains a .parsed attribute that holds the instantiated Pydantic model.

    from pydantic import BaseModel, Field
    
    class MovieReview(BaseModel):
        title: str = Field(description="The title of the movie")
        rating: int = Field(description="The rating of the movie out of 10")
        summary: str = Field(description="A brief summary of the movie")
    
    @ell.complex(model="gpt-4o-2024-08-06", response_format=MovieReview)
    def generate_movie_review(movie: str) -> MovieReview:
        """You are a movie review generator. Given the name of a movie, you need to return a structured review."""
        return f"generate a review for the movie {movie}"
    
    # Usage
    message = generate_movie_review("The Matrix")
    review = message.parsed
    print(f"Movie Title: {review.title}")