promptic

repository·main·Indexed 42 days ago

https://github.com/knowsuchagency/promptic

A Python library for productive LLM application development that provides a 'requests'-like experience. Built on LiteLLM to prevent provider lock-in, it features the @llm decorator for prompt construction, type-safe structured outputs via Pydantic, agent building with @llm.tool, streaming, prompt caching, and conversation memory management. It also integrates with Weave for observability and VCR.py for deterministic testing of LLM interactions.

Tokens
5.8K
Snippets
21
Records
28
Agent score
53%

What's inside promptic

  1. Overview of promptic features

    main

    promptic is designed to be a productive and pythonic library for LLM application development. It leverages LiteLLM to prevent provider lock-in, allowing you to switch LLM providers by changing a single line of code.

    Key features include:

    • Type-safe structured outputs: Uses Pydantic for reliable data extraction.
    • Agent building: Easy implementation of agents using function calling.
    • Streaming: Built-in support for real-time response streaming.
    • Prompt caching: Automatic caching for supported models to reduce latency and cost.
    • Conversation memory: Built-in mechanisms to manage chat history.
  2. Manage conversation memory and state

    main

    By default, promptic functions are stateless. You can enable conversation memory in two ways:

    1. Built-in Memory: Set memory=True in the @llm decorator. This allows the LLM to maintain context across multiple calls. Use the .message() method on the decorated function to send follow-up messages without re-executing the original prompt template.
    2. Custom State: Extend the State class to implement custom persistence (e.g., saving history to Redis or a database) and pass it to the state parameter in @llm.
    from promptic import llm
    
    @llm(memory=True)
    def chat(message):
        """{message}"""
    
    # Initial call
    print(chat("Hi, I'm Alice"))
    
    # Follow-up using .message() to maintain context
    print(chat.message("What is my name?"))
  3. Create agents using @llm.tool

    main

    You can build agents by decorating functions with @llm.tool. These functions become tools that an LLM can invoke. When an LLM function (the agent) is called, it can automatically execute these tools to perform actions or retrieve information based on the user's request.

    from promptic import llm
    
    @llm(model="gpt-4o")
    def scheduler(command):
        """{command}"""
    
    @scheduler.tool
    def get_current_time():
        """Get the current time"""
        return "10:00 AM"
    
    print(scheduler("What time is it?"))
  4. Use the @llm decorator for basic prompting

    main

    The @llm decorator turns a Python function into an LLM prompt. The function's docstring serves as the prompt template, where curly braces {} denote variables that will be replaced by the function's arguments. When the function is called, promptic generates the prompt and returns the LLM's response.

    You can also pass configuration options to the decorator, such as model and system prompts.

    from promptic import llm
    
    @llm
    def translate(text, language="Chinese"):
        """Translate '{text}' to {language}"""
    
    print(translate("Hello world!"))
    # 您好,世界!
    
    @llm(
        model="claude-3-haiku-20240307",
        system="You are a customer service analyst. Provide clear sentiment analysis with key points.",
    )
    def analyze_sentiment(text):
        """Analyze the sentiment of this customer feedback: {text}"""
    
    print(analyze_sentiment("The product was okay but shipping took forever"))
  5. Use VCR.py to record and replay LLM API calls

    main

    VCR.py integration allows you to record HTTP interactions (like API calls to OpenAI or Anthropic) into YAML files called "cassettes". Subsequent test runs replay these recordings instead of making real API calls, providing speed, cost savings, and determinism.

    To use it, mark your test functions with the @pytest.mark.vcr decorator. On the first run, VCR records the live API responses; on subsequent runs, it uses the recorded data.

    import pytest
    from promptic import llm
    
    @pytest.mark.vcr
    def test_llm_call():
        @llm(model="gpt-4o-mini", temperature=0)
        def get_greeting(name: str) -> str:
            """Say hello to {name}"""
    
        result = get_greeting("Alice")
        assert "Alice" in result
  6. Integrate with Weave for observability

    main

    To trace LLM interactions and function calls, you can use the Promptic class with a weave client. This allows you to visualize and debug your LLM application flows.

    import weave
    from promptic import Promptic
    
    client = weave.init("my-project")
    promptic = Promptic(weave_client=client)
    
    @promptic.llm
    def translate(text, language="Chinese"):
        """Translate '{text}' to {language}"""
    
    print(translate("Hello"))
  7. Get structured outputs using Pydantic or JSON Schema

    main

    You can force the LLM to return data in a specific format using two methods:

    1. Pydantic Models: Use a Pydantic model as the function's return type annotation. The LLM response is automatically validated and returned as a Pydantic object.
    2. JSON Schema: Pass a dictionary representing a JSON Schema to the json_schema parameter in the @llm decorator.
    from pydantic import BaseModel
    from promptic import llm
    
    class Forecast(BaseModel):
        location: str
        temperature: float
        units: str
    
    @llm
    def get_weather(location, units: str = "fahrenheit") -> Forecast:
        """What's the weather for {location} in {units}?"""
    
    print(get_weather("San Francisco"))
  8. Migrate existing tests to VCR

    main

    To migrate promptic tests to use VCR.py for recording and replaying LLM API interactions, follow these steps:

    1. Add the VCR marker: Decorate your test functions with @pytest.mark.vcr.
    2. Record initial cassettes: Run your tests with the --record-mode=once flag to generate the recording files.
    3. Verify cassettes: Run tests with --record-mode=none to ensure they pass using only the recorded data.
    4. Commit cassettes: Add the generated files in tests/cassettes/ to your version control.

    Best Practices:

    • Ensure determinism: Always set temperature=0 in your @llm calls to ensure consistent responses for recording.
    • Group related tests: Keep tests with similar prompts in the same file to share cassette directories.
    • Handle multiple models: Use @pytest.mark.parametrize alongside @pytest.mark.vcr when testing different models.
    @pytest.mark.vcr
    def test_translation():
        @llm(model="gpt-4o-mini", temperature=0)
        def translate(text: str, language: str) -> str:
            """Translate {text} to {language}"""
    
        result = translate("Hello", "Spanish")
        assert "Hola" in result
  9. Best practices for VCR with Promptic

    main

    To ensure reliable and maintainable tests when using VCR:

    • Use deterministic settings: Always set temperature=0 in your @llm calls to ensure the LLM responses are consistent across recordings.
    • Organize by recording needs: Use @pytest.mark.vcr for tests making API calls and @pytest.mark.no_vcr for pure logic tests to avoid unnecessary overhead.
    • Update cassettes when prompts change: If you modify a prompt or expected output, use --record-mode=rewrite to refresh the cassette.
    • Handle sensitive data: API keys are automatically filtered and replaced with REDACTED in cassettes (configured in tests/conftest.py).
    • Commit cassettes to version control: This allows CI/CD to run tests without API keys and ensures other developers can run tests immediately.
  10. Test LLM functions deterministically with VCR.py

    main

    Promptic integrates with VCR.py to record and replay HTTP interactions. This allows you to run tests without making real API calls after the first run, making tests faster and cheaper.

    To use it, apply the @pytest.mark.vcr decorator to your test function.

    import pytest
    from promptic import llm
    
    @pytest.mark.vcr
    def test_my_llm_function():
        @llm(model="gpt-4o-mini", temperature=0)
        def get_capital(country: str) -> str:
            """What is the capital of {country}?"""
    
        assert get_capital("France") == "Paris"
  11. Troubleshoot VCR errors

    main

    "Cassette not found"

    Cause: The test is trying to replay a cassette that doesn't exist. Solution: Run the test with --record-mode=once to create the cassette.

    "Request not found in cassette"

    Cause: The test is making a request that wasn't previously recorded. Solution:

    1. Re-record the cassette using --record-mode=rewrite.
    2. Use --record-mode=new_episodes to append the new request to the existing cassette.