Apache Burr
repository·main·Indexed 25 days ago
https://github.com/apache/burrApache Burr (incubating) is a framework for developing stateful applications, such as chatbots, agents, and simulations, using a state-machine approach with simple Python building blocks. It provides a built-in UI for real-time telemetry and debugging, and supports complex decision-making logic for LLM-driven applications. Version 0.42.0.
What's inside apache-burr
- Burr UI is an open-source telemetry interface designed for monitoring, debugging, and replaying application runs in real time. It is capable of running locally for development or being deployed alongside a production stack to observe live application behavior.
Overview of Burr State Persistence
mainBurr provides tools for loading and saving state using lifecycle hooks. It supports various database integrations through both synchronous and asynchronous implementations.
To avoid naming conflicts with underlying database libraries, Burr uses a naming convention of
b_dependency-library(e.g.,b_psycopg2).Use parallelism tools in Apache Burr
mainApache Burr provides a set of tools withinburr.core.parallelismto manage sub-actions and sub-graphs, making it easier to execute multiple tasks or processes in parallel within your workflows. These tools allow you to decompose complex graphs into manageable, concurrent units of work.Index of Burr usage examples
mainThe following examples demonstrate various features and use cases of the Burr library:
- simple-chatbot-intro: A basic chatbot implementation; recommended starting point for understanding Burr.
- conversational-rag: Demonstrates conversational Retrieval-Augmented Generation (RAG) using state and prior knowledge to augment LLM calls.
- hello-world-counter: A minimal state machine example used in documentation.
- llm-adventure-game: A text-based adventure game demonstrating progression through hidden states and component reuse.
- ml-training: A simple ML training pipeline demonstrating how to track model training.
- multi-agent-collaboration: Demonstrates multi-agent collaboration patterns.
- multi-modal-chatbot: Shows how to use a model to conditionally delegate tasks to other models.
- streaming-overview: Demonstrates using the streaming API for faster user responses.
- integrations/bedrock: Minimal graphs using Amazon Bedrock via
BedrockActionandBedrockStreamingAction. - tracing-and-spans: Demonstrates how to use Burr's tracing functionality for increased visibility.
- web-server: Shows how to integrate Burr into a web server for user interaction.
Build trustworthy LLM applications with Instructor and Burr
mainThis example demonstrates how to combine
instructorfor structured LLM outputs withburrfor application observability, debugging, and testing.By using Burr in your LLM application, you gain:
- Observability: Real-time monitoring and logging of your
Applicationexecution, viewable via Burr's web user interface. - Persistence: The ability to save the application
Stateat any point. This enables creating user sessions (like conversation histories), investigating bugs, iterating on code paths, and generating test cases for guardrails. - Portability: The
Applicationcan be deployed in various environments, including notebooks, scripts, or web services (e.g., using FastAPI).
- Observability: Real-time monitoring and logging of your
Explore Streaming Async and Notebook implementations
mainThe streaming overview includes additional implementations for different environments:
- Async Streaming: The
async_application.pyfile demonstrates how to implement streaming using asynchronous patterns. This is useful for building high-concurrency applications where you want to stream responses without blocking. - Interactive Notebooks: A Google Colab notebook is available to walk through the streaming logic step-by-step in a managed notebook environment.
- Async Streaming: The
Use Streamlit integration for debugging and development
mainThe Streamlit integration provides utility functions to visualize and interact with Burr state machines. Note that these are considered 'tough-points' (utility functions) and are subject to change; they are recommended primarily for debugging and development purposes rather than production use.Scaling Burr applications in a distributed environment
mainWhen scaling a Burr-powered web service horizontally, consider these two layers:
Database Layer
Scalability depends on your chosen database and schema. Burr facilitates efficient querying by partitioning data using
application_idand apartition_key(such as auser_id). Indexing your state table on these keys allows for efficient retrieval of specific application states.Compute Layer
To scale the server instances, you must handle state synchronization and locking to prevent race conditions if multiple servers attempt to run the same application simultaneously. Strategies include:
- Database Locking: Use a locking mechanism in the database to ensure only one server processes a specific application at a time.
- Sticky Sessions/Sharding: Route a specific user to the same server instance to minimize synchronization issues.
- Custom Persistence Logic: Implement custom logic at the persistence layer to handle forking or resolution of state.
Implement streaming actions in Burr
mainStreaming actions allow you to yield intermediate results (like LLM tokens) to the user as they are produced, reducing time-to-first-token.
Function-based Streaming Actions
Use the
@streaming_actiondecorator. The function must be a generator that yields a tuple:(result_dict, state_update, None, None).- Intermediate results: Yield
(result_dict, None)to provide data without updating the state. - Final result: The last
yieldmust include the final result and the state update (e.g.,yield final_result_dict, state.append(...)).
Class-based Streaming Actions
Inherit from
StreamingAction. This approach separates the execution logic from the state update logic:stream_run(...): A generator that yields only theresult_dict. The finalyieldin this method is passed to theupdatemethod.update(result, state): Receives the final result fromstream_runand returns the updatedState.readsandwrites: Properties defining the state keys used.
Async Streaming Actions
Both function-based and class-based patterns support
asyncimplementations usingAsyncGeneratorandasync forloops.- Intermediate results: Yield
Synchronous vs Asynchronous hooks
mainBurr supports both synchronous and asynchronous hooks. The behavior depends on which version you implement:
- Synchronous hooks: These are called during both synchronous and asynchronous run methods (including
step,astep,iterate,aiterate,run, andarun). - Asynchronous hooks: These are only called when using the asynchronous methods (
astep,aiterate, andarun).
Note on Hook Order: Currently, hook execution order is undefined (though they currently follow the order of definition). Future implementations may call
pre...hooks in definition order andpost...hooks in reverse order.- Synchronous hooks: These are called during both synchronous and asynchronous run methods (including
Planned Exception Management and Error Transitions
mainCurrently, exceptions in an action break the control flow and stop the program. Burr plans to introduce the ability to conditionally transition based on exceptions, allowing you to route to error-handling or retry actions.
While the final API is being designed, the conceptual approach involves defining error-based transitions in the builder. For example, you might transition from an action back to itself on a specific error type, or limit the number of error-based transitions before resetting.
# Conceptual idea for error-based transitions in the builder builder.with_actions( some_flaky_action=some_flaky_action ).with_transitions( ( "some_flaky_action", "some_flaky_action", error(APIException) # infinite retries error(APIException, max=3) # 3 visits to this edge then it gets reset if this is not chosen # That's stored in state ) )Run all combinations of actions and states using MapActionsAndStates
mainTo execute a full Cartesian product of all provided actions and all provided states (e.g., running every model in a list against every prompt in a list), implement the
MapActionsAndStatesclass. This is the base class for bothMapStatesandMapActions.Implementation requirements:
.actions(): Generator yielding the actions to run..states(): Generator yielding the state variations..reduce(): Merges the results of all combinations into the final state.
from burr.core import action, state from burr.core.parallelism import MapActionsAndStates, RunnableGraph from typing import Callable, Generator, List @action(reads=["prompt", "model"], writes=["llm_output"]) def query_llm(state: State, model: str) -> State: return state.update(llm_output=_query_my_llm(prompt=state["prompt"], model=model)) class TestModelsOverPrompts(MapActionsAndStates): def actions(self, state: State, context: ApplicationContext, inputs: Dict[str, Any]) -> Generator[Action | Callable | RunnableGraph, None, None]: for action in [ query_llm.bind(model="gpt-4").with_name("gpt_4_answer"), query_llm.bind(model="o1").with_name("o1_answer"), query_llm.bind(model="claude").with_name("claude_answer"), ]: yield action def states(self, state: State, context: ApplicationContext, inputs: Dict[str, Any]) -> Generator[State, None, None]: for prompt in [ "What is the meaning of life?", "What is the airspeed velocity of an unladen swallow?", "What is the best way to cook a steak?", ]: yield state.update(prompt=prompt) def reduce(self, state: State, states: Generator[State, None, None]) -> State: all_llm_outputs = [] for sub_state in states: all_llm_outputs.append( { "output" : sub_state["llm_output"], "model" : sub_state["model"], "prompt" : sub_state["prompt"], } ) return state.update(all_llm_outputs=all_llm_outputs) @property def reads(self) -> List[str]: return ["prompts"] @property def writes(self) -> List[str]: return ["all_llm_outputs"]