| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| |
| """ |
| This is an example module that defines a Burr application. |
| |
| It hypothetically transcribes audio and then runs a hypothesis on the transcription to determine a medical diagnosis. |
| """ |
| from typing import Any, Callable, Dict, Generator, List, Tuple |
| |
| import openai |
| |
| from burr import core |
| from burr.core import Action, ApplicationContext, GraphBuilder, State, action |
| from burr.core.parallelism import MapStates, RunnableGraph |
| from burr.tracking import LocalTrackingClient |
| |
| |
| @action(reads=["audio"], writes=["transcription"]) |
| def transcribe_audio(state: State) -> State: |
| """Action to transcribe audio.""" |
| # here we fake transcription. For this example the audio is text already... |
| return state.update(transcription=state["audio"]) |
| |
| |
| @action(reads=["hypothesis", "transcription"], writes=["diagnosis"]) |
| def run_hypothesis(state: State) -> State: |
| """Action to run a hypothesis on a transcription.""" |
| client = openai.Client() # here for simplicity because clients and SERDE don't mix well. |
| hypothesis = state["hypothesis"] |
| transcription = state["transcription"] |
| prompt = ( |
| "Given the following diagnosis hypothesis and medical transcription:\n\n" |
| f"Diagnosis hypothesis:{hypothesis}\n\n" |
| f"Transcription:\n```{transcription}```\n\n" |
| "Answer 'yes' if you believe the hypothesis has a strong reason to hold given the content of the transcription. " |
| "Answer 'no' if you do not believe the hypothesis holds given the content of the transcription. " |
| "Answer 'unsure' if you are unsure and need more details. " |
| ) |
| response = client.chat.completions.create( |
| messages=[ |
| { |
| "role": "system", |
| "content": "You are a doctor diagnosing illnesses that is methodical" |
| " in determining whether a diagnosis hypothesis is supported by a transcription.", |
| }, |
| {"role": "user", "content": prompt}, |
| ], |
| model="gpt-4o-mini", |
| ) |
| return state.update(diagnosis=response.choices[0].message.content) |
| |
| |
| class TestMultipleHypotheses(MapStates): |
| """Parallel action to test multiple hypotheses.""" |
| |
| def action(self, state: State, inputs: Dict[str, Any]) -> Action | Callable | RunnableGraph: |
| """which action to run for each state.""" |
| return run_hypothesis |
| |
| def states( |
| self, state: State, context: ApplicationContext, inputs: Dict[str, Any] |
| ) -> Generator[State, None, None]: |
| """Generate the states to run the action on. |
| You could easily have a list_hypotheses upstream action that writes to "hypothesis" in state |
| And loop through those |
| This hardcodes for simplicity |
| """ |
| for hypothesis in [ |
| "Common cold", |
| "Sprained ankle", |
| "Broken arm", |
| ]: |
| yield state.update(hypothesis=hypothesis) |
| |
| def reduce(self, state: State, states: Generator[State, None, None]) -> State: |
| """Combine the outputs of the parallel action.""" |
| all_diagnoses_outputs = [] |
| for _sub_state in states: |
| all_diagnoses_outputs.append( |
| {"hypothesis": _sub_state["hypothesis"], "diagnosis": _sub_state["diagnosis"]} |
| ) |
| return state.update(diagnosis_outputs=all_diagnoses_outputs) |
| |
| @property |
| def reads(self) -> List[str]: |
| return ["transcription"] |
| |
| @property |
| def writes(self) -> List[str]: |
| return ["diagnosis_outputs"] |
| |
| |
| @action(reads=["diagnosis_outputs"], writes=["final_diagnosis"]) |
| def determine_diagnosis(state: State) -> State: |
| """Action to determine the final diagnosis.""" |
| # could also get an LLM to decide here, or have a human decide, etc. |
| possible_hypotheses = [d for d in state["diagnosis_outputs"] if d["diagnosis"].lower() == "yes"] |
| if len(possible_hypotheses) == 1: |
| return state.update(final_diagnosis=possible_hypotheses[0]["hypothesis"]) |
| elif len(possible_hypotheses) > 1: |
| return state.update( |
| final_diagnosis="Need further clarification. Multiple diagnoses possible." |
| ) |
| else: |
| return state.update(final_diagnosis="Healthy individual") |
| |
| |
| def build_graph() -> core.Graph: |
| """Builds the graph for the application""" |
| graph = ( |
| GraphBuilder() |
| .with_actions( |
| transcribe_audio=transcribe_audio, |
| hypotheses=TestMultipleHypotheses(), |
| determine_diagnosis=determine_diagnosis, |
| ) |
| .with_transitions( |
| ("transcribe_audio", "hypotheses"), |
| ("hypotheses", "determine_diagnosis"), |
| ) |
| .build() |
| ) |
| return graph |
| |
| |
| def build_application( |
| app_id, |
| graph, |
| initial_state, |
| initial_entrypoint, |
| partition_key, |
| tracker, |
| use_otel_tracing: bool = False, |
| ) -> core.Application: |
| """Builds an application with the given parameters. |
| |
| :param app_id: |
| :param graph: |
| :param initial_state: |
| :param initial_entrypoint: |
| :param partition_key: |
| :param tracker: |
| :param use_otel_tracing: |
| :return: |
| """ |
| app_builder = ( |
| core.ApplicationBuilder() |
| .with_graph(graph) |
| .with_state(**initial_state) |
| .with_entrypoint(initial_entrypoint) |
| .with_identifiers(partition_key=partition_key, app_id=app_id) |
| ) |
| if tracker: |
| app_builder = app_builder.with_tracker(tracker, use_otel_tracing=use_otel_tracing) |
| app = app_builder.build() |
| return app |
| |
| |
| def run_my_agent( |
| input_audio: str, partition_key: str = None, app_id: str = None, tracking_project: str = None |
| ) -> Tuple[str, str]: |
| """Runs the agent with the given input audio (in this case a string transcription...). |
| :param input_audio: we fake it here, and ask for a string... |
| :param partition_key: |
| :param app_id: |
| :param tracking_project: |
| :return: |
| """ |
| graph = build_graph() |
| tracker = None |
| if tracking_project: |
| tracker = LocalTrackingClient(project=tracking_project) |
| # we fake the input audio to be a string here rather than a waveform. |
| app = build_application( |
| app_id, graph, {"audio": input_audio}, "transcribe_audio", partition_key, tracker=tracker |
| ) |
| # app.visualize("diagnosis.png", include_conditions=True, view=False, format="png") |
| last_action, _, agent_state = app.run( |
| halt_after=["determine_diagnosis"], |
| inputs={"audio": input_audio}, |
| ) |
| return input_audio, agent_state["final_diagnosis"] |
| |
| |
| if __name__ == "__main__": |
| print(run_my_agent("Patient exhibits mucus dripping from nostrils and coughing.")) |
| print(run_my_agent("Patient has a limp and is unable to flex right ankle. Ankle is swollen.")) |
| print( |
| run_my_agent( |
| "Patient fell off and landed on their right arm. Their right wrist is swollen, they can still move their " |
| "fingers, and there is only minor pain or discomfort when the wrist is moved or touched." |
| ) |
| ) |