You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@burr.apache.org by gi...@apache.org on 2025/09/14 04:17:13 UTC
(burr) 01/01: Deploy documentation from 447806eeb2c905d88aa85053b3a699b15b4541e6
This is an automated email from the ASF dual-hosted git repository.
github-bot pushed a commit to branch asf-staging
in repository https://gitbox.apache.org/repos/asf/burr.git
commit 282ae53605f8e1a131d439ac1186cc7c22aeba86
Author: GitHub Actions <ac...@github.com>
AuthorDate: Sun Sep 14 04:17:06 2025 +0000
Deploy documentation from 447806eeb2c905d88aa85053b3a699b15b4541e6
---
README.md | 2 +
...8ee9bdd9b240d6bbac4293e45be4129081f762c2dc4.svg | 56 +
...705148b505c95bc44bd8d20762b4408ce2e806f5ff1.svg | 150 ++
...5b657550ebf380b508173891513db90f397233853c1.svg | 153 ++
content/_images/_creating_tests.png | Bin 0 -> 83265 bytes
content/_images/_divide-and-conquer.png | Bin 0 -> 41547 bytes
content/_images/_ml_training.png | Bin 0 -> 18116 bytes
content/_images/_simulation.png | Bin 0 -> 21148 bytes
...8fa622ef6efde3ceab91bbee2248dce41b94b14cff9.svg | 38 +
content/_images/agent_supervisor.png | Bin 0 -> 50186 bytes
content/_images/burr_sdlc.png | Bin 0 -> 38597 bytes
content/_images/chatbot.png | Bin 0 -> 9838 bytes
...c164177df0b8e6967c73e2babf6f81bade4ac0b6dc8.svg | 73 +
content/_images/hierarchical_agent_teams.png | Bin 0 -> 19619 bytes
content/_images/meme.png | Bin 0 -> 460039 bytes
content/_images/multi_agent_collaboration.png | Bin 0 -> 49634 bytes
content/_images/multi_modal_agent.png | Bin 0 -> 22774 bytes
content/_images/parallelism.png | Bin 0 -> 34820 bytes
content/_images/recursive_steps.png | Bin 0 -> 75699 bytes
content/_sources/concepts/actions.rst.txt | 329 +++
.../concepts/additional-visibility.rst.txt | 349 +++
content/_sources/concepts/hooks.rst.txt | 69 +
content/_sources/concepts/index.rst.txt | 28 +
content/_sources/concepts/overview.rst.txt | 14 +
content/_sources/concepts/parallelism.rst.txt | 732 ++++++
.../_sources/concepts/planned-capabilities.rst.txt | 96 +
content/_sources/concepts/recursion.rst.txt | 163 ++
content/_sources/concepts/sdlc.rst.txt | 27 +
content/_sources/concepts/serde.rst.txt | 113 +
content/_sources/concepts/state-machine.rst.txt | 183 ++
.../_sources/concepts/state-persistence.rst.txt | 268 +++
content/_sources/concepts/state-typing.rst.txt | 123 +
content/_sources/concepts/state.rst.txt | 62 +
.../_sources/concepts/streaming-actions.rst.txt | 225 ++
content/_sources/concepts/sync-vs-async.rst.txt | 117 +
content/_sources/concepts/tracking.rst.txt | 114 +
content/_sources/concepts/transitions.rst.txt | 59 +
content/_sources/contributing/architecture.rst.txt | 38 +
content/_sources/contributing/contributing.rst.txt | 85 +
content/_sources/contributing/index.rst.txt | 18 +
content/_sources/contributing/iterating.rst.txt | 77 +
content/_sources/contributing/setup.rst.txt | 55 +
.../_sources/examples/agents/agent-patterns.md.txt | 31 +
.../examples/agents/divide-and-conquer.md.txt | 26 +
content/_sources/examples/agents/index.rst.txt | 11 +
.../examples/chatbots/basic-chatbot.ipynb.txt | 453 ++++
.../examples/chatbots/gpt-like-chatbot.ipynb.txt | 1039 ++++++++
content/_sources/examples/chatbots/index.rst.txt | 10 +
.../chatbots/rag-chatbot-hamilton.ipynb.txt | 801 +++++++
.../_sources/examples/data-science/index.rst.txt | 9 +
.../examples/data-science/ml_training.md.txt | 203 ++
.../examples/data-science/simulation.md.txt | 151 ++
content/_sources/examples/deployment/index.rst.txt | 23 +
.../examples/deployment/infrastructure.rst.txt | 16 +
.../examples/deployment/monitoring.rst.txt | 24 +
.../examples/deployment/web-server.rst.txt | 25 +
.../examples/guardrails/creating_tests.rst.txt | 73 +
content/_sources/examples/guardrails/index.rst.txt | 8 +
content/_sources/examples/index.rst.txt | 20 +
.../simple/choose-your-own-adventure.ipynb.txt | 315 +++
content/_sources/examples/simple/counter.ipynb.txt | 231 ++
content/_sources/examples/simple/cowsay.ipynb.txt | 281 +++
content/_sources/examples/simple/index.rst.txt | 12 +
content/_sources/getting_started/index.rst.txt | 15 +
content/_sources/getting_started/install.rst.txt | 158 ++
.../getting_started/simple-example.rst.txt | 206 ++
content/_sources/getting_started/up-next.rst.txt | 75 +
content/_sources/getting_started/why-burr.rst.txt | 23 +
content/_sources/index.rst.txt | 28 +
content/_sources/main.rst.txt | 92 +
content/_sources/reference/actions.rst.txt | 50 +
content/_sources/reference/application.rst.txt | 39 +
content/_sources/reference/conditions.rst.txt | 14 +
content/_sources/reference/index.rst.txt | 27 +
.../reference/integrations/hamilton.rst.txt | 15 +
.../reference/integrations/haystack.rst.txt | 9 +
.../_sources/reference/integrations/index.rst.txt | 17 +
.../reference/integrations/langchain.rst.txt | 18 +
.../reference/integrations/opentelemetry.rst.txt | 25 +
.../reference/integrations/pydantic.rst.txt | 8 +
.../_sources/reference/integrations/ray.rst.txt | 8 +
.../reference/integrations/streamlit.rst.txt | 31 +
.../reference/integrations/traceloop.rst.txt | 15 +
content/_sources/reference/lifecycle.rst.txt | 60 +
content/_sources/reference/parallelism.rst.txt | 27 +
content/_sources/reference/persister.rst.txt | 144 ++
content/_sources/reference/serde.rst.txt | 50 +
content/_sources/reference/state.rst.txt | 18 +
content/_sources/reference/telemetry.rst.txt | 47 +
content/_sources/reference/tracking.rst.txt | 12 +
content/_sources/reference/typing.rst.txt | 19 +
content/_sources/reference/visibility.rst.txt | 22 +
content/_static/basic.css | 906 +++++++
content/_static/burr_sdlc.png | Bin 0 -> 38597 bytes
content/_static/chatbot.png | Bin 0 -> 9838 bytes
content/_static/custom.css | 5 +
content/_static/debug.css | 69 +
content/_static/demo_graph.png | Bin 0 -> 45104 bytes
content/_static/doctools.js | 149 ++
content/_static/documentation_options.js | 13 +
content/_static/favicon.ico | Bin 0 -> 15086 bytes
content/_static/file.png | Bin 0 -> 286 bytes
content/_static/language_data.js | 192 ++
content/_static/meme.png | Bin 0 -> 460039 bytes
content/_static/minus.png | Bin 0 -> 90 bytes
...f572d296f466b2cfe2517ffebfabe82451661e28f02.css | 2474 ++++++++++++++++++++
content/_static/parallelism.png | Bin 0 -> 34820 bytes
content/_static/plus.png | Bin 0 -> 90 bytes
content/_static/pygments.css | 232 ++
content/_static/recursive_steps.png | Bin 0 -> 75699 bytes
content/_static/scripts/furo-extensions.js | 0
content/_static/scripts/furo.js | 3 +
content/_static/scripts/furo.js.LICENSE.txt | 7 +
content/_static/scripts/furo.js.map | 1 +
content/_static/searchtools.js | 635 +++++
content/_static/skeleton.css | 296 +++
content/_static/sphinx_highlight.js | 154 ++
content/_static/styles/furo-extensions.css | 2 +
content/_static/styles/furo-extensions.css.map | 1 +
content/_static/styles/furo.css | 2 +
content/_static/styles/furo.css.map | 1 +
content/_static/testimonials.css | 48 +
content/concepts/actions/index.html | 718 ++++++
content/concepts/additional-visibility/index.html | 746 ++++++
content/concepts/hooks/index.html | 489 ++++
content/concepts/index.html | 531 +++++
content/concepts/overview/index.html | 439 ++++
content/concepts/parallelism/index.html | 1137 +++++++++
content/concepts/planned-capabilities/index.html | 529 +++++
content/concepts/recursion/index.html | 589 +++++
content/concepts/sdlc/index.html | 450 ++++
content/concepts/serde/index.html | 547 +++++
content/concepts/state-machine/index.html | 601 +++++
content/concepts/state-persistence/index.html | 685 ++++++
content/concepts/state-typing/index.html | 546 +++++
content/concepts/state/index.html | 497 ++++
content/concepts/streaming-actions/index.html | 645 +++++
content/concepts/sync-vs-async/index.html | 582 +++++
content/concepts/tracking/index.html | 554 +++++
content/concepts/transitions/index.html | 492 ++++
content/contributing/architecture/index.html | 491 ++++
content/contributing/contributing/index.html | 514 ++++
content/contributing/index.html | 460 ++++
content/contributing/iterating/index.html | 505 ++++
content/contributing/setup/index.html | 487 ++++
content/examples/agents/agent-patterns/index.html | 475 ++++
.../examples/agents/divide-and-conquer/index.html | 468 ++++
content/examples/agents/index.html | 447 ++++
content/examples/chatbots/basic-chatbot/index.html | 785 +++++++
.../examples/chatbots/gpt-like-chatbot/index.html | 1030 ++++++++
content/examples/chatbots/index.html | 467 ++++
.../chatbots/rag-chatbot-hamilton/index.html | 956 ++++++++
content/examples/data-science/index.html | 449 ++++
.../examples/data-science/ml_training/index.html | 653 ++++++
.../examples/data-science/simulation/index.html | 597 +++++
content/examples/deployment/index.html | 454 ++++
.../examples/deployment/infrastructure/index.html | 461 ++++
content/examples/deployment/monitoring/index.html | 449 ++++
content/examples/deployment/web-server/index.html | 470 ++++
.../examples/guardrails/creating_tests/index.html | 508 ++++
content/examples/guardrails/index.html | 440 ++++
content/examples/index.html | 469 ++++
.../simple/choose-your-own-adventure/index.html | 737 ++++++
content/examples/simple/counter/index.html | 543 +++++
content/examples/simple/cowsay/index.html | 569 +++++
content/examples/simple/index.html | 447 ++++
content/genindex/index.html | 1484 ++++++++++++
content/getting_started/index.html | 439 ++++
content/getting_started/install/index.html | 552 +++++
content/getting_started/simple-example/index.html | 620 +++++
content/getting_started/up-next/index.html | 510 ++++
content/getting_started/why-burr/index.html | 446 ++++
content/index.html | 523 +++++
content/main/index.html | 507 ++++
content/objects.inv | Bin 0 -> 4445 bytes
content/py-modindex/index.html | 447 ++++
content/reference/actions/index.html | 1311 +++++++++++
content/reference/application/index.html | 1950 +++++++++++++++
content/reference/conditions/index.html | 636 +++++
content/reference/index.html | 547 +++++
content/reference/integrations/hamilton/index.html | 538 +++++
content/reference/integrations/haystack/index.html | 492 ++++
content/reference/integrations/index.html | 472 ++++
.../reference/integrations/langchain/index.html | 442 ++++
.../integrations/opentelemetry/index.html | 557 +++++
content/reference/integrations/pydantic/index.html | 435 ++++
content/reference/integrations/ray/index.html | 493 ++++
.../reference/integrations/streamlit/index.html | 583 +++++
.../reference/integrations/traceloop/index.html | 440 ++++
content/reference/lifecycle/index.html | 774 ++++++
content/reference/parallelism/index.html | 1281 ++++++++++
content/reference/persister/index.html | 2337 ++++++++++++++++++
content/reference/serde/index.html | 688 ++++++
content/reference/state/index.html | 687 ++++++
content/reference/telemetry/index.html | 469 ++++
content/reference/tracking/index.html | 697 ++++++
content/reference/typing/index.html | 681 ++++++
content/reference/visibility/index.html | 665 ++++++
content/robots.txt | 3 +
content/search/index.html | 413 ++++
content/searchindex.js | 1 +
content/sitemap.xml | 2 +
202 files changed, 62270 insertions(+)
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..49bc946f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,2 @@
+# Documentation for asf-staging
+This branch contains the built documentation.
diff --git a/content/_images/381ba5fd7b53f739dbd688ee9bdd9b240d6bbac4293e45be4129081f762c2dc4.svg b/content/_images/381ba5fd7b53f739dbd688ee9bdd9b240d6bbac4293e45be4129081f762c2dc4.svg
new file mode 100644
index 00000000..87ed5127
--- /dev/null
+++ b/content/_images/381ba5fd7b53f739dbd688ee9bdd9b240d6bbac4293e45be4129081f762c2dc4.svg
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 12.0.0 (20240704.0754)
+ -->
+<!-- Pages: 1 -->
+<svg width="200pt" height="211pt"
+ viewBox="0.00 0.00 199.93 210.80" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 206.8)">
+<polygon fill="white" stroke="none" points="-4,4 -4,-206.8 195.93,-206.8 195.93,4 -4,4"/>
+<!-- say_nothing -->
+<g id="node1" class="node">
+<title>say_nothing</title>
+<path fill="#b4d8e4" stroke="black" d="M103.35,-202.8C103.35,-202.8 33,-202.8 33,-202.8 27,-202.8 21,-196.8 21,-190.8 21,-190.8 21,-178.2 21,-178.2 21,-172.2 27,-166.2 33,-166.2 33,-166.2 103.35,-166.2 103.35,-166.2 109.35,-166.2 115.35,-172.2 115.35,-178.2 115.35,-178.2 115.35,-190.8 115.35,-190.8 115.35,-196.8 109.35,-202.8 103.35,-202.8"/>
+<text text-anchor="middle" x="68.17" y="-178.7" font-family="Helvetica,sans-Serif" font-size="14.00">say_nothing</text>
+</g>
+<!-- cow_should_speak -->
+<g id="node3" class="node">
+<title>cow_should_speak</title>
+<path fill="#b4d8e4" stroke="black" d="M124.35,-119.7C124.35,-119.7 12,-119.7 12,-119.7 6,-119.7 0,-113.7 0,-107.7 0,-107.7 0,-95.1 0,-95.1 0,-89.1 6,-83.1 12,-83.1 12,-83.1 124.35,-83.1 124.35,-83.1 130.35,-83.1 136.35,-89.1 136.35,-95.1 136.35,-95.1 136.35,-107.7 136.35,-107.7 136.35,-113.7 130.35,-119.7 124.35,-119.7"/>
+<text text-anchor="middle" x="68.17" y="-95.6" font-family="Helvetica,sans-Serif" font-size="14.00">cow_should_speak</text>
+</g>
+<!-- say_nothing->cow_should_speak -->
+<g id="edge4" class="edge">
+<title>say_nothing->cow_should_speak</title>
+<path fill="none" stroke="black" d="M66.88,-165.74C66.59,-161.02 66.32,-155.92 66.17,-151.2 65.95,-143.87 65.95,-142.03 66.17,-134.7 66.21,-133.67 66.24,-132.62 66.29,-131.56"/>
+<polygon fill="black" stroke="black" points="69.78,-131.84 66.8,-121.67 62.79,-131.48 69.78,-131.84"/>
+</g>
+<!-- say_hello -->
+<g id="node2" class="node">
+<title>say_hello</title>
+<path fill="#b4d8e4" stroke="black" d="M95.47,-36.6C95.47,-36.6 40.88,-36.6 40.88,-36.6 34.88,-36.6 28.88,-30.6 28.88,-24.6 28.88,-24.6 28.88,-12 28.88,-12 28.88,-6 34.88,0 40.88,0 40.88,0 95.47,0 95.47,0 101.47,0 107.47,-6 107.47,-12 107.47,-12 107.47,-24.6 107.47,-24.6 107.47,-30.6 101.47,-36.6 95.47,-36.6"/>
+<text text-anchor="middle" x="68.17" y="-12.5" font-family="Helvetica,sans-Serif" font-size="14.00">say_hello</text>
+</g>
+<!-- say_hello->cow_should_speak -->
+<g id="edge2" class="edge">
+<title>say_hello->cow_should_speak</title>
+<path fill="none" stroke="black" d="M100.96,-36.93C105.88,-41.07 110.28,-45.95 113.17,-51.6 116.52,-58.12 116.52,-61.58 113.17,-68.1 112,-70.39 110.57,-72.56 108.97,-74.6"/>
+<polygon fill="black" stroke="black" points="106.52,-72.1 102.02,-81.69 111.52,-77 106.52,-72.1"/>
+</g>
+<!-- cow_should_speak->say_nothing -->
+<g id="edge3" class="edge">
+<title>cow_should_speak->say_nothing</title>
+<path fill="none" stroke="black" stroke-dasharray="5,2" d="M68.17,-119.96C68.17,-130.05 68.17,-142.98 68.17,-154.53"/>
+<polygon fill="black" stroke="black" points="64.68,-154.39 68.18,-164.39 71.68,-154.39 64.68,-154.39"/>
+<text text-anchor="middle" x="130.05" y="-137.9" font-family="Times,serif" font-size="14.00">not cow_should_speak</text>
+</g>
+<!-- cow_should_speak->say_hello -->
+<g id="edge1" class="edge">
+<title>cow_should_speak->say_hello</title>
+<path fill="none" stroke="black" stroke-dasharray="5,2" d="M25.76,-82.82C12.58,-74.62 3.19,-63.94 10.42,-51.6 12.84,-47.48 16,-43.84 19.6,-40.64"/>
+<polygon fill="black" stroke="black" points="21.46,-43.61 27.42,-34.85 17.3,-37.99 21.46,-43.61"/>
+<text text-anchor="middle" x="62.55" y="-54.8" font-family="Times,serif" font-size="14.00">cow_should_speak</text>
+</g>
+</g>
+</svg>
\ No newline at end of file
diff --git a/content/_images/7baf196d9ea191cc9f552705148b505c95bc44bd8d20762b4408ce2e806f5ff1.svg b/content/_images/7baf196d9ea191cc9f552705148b505c95bc44bd8d20762b4408ce2e806f5ff1.svg
new file mode 100644
index 00000000..f3f54b07
--- /dev/null
+++ b/content/_images/7baf196d9ea191cc9f552705148b505c95bc44bd8d20762b4408ce2e806f5ff1.svg
@@ -0,0 +1,150 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 12.0.0 (20240704.0754)
+ -->
+<!-- Pages: 1 -->
+<svg width="497pt" height="304pt"
+ viewBox="0.00 0.00 497.41 304.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 300)">
+<polygon fill="white" stroke="none" points="-4,4 -4,-300 493.41,-300 493.41,4 -4,4"/>
+<!-- prompt -->
+<g id="node1" class="node">
+<title>prompt</title>
+<path fill="none" stroke="black" d="M408,-231C408,-231 376.25,-231 376.25,-231 370.25,-231 364.25,-225 364.25,-219 364.25,-219 364.25,-207 364.25,-207 364.25,-201 370.25,-195 376.25,-195 376.25,-195 408,-195 408,-195 414,-195 420,-201 420,-207 420,-207 420,-219 420,-219 420,-225 414,-231 408,-231"/>
+<text text-anchor="middle" x="392.12" y="-207.95" font-family="Times,serif" font-size="14.00">prompt</text>
+</g>
+<!-- decide_mode -->
+<g id="node3" class="node">
+<title>decide_mode</title>
+<path fill="none" stroke="black" d="M318.5,-166C318.5,-166 253.75,-166 253.75,-166 247.75,-166 241.75,-160 241.75,-154 241.75,-154 241.75,-142 241.75,-142 241.75,-136 247.75,-130 253.75,-130 253.75,-130 318.5,-130 318.5,-130 324.5,-130 330.5,-136 330.5,-142 330.5,-142 330.5,-154 330.5,-154 330.5,-160 324.5,-166 318.5,-166"/>
+<text text-anchor="middle" x="286.12" y="-142.95" font-family="Times,serif" font-size="14.00">decide_mode</text>
+</g>
+<!-- prompt->decide_mode -->
+<g id="edge5" class="edge">
+<title>prompt->decide_mode</title>
+<path fill="none" stroke="black" d="M364,-195.28C352.07,-188.19 337.97,-179.82 325.07,-172.15"/>
+<polygon fill="black" stroke="black" points="327.18,-169.33 316.8,-167.23 323.6,-175.35 327.18,-169.33"/>
+</g>
+<!-- input__prompt -->
+<g id="node2" class="node">
+<title>input__prompt</title>
+<ellipse fill="none" stroke="black" stroke-dasharray="5,2" cx="392.12" cy="-278" rx="62.1" ry="18"/>
+<text text-anchor="middle" x="392.12" y="-272.95" font-family="Times,serif" font-size="14.00">input: prompt</text>
+</g>
+<!-- input__prompt->prompt -->
+<g id="edge1" class="edge">
+<title>input__prompt->prompt</title>
+<path fill="none" stroke="black" d="M392.12,-259.78C392.12,-254.37 392.12,-248.24 392.12,-242.26"/>
+<polygon fill="black" stroke="black" points="395.63,-242.71 392.13,-232.71 388.63,-242.71 395.63,-242.71"/>
+</g>
+<!-- generate_image -->
+<g id="node4" class="node">
+<title>generate_image</title>
+<path fill="none" stroke="black" d="M90.25,-101C90.25,-101 12,-101 12,-101 6,-101 0,-95 0,-89 0,-89 0,-77 0,-77 0,-71 6,-65 12,-65 12,-65 90.25,-65 90.25,-65 96.25,-65 102.25,-71 102.25,-77 102.25,-77 102.25,-89 102.25,-89 102.25,-95 96.25,-101 90.25,-101"/>
+<text text-anchor="middle" x="51.12" y="-77.95" font-family="Times,serif" font-size="14.00">generate_image</text>
+</g>
+<!-- decide_mode->generate_image -->
+<g id="edge6" class="edge">
+<title>decide_mode->generate_image</title>
+<path fill="none" stroke="black" stroke-dasharray="5,2" d="M241.38,-132.38C238.59,-131.55 235.82,-130.75 233.12,-130 182.69,-115.94 166.94,-115.1 113.33,-101.5"/>
+<polygon fill="black" stroke="black" points="114.5,-98.19 103.94,-99.08 112.75,-104.96 114.5,-98.19"/>
+</g>
+<!-- generate_code -->
+<g id="node6" class="node">
+<title>generate_code</title>
+<path fill="none" stroke="black" d="M202.12,-101C202.12,-101 132.12,-101 132.12,-101 126.12,-101 120.12,-95 120.12,-89 120.12,-89 120.12,-77 120.12,-77 120.12,-71 126.12,-65 132.12,-65 132.12,-65 202.12,-65 202.12,-65 208.12,-65 214.12,-71 214.12,-77 214.12,-77 214.12,-89 214.12,-89 214.12,-95 208.12,-101 202.12,-101"/>
+<text text-anchor="middle" x="167.12" y="-77.95" font-family="Times,serif" font-size="14.00">generate_code</text>
+</g>
+<!-- decide_mode->generate_code -->
+<g id="edge7" class="edge">
+<title>decide_mode->generate_code</title>
+<path fill="none" stroke="black" stroke-dasharray="5,2" d="M253.29,-129.62C239.9,-122.53 224.22,-114.23 209.95,-106.67"/>
+<polygon fill="black" stroke="black" points="211.66,-103.62 201.19,-102.03 208.39,-109.81 211.66,-103.62"/>
+</g>
+<!-- answer_question -->
+<g id="node7" class="node">
+<title>answer_question</title>
+<path fill="none" stroke="black" d="M326.5,-101C326.5,-101 243.75,-101 243.75,-101 237.75,-101 231.75,-95 231.75,-89 231.75,-89 231.75,-77 231.75,-77 231.75,-71 237.75,-65 243.75,-65 243.75,-65 326.5,-65 326.5,-65 332.5,-65 338.5,-71 338.5,-77 338.5,-77 338.5,-89 338.5,-89 338.5,-95 332.5,-101 326.5,-101"/>
+<text text-anchor="middle" x="285.12" y="-77.95" font-family="Times,serif" font-size="14.00">answer_question</text>
+</g>
+<!-- decide_mode->answer_question -->
+<g id="edge8" class="edge">
+<title>decide_mode->answer_question</title>
+<path fill="none" stroke="black" stroke-dasharray="5,2" d="M285.85,-129.78C285.77,-124.37 285.67,-118.24 285.57,-112.26"/>
+<polygon fill="black" stroke="black" points="289.08,-112.65 285.42,-102.71 282.08,-112.76 289.08,-112.65"/>
+</g>
+<!-- prompt_for_more -->
+<g id="node8" class="node">
+<title>prompt_for_more</title>
+<path fill="none" stroke="black" d="M457.88,-101C457.88,-101 368.38,-101 368.38,-101 362.38,-101 356.38,-95 356.38,-89 356.38,-89 356.38,-77 356.38,-77 356.38,-71 362.38,-65 368.38,-65 368.38,-65 457.88,-65 457.88,-65 463.88,-65 469.88,-71 469.88,-77 469.88,-77 469.88,-89 469.88,-89 469.88,-95 463.88,-101 457.88,-101"/>
+<text text-anchor="middle" x="413.12" y="-77.95" font-family="Times,serif" font-size="14.00">prompt_for_more</text>
+</g>
+<!-- decide_mode->prompt_for_more -->
+<g id="edge9" class="edge">
+<title>decide_mode->prompt_for_more</title>
+<path fill="none" stroke="black" d="M321.16,-129.62C335.74,-122.39 352.84,-113.91 368.31,-106.23"/>
+<polygon fill="black" stroke="black" points="369.44,-109.58 376.84,-102 366.33,-103.31 369.44,-109.58"/>
+</g>
+<!-- response -->
+<g id="node9" class="node">
+<title>response</title>
+<path fill="none" stroke="black" d="M304.75,-36C304.75,-36 265.5,-36 265.5,-36 259.5,-36 253.5,-30 253.5,-24 253.5,-24 253.5,-12 253.5,-12 253.5,-6 259.5,0 265.5,0 265.5,0 304.75,0 304.75,0 310.75,0 316.75,-6 316.75,-12 316.75,-12 316.75,-24 316.75,-24 316.75,-30 310.75,-36 304.75,-36"/>
+<text text-anchor="middle" x="285.12" y="-12.95" font-family="Times,serif" font-size="14.00">response</text>
+</g>
+<!-- generate_image->response -->
+<g id="edge10" class="edge">
+<title>generate_image->response</title>
+<path fill="none" stroke="black" d="M102.5,-67.38C105.41,-66.57 108.31,-65.77 111.12,-65 155.72,-52.79 206.9,-39.32 242.15,-30.14"/>
+<polygon fill="black" stroke="black" points="242.75,-33.6 251.55,-27.69 240.99,-26.82 242.75,-33.6"/>
+</g>
+<!-- input__model -->
+<g id="node5" class="node">
+<title>input__model</title>
+<ellipse fill="none" stroke="black" stroke-dasharray="5,2" cx="165.12" cy="-148" rx="58.52" ry="18"/>
+<text text-anchor="middle" x="165.12" y="-142.95" font-family="Times,serif" font-size="14.00">input: model</text>
+</g>
+<!-- input__model->generate_image -->
+<g id="edge2" class="edge">
+<title>input__model->generate_image</title>
+<path fill="none" stroke="black" d="M137.53,-131.75C124.04,-124.29 107.52,-115.16 92.55,-106.89"/>
+<polygon fill="black" stroke="black" points="94.26,-103.84 83.82,-102.07 90.88,-109.97 94.26,-103.84"/>
+</g>
+<!-- input__model->generate_code -->
+<g id="edge3" class="edge">
+<title>input__model->generate_code</title>
+<path fill="none" stroke="black" d="M165.67,-129.78C165.84,-124.37 166.04,-118.24 166.23,-112.26"/>
+<polygon fill="black" stroke="black" points="169.71,-112.81 166.53,-102.71 162.72,-112.59 169.71,-112.81"/>
+</g>
+<!-- input__model->answer_question -->
+<g id="edge4" class="edge">
+<title>input__model->answer_question</title>
+<path fill="none" stroke="black" d="M193.87,-131.91C208.2,-124.38 225.84,-115.13 241.77,-106.76"/>
+<polygon fill="black" stroke="black" points="243.38,-109.87 250.6,-102.12 240.12,-103.67 243.38,-109.87"/>
+</g>
+<!-- generate_code->response -->
+<g id="edge12" class="edge">
+<title>generate_code->response</title>
+<path fill="none" stroke="black" d="M199.68,-64.62C213.08,-57.47 228.76,-49.09 243.01,-41.48"/>
+<polygon fill="black" stroke="black" points="244.57,-44.62 251.75,-36.82 241.28,-38.44 244.57,-44.62"/>
+</g>
+<!-- answer_question->response -->
+<g id="edge11" class="edge">
+<title>answer_question->response</title>
+<path fill="none" stroke="black" d="M285.12,-64.78C285.12,-59.37 285.12,-53.24 285.12,-47.26"/>
+<polygon fill="black" stroke="black" points="288.63,-47.71 285.13,-37.71 281.63,-47.71 288.63,-47.71"/>
+</g>
+<!-- prompt_for_more->response -->
+<g id="edge13" class="edge">
+<title>prompt_for_more->response</title>
+<path fill="none" stroke="black" d="M377.81,-64.62C362,-56.84 343.24,-47.61 326.76,-39.49"/>
+<polygon fill="black" stroke="black" points="328.7,-36.55 318.19,-35.27 325.61,-42.83 328.7,-36.55"/>
+</g>
+<!-- response->prompt -->
+<g id="edge14" class="edge">
+<title>response->prompt</title>
+<path fill="none" stroke="black" d="M317.03,-21.86C366.8,-27.05 459.27,-39.88 479.12,-65 510.58,-104.78 461.19,-157.01 424.93,-187.3"/>
+<polygon fill="black" stroke="black" points="422.78,-184.54 417.22,-193.56 427.19,-189.97 422.78,-184.54"/>
+</g>
+</g>
+</svg>
\ No newline at end of file
diff --git a/content/_images/961ccf2d21bb45ddc5a735b657550ebf380b508173891513db90f397233853c1.svg b/content/_images/961ccf2d21bb45ddc5a735b657550ebf380b508173891513db90f397233853c1.svg
new file mode 100644
index 00000000..632659d2
--- /dev/null
+++ b/content/_images/961ccf2d21bb45ddc5a735b657550ebf380b508173891513db90f397233853c1.svg
@@ -0,0 +1,153 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 12.0.0 (20240704.0754)
+ -->
+<!-- Pages: 1 -->
+<svg width="576pt" height="328pt"
+ viewBox="0.00 0.00 575.76 327.50" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 323.5)">
+<polygon fill="white" stroke="none" points="-4,4 -4,-323.5 571.76,-323.5 571.76,4 -4,4"/>
+<!-- prompt -->
+<g id="node1" class="node">
+<title>prompt</title>
+<path fill="none" stroke="black" d="M505.39,-252.5C505.39,-252.5 473.64,-252.5 473.64,-252.5 467.64,-252.5 461.64,-246.5 461.64,-240.5 461.64,-240.5 461.64,-228.5 461.64,-228.5 461.64,-222.5 467.64,-216.5 473.64,-216.5 473.64,-216.5 505.39,-216.5 505.39,-216.5 511.39,-216.5 517.39,-222.5 517.39,-228.5 517.39,-228.5 517.39,-240.5 517.39,-240.5 517.39,-246.5 511.39,-252.5 505.39,-252.5"/>
+<text text-anchor="middle" x="489.52" y="-229.45" font-family="Times,serif" font-size="14.00">prompt</text>
+</g>
+<!-- decide_mode -->
+<g id="node3" class="node">
+<title>decide_mode</title>
+<path fill="none" stroke="black" d="M436.89,-185.5C436.89,-185.5 372.14,-185.5 372.14,-185.5 366.14,-185.5 360.14,-179.5 360.14,-173.5 360.14,-173.5 360.14,-161.5 360.14,-161.5 360.14,-155.5 366.14,-149.5 372.14,-149.5 372.14,-149.5 436.89,-149.5 436.89,-149.5 442.89,-149.5 448.89,-155.5 448.89,-161.5 448.89,-161.5 448.89,-173.5 448.89,-173.5 448.89,-179.5 442.89,-185.5 436.89,-185.5"/>
+<text text-anchor="middle" x="404.52" y="-162.45" font-family="Times,serif" font-size="14.00">decide_mode</text>
+</g>
+<!-- prompt->decide_mode -->
+<g id="edge5" class="edge">
+<title>prompt->decide_mode</title>
+<path fill="none" stroke="black" d="M466.74,-216.08C457.4,-208.94 446.43,-200.55 436.33,-192.82"/>
+<polygon fill="black" stroke="black" points="438.63,-190.18 428.56,-186.89 434.38,-195.74 438.63,-190.18"/>
+</g>
+<!-- input__prompt -->
+<g id="node2" class="node">
+<title>input__prompt</title>
+<ellipse fill="none" stroke="black" stroke-dasharray="5,2" cx="489.52" cy="-301.5" rx="62.1" ry="18"/>
+<text text-anchor="middle" x="489.52" y="-296.45" font-family="Times,serif" font-size="14.00">input: prompt</text>
+</g>
+<!-- input__prompt->prompt -->
+<g id="edge1" class="edge">
+<title>input__prompt->prompt</title>
+<path fill="none" stroke="black" d="M489.52,-283.08C489.52,-277.25 489.52,-270.59 489.52,-264.14"/>
+<polygon fill="black" stroke="black" points="493.02,-264.48 489.52,-254.48 486.02,-264.48 493.02,-264.48"/>
+</g>
+<!-- generate_image -->
+<g id="node4" class="node">
+<title>generate_image</title>
+<path fill="none" stroke="black" d="M130.64,-103C130.64,-103 52.39,-103 52.39,-103 46.39,-103 40.39,-97 40.39,-91 40.39,-91 40.39,-79 40.39,-79 40.39,-73 46.39,-67 52.39,-67 52.39,-67 130.64,-67 130.64,-67 136.64,-67 142.64,-73 142.64,-79 142.64,-79 142.64,-91 142.64,-91 142.64,-97 136.64,-103 130.64,-103"/>
+<text text-anchor="middle" x="91.52" y="-79.95" font-family="Times,serif" font-size="14.00">generate_image</text>
+</g>
+<!-- decide_mode->generate_image -->
+<g id="edge6" class="edge">
+<title>decide_mode->generate_image</title>
+<path fill="none" stroke="black" stroke-dasharray="5,2" d="M359.79,-166.05C290.41,-164.61 160.83,-158.64 122.27,-134.5 114.19,-129.44 107.85,-121.39 103.09,-113.3"/>
+<polygon fill="black" stroke="black" points="106.28,-111.85 98.57,-104.58 100.06,-115.07 106.28,-111.85"/>
+<text text-anchor="middle" x="185.14" y="-121.2" font-family="Times,serif" font-size="14.00">mode=generate_image</text>
+</g>
+<!-- generate_code -->
+<g id="node6" class="node">
+<title>generate_code</title>
+<path fill="none" stroke="black" d="M278.52,-103C278.52,-103 208.52,-103 208.52,-103 202.52,-103 196.52,-97 196.52,-91 196.52,-91 196.52,-79 196.52,-79 196.52,-73 202.52,-67 208.52,-67 208.52,-67 278.52,-67 278.52,-67 284.52,-67 290.52,-73 290.52,-79 290.52,-79 290.52,-91 290.52,-91 290.52,-97 284.52,-103 278.52,-103"/>
+<text text-anchor="middle" x="243.52" y="-79.95" font-family="Times,serif" font-size="14.00">generate_code</text>
+</g>
+<!-- decide_mode->generate_code -->
+<g id="edge7" class="edge">
+<title>decide_mode->generate_code</title>
+<path fill="none" stroke="black" stroke-dasharray="5,2" d="M359.74,-161.19C323.45,-156.06 276.03,-147.17 261.52,-134.5 255.43,-129.18 251.39,-121.61 248.72,-114.02"/>
+<polygon fill="black" stroke="black" points="252.16,-113.31 246.11,-104.61 245.41,-115.18 252.16,-113.31"/>
+<text text-anchor="middle" x="320.02" y="-121.2" font-family="Times,serif" font-size="14.00">mode=generate_code</text>
+</g>
+<!-- answer_question -->
+<g id="node7" class="node">
+<title>answer_question</title>
+<path fill="none" stroke="black" d="M402.89,-103C402.89,-103 320.14,-103 320.14,-103 314.14,-103 308.14,-97 308.14,-91 308.14,-91 308.14,-79 308.14,-79 308.14,-73 314.14,-67 320.14,-67 320.14,-67 402.89,-67 402.89,-67 408.89,-67 414.89,-73 414.89,-79 414.89,-79 414.89,-91 414.89,-91 414.89,-97 408.89,-103 402.89,-103"/>
+<text text-anchor="middle" x="361.52" y="-79.95" font-family="Times,serif" font-size="14.00">answer_question</text>
+</g>
+<!-- decide_mode->answer_question -->
+<g id="edge8" class="edge">
+<title>decide_mode->answer_question</title>
+<path fill="none" stroke="black" stroke-dasharray="5,2" d="M397.41,-149.12C393.38,-139.74 388.05,-128.04 382.52,-118 381.59,-116.32 380.61,-114.61 379.59,-112.9"/>
+<polygon fill="black" stroke="black" points="382.65,-111.2 374.38,-104.59 376.72,-114.92 382.65,-111.2"/>
+<text text-anchor="middle" x="454.39" y="-121.2" font-family="Times,serif" font-size="14.00">mode=answer_question</text>
+</g>
+<!-- prompt_for_more -->
+<g id="node8" class="node">
+<title>prompt_for_more</title>
+<path fill="none" stroke="black" d="M534.27,-103C534.27,-103 444.77,-103 444.77,-103 438.77,-103 432.77,-97 432.77,-91 432.77,-91 432.77,-79 432.77,-79 432.77,-73 438.77,-67 444.77,-67 444.77,-67 534.27,-67 534.27,-67 540.27,-67 546.27,-73 546.27,-79 546.27,-79 546.27,-91 546.27,-91 546.27,-97 540.27,-103 534.27,-103"/>
+<text text-anchor="middle" x="489.52" y="-79.95" font-family="Times,serif" font-size="14.00">prompt_for_more</text>
+</g>
+<!-- decide_mode->prompt_for_more -->
+<g id="edge9" class="edge">
+<title>decide_mode->prompt_for_more</title>
+<path fill="none" stroke="black" d="M449.11,-160.23C478.92,-155.02 514.31,-146.51 523.52,-134.5 529.09,-127.23 526.67,-119.14 521.13,-111.69"/>
+<polygon fill="black" stroke="black" points="523.84,-109.47 514.52,-104.43 518.67,-114.18 523.84,-109.47"/>
+</g>
+<!-- response -->
+<g id="node9" class="node">
+<title>response</title>
+<path fill="none" stroke="black" d="M381.14,-36C381.14,-36 341.89,-36 341.89,-36 335.89,-36 329.89,-30 329.89,-24 329.89,-24 329.89,-12 329.89,-12 329.89,-6 335.89,0 341.89,0 341.89,0 381.14,0 381.14,0 387.14,0 393.14,-6 393.14,-12 393.14,-12 393.14,-24 393.14,-24 393.14,-30 387.14,-36 381.14,-36"/>
+<text text-anchor="middle" x="361.52" y="-12.95" font-family="Times,serif" font-size="14.00">response</text>
+</g>
+<!-- generate_image->response -->
+<g id="edge10" class="edge">
+<title>generate_image->response</title>
+<path fill="none" stroke="black" d="M142.94,-71.62C193.82,-59.37 270.83,-40.83 318.55,-29.34"/>
+<polygon fill="black" stroke="black" points="319.35,-32.75 328.25,-27.01 317.71,-25.95 319.35,-32.75"/>
+</g>
+<!-- input__model -->
+<g id="node5" class="node">
+<title>input__model</title>
+<ellipse fill="none" stroke="black" stroke-dasharray="5,2" cx="58.52" cy="-167.5" rx="58.52" ry="18"/>
+<text text-anchor="middle" x="58.52" y="-162.45" font-family="Times,serif" font-size="14.00">input: model</text>
+</g>
+<!-- input__model->generate_image -->
+<g id="edge2" class="edge">
+<title>input__model->generate_image</title>
+<path fill="none" stroke="black" d="M49.26,-149.64C45.25,-139.97 42.5,-127.85 47.52,-118 48.79,-115.5 50.36,-113.14 52.15,-110.94"/>
+<polygon fill="black" stroke="black" points="54.25,-113.79 58.83,-104.24 49.29,-108.85 54.25,-113.79"/>
+</g>
+<!-- input__model->generate_code -->
+<g id="edge3" class="edge">
+<title>input__model->generate_code</title>
+<path fill="none" stroke="black" d="M59.1,-149.33C60.4,-138.81 63.82,-125.9 72.52,-118 72.79,-117.75 135.74,-106.02 185.17,-96.83"/>
+<polygon fill="black" stroke="black" points="185.76,-100.28 194.95,-95.01 184.48,-93.4 185.76,-100.28"/>
+</g>
+<!-- input__model->answer_question -->
+<g id="edge4" class="edge">
+<title>input__model->answer_question</title>
+<path fill="none" stroke="black" d="M111.9,-159.9C143.3,-155.01 183.4,-146.98 217.52,-134.5 231.81,-129.27 233.43,-123.76 247.52,-118 250.2,-116.9 272.65,-110.56 296.93,-103.81"/>
+<polygon fill="black" stroke="black" points="297.62,-107.25 306.32,-101.2 295.74,-100.5 297.62,-107.25"/>
+</g>
+<!-- generate_code->response -->
+<g id="edge12" class="edge">
+<title>generate_code->response</title>
+<path fill="none" stroke="black" d="M275.14,-66.58C288.89,-59 305.2,-50.02 319.92,-41.91"/>
+<polygon fill="black" stroke="black" points="321.41,-45.09 328.48,-37.2 318.03,-38.96 321.41,-45.09"/>
+</g>
+<!-- answer_question->response -->
+<g id="edge11" class="edge">
+<title>answer_question->response</title>
+<path fill="none" stroke="black" d="M361.52,-66.58C361.52,-60.75 361.52,-54.09 361.52,-47.64"/>
+<polygon fill="black" stroke="black" points="365.02,-47.98 361.52,-37.98 358.02,-47.98 365.02,-47.98"/>
+</g>
+<!-- prompt_for_more->response -->
+<g id="edge13" class="edge">
+<title>prompt_for_more->response</title>
+<path fill="none" stroke="black" d="M455.22,-66.58C439.27,-58.48 420.17,-48.78 403.38,-40.26"/>
+<polygon fill="black" stroke="black" points="405.13,-37.22 394.62,-35.81 401.96,-43.46 405.13,-37.22"/>
+</g>
+<!-- response->prompt -->
+<g id="edge14" class="edge">
+<title>response->prompt</title>
+<path fill="none" stroke="black" d="M393.44,-22.2C443.23,-27.91 535.74,-41.73 555.52,-67 589.35,-110.23 545.13,-173.01 514.42,-207.83"/>
+<polygon fill="black" stroke="black" points="512.11,-205.17 507.98,-214.93 517.29,-209.87 512.11,-205.17"/>
+</g>
+</g>
+</svg>
\ No newline at end of file
diff --git a/content/_images/_creating_tests.png b/content/_images/_creating_tests.png
new file mode 100644
index 00000000..4fe992f7
Binary files /dev/null and b/content/_images/_creating_tests.png differ
diff --git a/content/_images/_divide-and-conquer.png b/content/_images/_divide-and-conquer.png
new file mode 100644
index 00000000..b4490c04
Binary files /dev/null and b/content/_images/_divide-and-conquer.png differ
diff --git a/content/_images/_ml_training.png b/content/_images/_ml_training.png
new file mode 100644
index 00000000..4b9fdb42
Binary files /dev/null and b/content/_images/_ml_training.png differ
diff --git a/content/_images/_simulation.png b/content/_images/_simulation.png
new file mode 100644
index 00000000..d2d45f02
Binary files /dev/null and b/content/_images/_simulation.png differ
diff --git a/content/_images/a749e8885585ee2b150f28fa622ef6efde3ceab91bbee2248dce41b94b14cff9.svg b/content/_images/a749e8885585ee2b150f28fa622ef6efde3ceab91bbee2248dce41b94b14cff9.svg
new file mode 100644
index 00000000..6bbf1737
--- /dev/null
+++ b/content/_images/a749e8885585ee2b150f28fa622ef6efde3ceab91bbee2248dce41b94b14cff9.svg
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.43.0 (0)
+ -->
+<!-- Title: %3 Pages: 1 -->
+<svg width="220pt" height="113pt"
+ viewBox="0.00 0.00 220.00 113.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 109)">
+<title>%3</title>
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-109 216,-109 216,4 -4,4"/>
+<!-- increment -->
+<g id="node1" class="node">
+<title>increment</title>
+<path fill="#b4d8e4" stroke="black" d="M88,-105C88,-105 20,-105 20,-105 14,-105 8,-99 8,-93 8,-93 8,-80 8,-80 8,-74 14,-68 20,-68 20,-68 88,-68 88,-68 94,-68 100,-74 100,-80 100,-80 100,-93 100,-93 100,-99 94,-105 88,-105"/>
+<text text-anchor="middle" x="54" y="-82.8" font-family="Helvetica,sans-Serif" font-size="14.00">increment</text>
+</g>
+<!-- increment->increment -->
+<g id="edge1" class="edge">
+<title>increment->increment</title>
+<path fill="none" stroke="black" stroke-dasharray="5,2" d="M100.02,-92.72C110.41,-92.16 118,-90.09 118,-86.5 118,-84.26 115.03,-82.61 110.27,-81.55"/>
+<polygon fill="black" stroke="black" points="110.38,-78.04 100.02,-80.28 109.52,-84.98 110.38,-78.04"/>
+<text text-anchor="middle" x="165" y="-82.8" font-family="Times,serif" font-size="14.00">counter < 10</text>
+</g>
+<!-- exit_counter -->
+<g id="node2" class="node">
+<title>exit_counter</title>
+<path fill="#b4d8e4" stroke="black" d="M96,-37C96,-37 12,-37 12,-37 6,-37 0,-31 0,-25 0,-25 0,-12 0,-12 0,-6 6,0 12,0 12,0 96,0 96,0 102,0 108,-6 108,-12 108,-12 108,-25 108,-25 108,-31 102,-37 96,-37"/>
+<text text-anchor="middle" x="54" y="-14.8" font-family="Helvetica,sans-Serif" font-size="14.00">exit_counter</text>
+</g>
+<!-- increment->exit_counter -->
+<g id="edge2" class="edge">
+<title>increment->exit_counter</title>
+<path fill="none" stroke="black" d="M54,-67.81C54,-61.6 54,-54.45 54,-47.6"/>
+<polygon fill="black" stroke="black" points="57.5,-47.24 54,-37.24 50.5,-47.24 57.5,-47.24"/>
+</g>
+</g>
+</svg>
\ No newline at end of file
diff --git a/content/_images/agent_supervisor.png b/content/_images/agent_supervisor.png
new file mode 100644
index 00000000..f6a90ba9
Binary files /dev/null and b/content/_images/agent_supervisor.png differ
diff --git a/content/_images/burr_sdlc.png b/content/_images/burr_sdlc.png
new file mode 100644
index 00000000..d27c1155
Binary files /dev/null and b/content/_images/burr_sdlc.png differ
diff --git a/content/_images/chatbot.png b/content/_images/chatbot.png
new file mode 100644
index 00000000..c742c2d2
Binary files /dev/null and b/content/_images/chatbot.png differ
diff --git a/content/_images/f52a61a867091a818e1acc164177df0b8e6967c73e2babf6f81bade4ac0b6dc8.svg b/content/_images/f52a61a867091a818e1acc164177df0b8e6967c73e2babf6f81bade4ac0b6dc8.svg
new file mode 100644
index 00000000..2658ed47
--- /dev/null
+++ b/content/_images/f52a61a867091a818e1acc164177df0b8e6967c73e2babf6f81bade4ac0b6dc8.svg
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 12.0.0 (20240704.0754)
+ -->
+<!-- Pages: 1 -->
+<svg width="194pt" height="331pt"
+ viewBox="0.00 0.00 193.93 330.50" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 326.5)">
+<polygon fill="white" stroke="none" points="-4,4 -4,-326.5 189.93,-326.5 189.93,4 -4,4"/>
+<!-- start -->
+<g id="node1" class="node">
+<title>start</title>
+<path fill="#b4d8e4" stroke="black" d="M122.93,-322.5C122.93,-322.5 92.92,-322.5 92.92,-322.5 86.92,-322.5 80.92,-316.5 80.92,-310.5 80.92,-310.5 80.92,-297.9 80.92,-297.9 80.92,-291.9 86.92,-285.9 92.92,-285.9 92.92,-285.9 122.93,-285.9 122.93,-285.9 128.93,-285.9 134.93,-291.9 134.93,-297.9 134.93,-297.9 134.93,-310.5 134.93,-310.5 134.93,-316.5 128.93,-322.5 122.93,-322.5"/>
+<text text-anchor="middle" x="107.92" y="-298.4" font-family="Helvetica,sans-Serif" font-size="14.00">start</text>
+</g>
+<!-- prompt_for_challenge -->
+<g id="node2" class="node">
+<title>prompt_for_challenge</title>
+<path fill="#b4d8e4" stroke="black" d="M172.35,-254.9C172.35,-254.9 43.5,-254.9 43.5,-254.9 37.5,-254.9 31.5,-248.9 31.5,-242.9 31.5,-242.9 31.5,-230.3 31.5,-230.3 31.5,-224.3 37.5,-218.3 43.5,-218.3 43.5,-218.3 172.35,-218.3 172.35,-218.3 178.35,-218.3 184.35,-224.3 184.35,-230.3 184.35,-230.3 184.35,-242.9 184.35,-242.9 184.35,-248.9 178.35,-254.9 172.35,-254.9"/>
+<text text-anchor="middle" x="107.92" y="-230.8" font-family="Helvetica,sans-Serif" font-size="14.00">prompt_for_challenge</text>
+</g>
+<!-- start->prompt_for_challenge -->
+<g id="edge1" class="edge">
+<title>start->prompt_for_challenge</title>
+<path fill="none" stroke="black" d="M107.92,-285.62C107.92,-279.74 107.92,-273.02 107.92,-266.5"/>
+<polygon fill="black" stroke="black" points="111.43,-266.74 107.93,-256.74 104.43,-266.74 111.43,-266.74"/>
+</g>
+<!-- evaluate_attempt -->
+<g id="node3" class="node">
+<title>evaluate_attempt</title>
+<path fill="#b4d8e4" stroke="black" d="M113.85,-187.3C113.85,-187.3 12,-187.3 12,-187.3 6,-187.3 0,-181.3 0,-175.3 0,-175.3 0,-162.7 0,-162.7 0,-156.7 6,-150.7 12,-150.7 12,-150.7 113.85,-150.7 113.85,-150.7 119.85,-150.7 125.85,-156.7 125.85,-162.7 125.85,-162.7 125.85,-175.3 125.85,-175.3 125.85,-181.3 119.85,-187.3 113.85,-187.3"/>
+<text text-anchor="middle" x="62.92" y="-163.2" font-family="Helvetica,sans-Serif" font-size="14.00">evaluate_attempt</text>
+</g>
+<!-- prompt_for_challenge->evaluate_attempt -->
+<g id="edge2" class="edge">
+<title>prompt_for_challenge->evaluate_attempt</title>
+<path fill="none" stroke="black" d="M95.87,-218.02C91.43,-211.55 86.29,-204.06 81.42,-196.96"/>
+<polygon fill="black" stroke="black" points="84.42,-195.14 75.87,-188.88 78.64,-199.1 84.42,-195.14"/>
+</g>
+<!-- maybe_progress -->
+<g id="node4" class="node">
+<title>maybe_progress</title>
+<path fill="#b4d8e4" stroke="black" d="M157.35,-119.7C157.35,-119.7 58.5,-119.7 58.5,-119.7 52.5,-119.7 46.5,-113.7 46.5,-107.7 46.5,-107.7 46.5,-95.1 46.5,-95.1 46.5,-89.1 52.5,-83.1 58.5,-83.1 58.5,-83.1 157.35,-83.1 157.35,-83.1 163.35,-83.1 169.35,-89.1 169.35,-95.1 169.35,-95.1 169.35,-107.7 169.35,-107.7 169.35,-113.7 163.35,-119.7 157.35,-119.7"/>
+<text text-anchor="middle" x="107.92" y="-95.6" font-family="Helvetica,sans-Serif" font-size="14.00">maybe_progress</text>
+</g>
+<!-- evaluate_attempt->maybe_progress -->
+<g id="edge3" class="edge">
+<title>evaluate_attempt->maybe_progress</title>
+<path fill="none" stroke="black" d="M74.98,-150.42C79.42,-143.95 84.56,-136.46 89.43,-129.36"/>
+<polygon fill="black" stroke="black" points="92.21,-131.5 94.98,-121.28 86.43,-127.54 92.21,-131.5"/>
+</g>
+<!-- maybe_progress->prompt_for_challenge -->
+<g id="edge5" class="edge">
+<title>maybe_progress->prompt_for_challenge</title>
+<path fill="none" stroke="black" d="M120.51,-119.81C126.11,-128.61 132.07,-139.73 134.93,-150.7 139.03,-166.44 139.03,-171.56 134.93,-187.3 133.05,-194.5 129.84,-201.76 126.27,-208.39"/>
+<polygon fill="black" stroke="black" points="123.33,-206.49 121.28,-216.88 129.36,-210.04 123.33,-206.49"/>
+</g>
+<!-- win -->
+<g id="node5" class="node">
+<title>win</title>
+<path fill="#b4d8e4" stroke="black" d="M122.93,-36.6C122.93,-36.6 92.92,-36.6 92.92,-36.6 86.92,-36.6 80.92,-30.6 80.92,-24.6 80.92,-24.6 80.92,-12 80.92,-12 80.92,-6 86.92,0 92.92,0 92.92,0 122.93,0 122.93,0 128.93,0 134.93,-6 134.93,-12 134.93,-12 134.93,-24.6 134.93,-24.6 134.93,-30.6 128.93,-36.6 122.93,-36.6"/>
+<text text-anchor="middle" x="107.92" y="-12.5" font-family="Helvetica,sans-Serif" font-size="14.00">win</text>
+</g>
+<!-- maybe_progress->win -->
+<g id="edge4" class="edge">
+<title>maybe_progress->win</title>
+<path fill="none" stroke="black" stroke-dasharray="5,2" d="M107.92,-82.8C107.92,-72.71 107.92,-59.78 107.92,-48.23"/>
+<polygon fill="black" stroke="black" points="111.43,-48.38 107.93,-38.38 104.43,-48.38 111.43,-48.38"/>
+<text text-anchor="middle" x="146.93" y="-54.8" font-family="Times,serif" font-size="14.00">did_win=True</text>
+</g>
+</g>
+</svg>
\ No newline at end of file
diff --git a/content/_images/hierarchical_agent_teams.png b/content/_images/hierarchical_agent_teams.png
new file mode 100644
index 00000000..35661cb6
Binary files /dev/null and b/content/_images/hierarchical_agent_teams.png differ
diff --git a/content/_images/meme.png b/content/_images/meme.png
new file mode 100644
index 00000000..c66b800b
Binary files /dev/null and b/content/_images/meme.png differ
diff --git a/content/_images/multi_agent_collaboration.png b/content/_images/multi_agent_collaboration.png
new file mode 100644
index 00000000..4cc090df
Binary files /dev/null and b/content/_images/multi_agent_collaboration.png differ
diff --git a/content/_images/multi_modal_agent.png b/content/_images/multi_modal_agent.png
new file mode 100644
index 00000000..39ad50eb
Binary files /dev/null and b/content/_images/multi_modal_agent.png differ
diff --git a/content/_images/parallelism.png b/content/_images/parallelism.png
new file mode 100644
index 00000000..29e6ddb4
Binary files /dev/null and b/content/_images/parallelism.png differ
diff --git a/content/_images/recursive_steps.png b/content/_images/recursive_steps.png
new file mode 100644
index 00000000..c8313d2e
Binary files /dev/null and b/content/_images/recursive_steps.png differ
diff --git a/content/_sources/concepts/actions.rst.txt b/content/_sources/concepts/actions.rst.txt
new file mode 100644
index 00000000..2316fae3
--- /dev/null
+++ b/content/_sources/concepts/actions.rst.txt
@@ -0,0 +1,329 @@
+=======
+Actions
+=======
+
+.. _actions:
+
+.. note::
+
+ Actions are the core building block of Burr. They read from state and write to state.
+ They can be synchronous and asynchonous, and have both a ``sync`` and ``async`` API.
+ There are both function and class-based APIs.
+
+
+Actions do the heavy-lifting in a workflow. They should contain all complex compute. You can define actions
+either through a class-based or function-based API. If actions implement ``async def run`` then will be run in an
+asynchronous context (and thus require one of the async application functions).
+
+.. note::
+ When implementing asynchronous actions with ``async def run``, you must also override the ``is_async`` method
+ to return ``True``. This tells the framework to execute the action in an asynchronous context:
+
+ .. code-block:: python
+
+ class AsyncAction(Action):
+ @property
+ def is_async(self) -> bool:
+ return True
+
+ async def run(self, state: State) -> dict:
+ # Async implementation
+ ...
+
+
+Actions have two primary responsibilities:
+
+1. ``run`` -- compute a result
+2. ``update`` -- modify the state
+
+The ``run`` method should return a dictionary of the result and the ``update`` method should return
+the updated state, given the results of the ``run`` method.
+They declare their dependencies so the framework knows *which* state variables they read and write. This allows the
+framework to optimize the execution of the workflow. We call (1) a ``Function`` and (2) a ``Reducer`` (similar to `Redux <https://redux.js.org/>`_, if you're familiar with frontend UI technology).
+
+There are two APIs for defining actions: class-based and function-based. They are largely equivalent, but differ in use.
+
+- use the function-based API when you want to write something quick and terse that reads from a fixed set of state variables
+- use the class-based API when you want to leverage inheritance or parameterize the action in more powerful ways
+
+.. _functionbasedactions:
+
+----------------------
+Function-based actions
+----------------------
+
+You can define actions by decorating a function with the :py:func:`@action <burr.core.action.action>` decorator. These
+can return either just the updated state or a tuple of the result and the state. For example:
+
+.. code-block:: python
+
+ from burr.core import action, State
+
+ @action(reads=["var_from_state"], writes=["var_to_update"])
+ def custom_action(state: State) -> State:
+ return state.update(var_to_update=state["var_from_state"] + 1)
+
+ app = ApplicationBuilder().with_actions(
+ custom_action=custom_action
+ )...
+
+
+Conceptually, these combine the ``update`` and ``run`` methods into a single function, and they're both executed at the same time.
+You also have the option of returning the intermediate results, which is useful for tracking/debugging. The code
+above is equivalent to returning an empty dictionary instead of the results.
+
+.. code-block:: python
+
+ from burr.core import action, State
+
+ @action(reads=["var_from_state"], writes=["var_to_update"])
+ def custom_action(state: State) -> State
+ return results, state.update(var_to_update=var_from_state + 1)
+
+ app = ApplicationBuilder().with_actions(
+ custom_action=custom_action
+ )...
+
+
+
+Function-based actions can take in parameters which are akin to passing in constructor parameters. This is done through the :py:meth:`bind <burr.core.action.bind>` method:
+
+.. code-block:: python
+
+ @action(reads=["var_from_state"], writes=["var_to_update"])
+ def custom_action(state: State, increment_by: int) -> State:
+ return state.update(var_to_update=state["var_from_state"] + increment_by)
+
+ app = ApplicationBuilder().with_actions(
+ custom_action=custom_action.bind(increment_by=2)
+ )...
+
+This is the same as ``functools.partial``, but it is more explicit and easier to read. If an action has parameters that are not
+bound, they will be referred to as inputs. For example:
+
+.. code-block:: python
+
+ @action(reads=["var_from_state"], writes=["var_to_update"])
+ def custom_action(state: State, increment_by: int) -> State:
+ return state.update(var_to_update=state["var_from_state"] + increment_by)
+
+ app = ApplicationBuilder().with_actions(
+ custom_action=custom_action
+ )...
+
+Will require the inputs to be passed in at runtime. See below for how to do that. You can use default values to set optional inputs as well:
+
+.. code-block:: python
+
+ @action(reads=["var_from_state"], writes=["var_to_update"])
+ def custom_action(state: State, increment_by: int = 1) -> State:
+ result = {"var_to_update": state["var_from_state"] + increment_by}
+ return state.update(var_to_update=state["var_from_state"] + increment_by)
+
+ app = ApplicationBuilder().with_actions(
+ custom_action=custom_action
+ )...
+
+This means that the application does not *need* the inputs to be set.
+
+Note: to access application-level metadata such as ``app_id``, ``partition_key``, ``sequence_id``, and ``action_name`` in your running application, you can have the :py:class:`ApplicationContext <burr.core.application.ApplicationContext>`
+injected into your Burr Actions. This is done by adding ``__context`` to the action signature:
+
+.. code-block:: python
+
+ from burr.core import action, State, ApplicationContext
+
+ @action(reads=[...], writes=[...])
+ def my_action(state: State, __context: ApplicationContext) -> State:
+ app_id = __context.app_id
+ partition_key = __context.partition_key
+ action_name = __context.action_name
+ sequence_id = __context.sequence_id
+ ...
+
+
+
+-------------------
+Class-Based Actions
+-------------------
+
+.. _classbasedactions:
+
+
+You can define an action by implementing the :py:class:`Action <burr.core.action.Action>` class:
+
+.. code-block:: python
+
+ from burr.core import Action, State
+
+ class CustomAction(Action):
+ @property
+ def reads(self) -> list[str]:
+ return ["var_from_state"]
+
+ def run(self, state: State) -> dict:
+ return {"var_to_update": state["var_from_state"] + 1}
+
+ @property
+ def writes(self) -> list[str]:
+ return ["var_to_update"]
+
+ def update(self, result: dict, state: State) -> State:
+ return state.update(**result)
+
+You then pass the action to the :py:class:`ApplicationBuilder <burr.core.application.ApplicationBuilder>`:
+
+.. code-block:: python
+
+ from burr.core import ApplicationBuilder
+
+ app = ApplicationBuilder().with_actions(
+ custom_action=CustomAction()
+ )...
+
+
+Note that if the action has inputs, you have to define the optional ``inputs`` property:
+
+.. code-block:: python
+
+ from burr.core import Action, State
+
+ class CustomAction(Action):
+ @property
+ def reads(self) -> list[str]:
+ return ["var_from_state"]
+
+ def run(self, state: State, increment_by: int) -> dict:
+ return {"var_to_update": state["var_from_state"] + increment_by}
+
+ @property
+ def writes(self) -> list[str]:
+ return ["var_to_update"]
+
+ def update(self, result: dict, state: State) -> State:
+ return state.update(**result)
+
+ @property
+ def inputs(self) -> list[str]:
+ return ["increment_by"]
+
+
+See below for how to pass in inputs at runtime. If you want to use optional inputs with the class-based API, ``inputs`` will return a tuple
+of (required, optional) inputs. For example:
+
+.. code-block:: python
+
+ from burr.core import Action, State
+
+ class CustomAction(Action):
+ ...
+ def inputs(self) -> Tuple[list[str], list[str]]:
+ return ["increment_by"], ["optional_input"]
+
+Note your code will have to handle the case where ``optional_input`` is not passed in (e.g. by setting the appropriate kwargs to the `run(...)` method.
+
+If your action needs to know the current `partition_key`, `app_id`, or `sequence_id`, you can request the `__context` variable
+be injected into the action. This is done by adding `__context` to the `inputs` property as well as the run method:
+
+.. code-block:: python
+
+ from burr.core import Action, State, ApplicationContext
+
+ class Counter(Action):
+ @property
+ def reads(self) -> list[str]:
+ return ["counter"]
+
+ def run(self,
+ state: State,
+ increment_by: int,
+ # add __context here
+ __context: ApplicationContext) -> dict:
+ # can access the app_id from the __context
+ print(__context.app_id, __context.partition_key, __context.sequence_id)
+ return {"counter": state["counter"] + increment_by}
+
+ @property
+ def writes(self) -> list[str]:
+ return ["counter"]
+
+ def update(self, result: dict, state: State) -> State:
+ return state.update(**result)
+
+ @property
+ def inputs(self) -> list[str]:
+ # add __context here
+ return ["increment_by", "__context"]
+
+Refer to :py:class:`Action <burr.core.action.Action>` for more documentation.
+
+
+.. _inputref:
+
+--------------
+Runtime Inputs
+--------------
+
+Actions can declare parameters that are not part of the state. Use this to:
+
+1. Provide variables that can be bound to an action. E.g. API clients, DB clients, etc.
+2. Provide inputs that are required as part of the application to function, e.g. human input, configuration, etc.
+
+For example using the function based API, consider the following action:
+
+.. code-block:: python
+
+ @action(reads=["..."], writes=["..."])
+ def my_action(state: State, client: Client, prompt: str) -> State:
+ """client & `prompt` here are something we need to pass in."""
+ context = client.get_data(state["..."])
+ result = llm_call(prompt, context) # some LLM call...
+ return state.update(**result)
+
+We need to pass in `client` and `prompt` somehow. Here are the ways to do that:
+
+.. code-block:: python
+
+
+ # (1) bind values
+ app = (
+ ApplicationBuilder()
+ # we can "bind" values to an action
+ .with_actions(my_action=my_action.bind(client=client))
+ ...
+ .build()
+ )
+
+ # (2) pass them in at runtime
+ app.run( # or app.step, app.iterate, app.astep, etc.\n"
+ halt_..., # your halt logic\n"
+ inputs={"prompt": "this will be passed into `prompt`"} # <-- we pass in values here
+ )
+
+For instance, say you have a chatbot. The first step will likely declare the ``input`` parameter ``prompt`` --
+it will take that, process it, and put the result in state. The subsequent steps will read the result of that from state.
+
+---------------
+Tagging Actions
+---------------
+
+You can tag actions with a list of strings -- this effectively functions as a "alias" for the action,
+so that a single alias can refer to multiple actions. This allows you to refer to a set of actions
+that have similar capabilities as one, using a variety of :ref:`application <applications>` APIs.
+
+.. code-block:: python
+
+ @action(reads=["..."], writes=["..."], tags=["response_to_display"])
+ def text_response(state: State, client: Client, prompt: str) -> State:
+ """client & `prompt` here are something we need to pass in."""
+ context = client.get_data(state["..."])
+ result = llm_call(prompt, context) # some LLM call...
+ return state.update(**result)
+
+This can be referred to either as the name of the action within the application, and/or as the tag
+``response_to_display`` -- referred to as ``@tag:response_to_display`` in the ``halt_after`` or ``halt_before`` parameters
+of :py:func:`Application.run <burr.core.application.Application.run>`, :py:func:`Application.iterate <burr.core.application.Application.iterate>`, etc.
+APIs.
+
+In all function-based APIs, you can pass in a list of tags with the ``tags`` parameter.
+In class-based APIs, you can set the ``tags`` property.
diff --git a/content/_sources/concepts/additional-visibility.rst.txt b/content/_sources/concepts/additional-visibility.rst.txt
new file mode 100644
index 00000000..b2620f92
--- /dev/null
+++ b/content/_sources/concepts/additional-visibility.rst.txt
@@ -0,0 +1,349 @@
+=====================
+Additional Visibility
+=====================
+
+.. note::
+
+ Burr comes with the ability to see inside your actions. This is a very pluggable framework
+ that comes with the default tracking client, but can also be hooked up to tools such as `OpenTelemetry <https://opentelemetry.io/>`_ (OTel)
+
+----------
+Quickstart
+----------
+Below is a quick start. For more in depth documentation see the next few sections.
+
+If you want to:
+ (a) automatically instrument LLM API calls, and
+ (b) see them show up in the Burr UI,
+
+you can do the following:
+
+ 1. Determine the LLM API you want to instrument (e.g. OpenAI, Anthropic, etc...). \
+ See `openllmetry repo <https://github.com/traceloop/openllmetry/tree/main/packages>`_ for available options.
+ 2. Use the local tracker and flip the `use_otel_tracing` flag to True in the ``ApplicationBuilder``.
+
+Here's an example to instrument OpenAI:
+
+.. code-block:: bash
+
+ # install the appropriate openllmetry package
+ pip install opentelemetry-instrumentation-openai
+
+
+.. code-block:: python
+
+ # add the right imports
+ from opentelemetry.instrumentation.openai import OpenAIInstrumentor
+ OpenAIInstrumentor().instrument() # this instruments openai clients
+
+ # create the local tracker
+ tracker = LocalTrackingClient(project="your-project")
+ app = (
+ ApplicationBuilder()
+ .with_graph(base_graph)
+ #... whatever you do normally here
+ .with_tracker(tracker, use_otel_tracing=True) # set use_otel_tracing to True
+ .build()
+ )
+ # use your app as you normally would -- go to the Burr UI and see additional spans!
+
+
+
+-------
+Tracing
+-------
+
+Burr comes with a tracing capability to see recursive spans inside an action. This is similar to
+the `OpenTelemetry <https://opentelemetry.io/>`_ sdk, although it is simplified significantly.
+
+To add the tracing capability, the action first has to declare a ``__tracer`` input. This is a
+an object that instantiates spans and tracks state.
+
+Then, using the ``__tracer`` as a callable, you can instantiate spans and track state.
+
+For the function-based API, this would look as follows:
+
+.. code-block:: python
+
+ from burr.visibility import TracerFactory
+ from burr.core import action
+
+ @action(reads=['prompt'], writes=['response'])
+ def my_action(state: State, __tracer: TracerFactory) -> State:
+ with __tracer('create_prompt'):
+ modified_prompt = _modify_prompt(state["prompt"])
+ with __tracer('check_prompt_safety'):
+ if _is_unsafe(modified_prompt):
+ modified_prompt = _fix(modified_prompt)
+ with __tracer('call_llm', dependencies=['create_prompt']):
+ response = _query(prompt=modified_prompt)
+ return state.update({'response': response})
+
+
+This would create the following traces:
+
+#. ``create_prompt``
+#. ``check_prompt_safety`` as a child of ``create_prompt``
+#. ``call_llm`` as a causal dependent of ``create_prompt``
+
+Dependencies are used to express [dag](-style structures of spans within actions. This is useful for gaining visibility into the internal structure
+of an action, but is likely best used with integrations with micro-orchestration systems for implementating actions, such as Hamilton or Lanchain.
+This maps to the `span link <https://opentelemetry.io/docs/concepts/signals/traces/#span-links>`_ concept in OpenTelemetry.
+
+Note that, on the surface, this doesn't actually *do* anything. It has to be paired with the appropriate hooks.
+These just function as callbacks (on enter/exit). The :py:class:`LocalTrackingClient <burr.tracking.LocalTrackingClient>`, used by the
+:ref:`tracking <tracking>` feature forms one of these hooks, but we will be adding more, including:
+
+1. An OpenTelemetry client
+2. A DataDog client
+
+.. note::
+
+ The class-based API can leverage this by declaring ``inputs`` as ``__tracer`` and then using the ``__tracer`` inside the ``run`` method.
+
+------------
+Observations
+------------
+
+You can make observations on the state by calling out to the :py:meth:`log_attribute <burr.visibility.ActionSpanTracer.log_attribute>` or :py:meth:`log_attributes <burr.visibility.ActionSpanTracer.log_attributes>` method on the ``__tracer`` context manager
+or the root tracer factory. Note: the `trace()` decorator explained below can substitute for the following approach in many cases.
+
+This allows you to log any arbitrary observations (think token count prompt, whatnot) that may not be part of state/results.
+
+For instance:
+
+.. code-block:: python
+
+ from burr.visibility import TracerFactory
+ from burr.core import action
+
+ @action(reads=['prompt'], writes=['response'])
+ def my_action(state: State, __tracer: TracerFactory) -> State:
+ __tracer.log_attributes(prompt_length=len(state["prompt"]), prompt=state["prompt"])
+
+ # note: see `@trace()` below as an alternative to this approach.
+ with __tracer('create_prompt') as t:
+ modified_prompt = _modify_prompt(state["prompt"])
+ t.log_attributes(modified_prompt=modified_prompt)
+ with __tracer('check_prompt_safety') as t:
+ if is_unsafe:=_is_unsafe(modified_prompt):
+ modified_prompt = _fix(modified_prompt)
+ t.log_attributes(fixed_prompt=modified_prompt, is_unsafe=is_unsafe)
+ with __tracer('call_llm', dependencies=['create_prompt']):
+ response = _query(prompt=modified_prompt)
+ t.log_attributes(response=response.message, tokens=response.tokens)
+ return state.update({'response': response.message})
+
+The above will log quite a few attributes, prompt length, response tokens, etc... The observation can be any serializable object.
+
+Note that we are currently building out the capability to wrap a class and "auto-log" standard attributes.
+
+You can read more in the :ref:`reference documentation <visibility>`.
+
+
+-----------------
+Tracing Functions
+-----------------
+
+You can observe non-burr functions, which will show up as part of your traces. To do this, you simply need to decorate the
+function with the :py:func:`@trace <burr.visibility.tracing.trace>` decorator. This will automatically create a span
+for the function (within the approprite context), and log as attributes the parameters + return value.
+
+For instance:
+
+.. code-block:: python
+
+ from burr.core import action
+ from burr.visibility import trace
+
+ @trace()
+ def _modify_prompt(prompt: str) -> str:
+ return ...
+
+ @trace()
+ def _fix(prompt: str) -> str:
+ return ...
+
+ @trace()
+ def _query(prompt: str) -> Response:
+ return ...
+
+ @action(reads=['prompt'], writes=['response'])
+ def my_action(state: State) -> State:
+ modified_prompt = _modify_prompt(state["prompt"])
+ if _is_unsafe(modified_prompt):
+ modified_prompt = _fix(modified_prompt)
+ response = _query(prompt=modified_prompt)
+ return state.update({'response': response})
+
+
+This will create spans for the ``_modify_prompt``, ``_fix``, and ``_query`` functions, and log the parameters and return values. You can opt out of logging paramers or return values and adding a filter to the decorator to exclude certain parameters.
+To contrast what you can instrument manually, the ``@trace`` decorator allows you to get more visibility and replace manual code such as the following (though you can happily combine the two approaches):
+
+.. code-block:: python
+
+ from burr.visibility import TracerFactory
+ from burr.core import action
+ @action(reads=['prompt'], writes=['response'])
+ def my_action(state: State, __tracer: TracerFactory) -> State:
+ with __tracer('create_prompt'):
+ modified_prompt = _modify_prompt(state["prompt"])
+ with __tracer('check_prompt_safety'):
+ if _is_unsafe(modified_prompt):
+ modified_prompt = _fix(modified_prompt)
+ with __tracer('call_llm', dependencies=['create_prompt']):
+ response = _query(prompt=modified_prompt)
+ return state.update({'response': response})
+
+
+This will create spans for the ``_modify_prompt``, ``_fix``, and ``_query`` functions, and log the parameters and return values.
+You can opt out of logging paramers or return values and adding a filter to the decorator to exclude certain parameters.
+
+.. _opentelref:
+
+--------------
+OpenTelemetry
+--------------
+
+While Burr does not support the entire `OpenTelemetry <https://opentelemetry.io/>`_
+spec, it does have integrations that allows it to (a) log to OpenTelemetry and (b)
+capture OpenTelemetry events with tracking.
+
+These features are currently experimental, but we expect the API to remain largely stable.
+
+Capturing OpenTelemetry events
+------------------------------
+
+Burr can capture OpenTelemetry traces/spans that are logged from within a Burr step.
+These get tracked in the UI, which can display traces and attributes, as explained above.
+
+To do this, you just have to set the ``use_otel_tracing`` flag on :py:meth:`with_tracker <burr.core.application.ApplicationBuilder.with_tracker>`
+function in the ``ApplicationBuilder``. This will automatically capture all OpenTelemetry traces, mixing them with Burr traces. Take the following (contrived)
+example:
+
+.. code-block:: python
+
+ from burr.visibility import TracerFactory
+ from burr.core import action
+ from opentelemetry import trace
+ otel_tracer = trace.get_tracer(__name__)
+
+ @action(reads=['prompt'], writes=['response'])
+ def my_action(state: State, __tracer: TracerFactory) -> State:
+ with __tracer:
+ # Burr logging
+ __tracer.log_attributes(prompt_length=len(state["prompt"]), prompt=state["prompt"])
+ # OpenTelemetry Tracer
+ with otel_tracer.start_as_current_span('create_prompt') as span:
+ modified_prompt = _modify_prompt(state["prompt"])
+ span.set_attributes(dict(modified_prompt=modified_prompt))
+ # Back to Burr tracer
+ with __tracer('call_llm', dependencies=['create_prompt']):
+ response = _query(prompt=modified_prompt)
+ t.log_attributes(response=response.message, tokens=response.tokens)
+ return state.update({'response': response.message})
+
+ app = (
+ ApplicationBuilder()
+ .with_actions(my_action, ...)
+ .with_state(...)
+ .with_transitions(...)
+ .with_tracker("local", project="my_project", use_otel_tracing=True)
+ .with_entrypoint("prompt", "my_action")
+ .build()
+ )
+
+While this is contrived, it illustrates that you can mix/match Burr/OpenTelemetry. This is valuable
+when you have a Burr action that calls out to a function that is instrumented via OpenTelemetry (
+of which there are a host of integrations).
+
+Note that this currently does not support logging remote traces, but we plan to have a
+more complete integration in the future.
+
+If you do not enable ``use_otel_tracing``, this will all be a no-op.
+
+Logging to OpenTelemetry
+-------------------------
+
+Burr can also log to any OpenTelemetry provider, again enabling mixing/matching of spans. To do this,
+you simply need to pass an instance of the :py:class:`OpenTelemetryBridge <burr.integrations.opentelemetry.OpenTelemetryBridge>` to the
+:py:meth:`with_hooks <burr.core.application.ApplicationBuilder.with_hooks>` method of the ``ApplicationBuilder``. This will automatically
+log all spans to the OpenTelemetry provider of choice (and you are responsible for initializes
+it as you see fit).
+
+.. code-block:: python
+
+ from burr.integrations.opentelemetry import OpenTelemetryBridge
+
+ otel_tracer = trace.get_tracer(__name__)
+ app = (
+ ApplicationBuilder()
+ .with_actions(my_action, ...)
+ .with_state(...)
+ .with_transitions(...)
+ .with_hooks(OpenTelemetryBridge(tracer=otel_tracer))
+ .with_entrypoint("prompt", "my_action")
+ .build()
+ )
+
+
+With this you can log to any OpenTelemetry provider.
+
+
+Instrumenting libraries
+-----------------------
+
+One way to gain observability is to **instrument** a library with OpenTelemetry. This involves importing an object that will automatically patch the library in order to produce and report telemetry (learn more about `autoinstrumentation <https://opentelemetry.io/docs/zero-code/python/>`_). The Python community has implemented `instrumentation for many popular libraries <https://opentelemetry-python-contrib.readthedocs.io/en/latest/>`_ (FastAPI, AWS Lambda, requests, etc.).
+
+This snippet enables automatically logging HTTP requests done via the ``requests`` library:
+
+.. code-block:: python
+
+ from opentelemetry.instrumentation.requests import RequestsInstrumentor
+ RequestsInstrumentor().instrument()
+
+
+This works with both of the integrations above, and simple requires the
+``opentelemetry-instrumentation-YYY`` package (where ``YYY`` is the library you want to instrument, openai in this case).
+
+
+LLM-specific Telemetry
+~~~~~~~~~~~~~~~~~~~~~~
+
+`Openllmetry <https://github.com/traceloop/openllmetry/>`_ is a project implementing autoinstrumentation for components of LLM stacks (LLM providers, vector databases, frameworks, etc.). This enables tracking prompts, token counts, temperature, vector search operations, etc.
+
+The following instruments the ``openai`` Python library
+
+.. code-block:: python
+
+ from opentelemetry.instrumentation.openai import OpenAIInstrumentor
+ OpenAIInstrumentor().instrument()
+
+
+Burr's ``init_instruments()``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The Burr integration for OpenTelemetry includes the convenience function ``init_instruments()``. You can provide it the names of modules to instrument as strings and it will try to instrument them. This feature has good IDE autocompletion support. Another benefit is the ability to instrument multiple libraries at once.
+
+The following instruments the module ``openai`` (equivalent to the previous snippet) along with ``lancedb`` and ``fastapi``.
+
+.. code-block:: python
+
+ from burr.integrations.opentelemetry import init_instruments
+ init_instruments("openai", "lancedb", "fastapi")
+
+
+Specifying the keyword argument ``init_all=True`` will try to instrument currently imported libraries with the instrumentation packages installed.
+
+You can get the logger to view in detail which library is instrumented.
+
+.. code-block:: python
+
+ import logging
+ from burr.integrations.opentelemetry import init_instruments
+
+ logger = logging.getLogger("burr.integrations.opentelemetry")
+ logger.addHandler(logging.StreamHandler())
+ logger.setLevel(logging.DEBUG)
+
+ init_instruments(init_all=True)
diff --git a/content/_sources/concepts/hooks.rst.txt b/content/_sources/concepts/hooks.rst.txt
new file mode 100644
index 00000000..8ff6100c
--- /dev/null
+++ b/content/_sources/concepts/hooks.rst.txt
@@ -0,0 +1,69 @@
+=====
+Hooks
+=====
+
+.. _hooks:
+.. note::
+
+ Hooks allow you to customize every aspect of Burr's execution, plugging in whatever tooling,
+ observability framework, or debugging tool you need.
+
+Burr has a system of lifecycle adapters (adapted from the similar `Hamilton <https://github.com/dagworks-inc/hamilton>`_ concept), which allow you to run tooling before and after
+various places in a node's execution. For instance, you could:
+
+1. Log every step as a trace in datadog
+2. Add a time-delay to your steps to allow for rendering
+3. Add a print statement to every step to see what happened (E.G. implement the printline in cowsay above)
+4. Synchronize state/updates to an external database
+5. Put results on a queue to feed to some monitoring system
+
+Note some of the above are yet to be implemented.
+
+To implement hooks, you subclass any number of the :ref:`available lifecycle hooks <hooksref>`.
+These have synchronous and asynchronous versions, and your hook can subclass as many as you want
+(as long as it doesn't do both the synchronous and asynchronous versions of the same hook).
+
+To use them, you pass them into the :py:class:`ApplicationBuilder <burr.core.application.ApplicationBuilder>` as a ``*args`` list of hooks. For instance,
+a hook that prints out the nodes name during execution looks like this.
+We implement the pre/post run step hooks.
+
+.. code-block:: python
+
+ class PrintLnHook(PostRunStepHook, PreRunStepHook):
+ def pre_run_step(self, *, state: "State", action: "Action", **future_kwargs: Any):
+ print(f"Starting action: {action.node.name}")
+
+ def post_run_step(
+ self,
+ *,
+ state: "State",
+ action: "Action",
+ result: Optional[dict],
+ sequence_id: int,
+ exception: Exception,
+ **future_kwargs: Any,
+ ):
+ print(f"Finishing action: {action.node.name}")
+
+To include this in the application, you pass it into the :py:meth:`with_hooks <burr.core.application.ApplicationBuilder.with_hooks>` method.
+
+.. code-block:: python
+
+ app = (
+ ApplicationBuilder()
+ .with_hooks(PrintLnHook())
+ ...
+ .build())
+
+.. note::
+
+ There are synchronous and asynchronous hooks. Synchronous hooks will be called with both synchronous and asynchronous run methods
+ (all of ``step``, ``astep``, ``iterate``, ``aiterate``, ``run``, and ``arun``), whereas synchronous hooks will only be called with
+ the asynchronous methods (``astep``, ``aiterate``, ``arun``).
+
+.. warning::
+ Hook order is currently undefined -- they happen to be called now in the order in which they are defined. We will likely
+ alter them to be called in the order they are defined (for `pre...`) hooks and in reverse order for `post...` hooks,
+ but this is not yet implemented.
+
+Read more about the hook API in the :ref:`hooks section <hooksref>`.
diff --git a/content/_sources/concepts/index.rst.txt b/content/_sources/concepts/index.rst.txt
new file mode 100644
index 00000000..f4aaba77
--- /dev/null
+++ b/content/_sources/concepts/index.rst.txt
@@ -0,0 +1,28 @@
+====================
+Concepts
+====================
+
+Overview of the concepts -- read these to get a mental model for how Burr works.
+
+.. _concepts:
+
+.. toctree::
+ :maxdepth: 2
+
+ overview
+ sdlc
+ actions
+ state
+ state-machine
+ transitions
+ tracking
+ state-persistence
+ serde
+ streaming-actions
+ state-typing
+ hooks
+ additional-visibility
+ parallelism
+ recursion
+ sync-vs-async
+ planned-capabilities
diff --git a/content/_sources/concepts/overview.rst.txt b/content/_sources/concepts/overview.rst.txt
new file mode 100644
index 00000000..de587899
--- /dev/null
+++ b/content/_sources/concepts/overview.rst.txt
@@ -0,0 +1,14 @@
+===========
+Cheat Sheet
+===========
+
+This is a quick overview of Burr's design -- the concepts are explored in more detail in the following sections. Read over this for a very high-level overview, or use this as a cheat sheet later.
+
+- With Burr you write an :ref:`Application <applications>` -- this manages control flow (allowing for automated or user-blocking workflows), :ref:`persistence <state-persistence>` to DBs, logs :ref:`telemetry <tracking>`, and delegates to a variety of plugins/integrations.
+- Applications are composed of :ref:`actions <actions>` (functions that write to/read from state), and :ref:`transitions <transitions>` (functions that determine the next action to execute based on state).
+- :ref:`State <state>` is immutable and uses the special Burr :py:class:`State <burr.core.state.State>` API. You write to it by applying a state operation (e.g. ``state = state.update(key=value)``, which returns a new state instance with the updated value.
+- All other production/debugging concerns are implemented as :ref:`hooks <hooks>`, which are simple callbacks that are called at various points in the application lifecycle (store/retrieve state, log information, etc...).
+
+Note that we did not mention LLMs above at all! That's good -- you want your LLM frameworks to be ever-so-slightly decoupled from them for the best experience (all of AI is just software, plain and simple, after all...).
+
+And that's the basics! Let's dive into the details.
diff --git a/content/_sources/concepts/parallelism.rst.txt b/content/_sources/concepts/parallelism.rst.txt
new file mode 100644
index 00000000..1cb55272
--- /dev/null
+++ b/content/_sources/concepts/parallelism.rst.txt
@@ -0,0 +1,732 @@
+.. _parallelism:
+
+=================
+Parallelism
+=================
+
+Burr can run multiple actions in parallel. Each parallel branch can contain one or more actions, and different branches can have different actions. This is useful when:
+
+- Trying different prompts with an LLM
+- Trying a prompt with different LLMs
+- Trying multiple prompts with multiple LLMs
+- Running multiple tools in parallel then combining/selecting from the result
+- Do semantic search and web search simultaneously for information retrieval
+
+And more! Just like Burr in general, these concepts are generic and can be applied to non-LLM applications
+This section shows how to enable parallelism and presents use cases.
+
+
+
+TL;DR
+=====
+Burr provides a high-level and a low-level API for parallelism. The high-level API supports many different patterns and should be sufficient for most use cases.
+
+- ``MapStates``: Apply an action to multiple values in state then reduce the action results (e.g., different prompts to the same LLM).
+- ``MapActions``: Apply different actions to the same state value then reduce the actions result (e.g., same prompt to different LLMs).
+- ``MapActionsAndStates``: Do the full cartesian product of actions and state values (e.g., try different prompts with multiple LLMs)
+- ``RunnableGraph``: Combined with the above options, you can replace a single Action by a Graph composed of multiple actions.
+
+With the low-level API, you can manually determine how parallel actions or subgraphs are executed.
+
+.. image:: ../_static/parallelism.png
+ :align: center
+
+Overview
+========
+Burr allows you to define parallel actions by expanding a single action into multiple individual actions or subgraphs which
+will execute them all and joining the results. This is a simple `map-reduce <https://en.wikipedia.org/wiki/MapReduce>`_ pattern.
+
+Currently, Burr has two separate APIs for building parallel applications -- higher level (use this first), and lower level.
+Beyond that, Burr can support parallelism however you wish to run it -- see the advanced use-cases section for more details.
+
+Higher-level API
+================
+
+You select a set of "configurations" over which you want to run, and Burr launches all of them then joins the result.
+
+This means you either:
+
+1. Vary the state and run over the same action/subgraph (think tuning LLM parameters/inputs, running simple experiments/optimization routines, etc...)
+2. Vary the action and provide the same state (think running multiple LLMs on the same input, running multiple analyses on the same data, etc...)
+
+Note we do not distinguish between subgraph and action -- under the hood it's all treated as a "sub-application" (more on that later).
+
+
+Run the same action over different states
+-----------------------------------------
+
+For case (1) (mapping states over the same action) you implement the ``MapStates`` class, doing the following:
+
+- We define a regular action ``query_llm()`` using the ``@action`` decorator.
+- We also create a subclass of ``MapStates`` named ``TestMultiplePrompts``, which must implement ``.reads()``, ``.writes()``, ``.action()``, ``.states()``, and ``.reduce()``.
+ - ``.reads()`` / ``.writes()`` define the state value it can interact with, just like the ``@action`` decorator
+ - ``.action()`` leverages the ``query_llm()`` previously defined
+ - ``.states()`` can read value from State and yields values to pass to the . action(). In this case, it updates the prompt state value that's read by ``query_llm()```. (the example hardcoded a list of prompts for simplicity, but this would be read from state)
+ - ``.reduce()`` receives multiple states, one per ``.action()`` call, where the llm_output value is set by ``query_llm()`` in ``.action()``. Then, it must set all_llm_output as specified in the ``MapStates.writes()`` method.
+- We pass an instance of the ``TestMultiplePrompts`` class to the ApplicationBuilder, which will run the action over the states we provide.
+
+This looks as follows -- in this case we're running the same LLM over different prompts:
+
+
+.. code-block:: python
+
+ from burr.core import action, state
+ from burr.core.parallelism import MapStates, RunnableGraph
+ from typing import Callable, Generator, List
+
+ @action(reads=["prompt"], writes=["llm_output"])
+ def query_llm(state: State) -> State:
+ return state.update(llm_output=_query_my_llm(prompt=state["prompt"]))
+
+ class TestMultiplePrompts(MapStates):
+
+ def action(self, state: State, inputs: Dict[str, Any]) -> Action | Callable | RunnableGraph:
+ # make sure to add a name to the action
+ # This is not necessary for subgraphs, as actions will already have names
+ return query_llm.with_name("query_llm")
+
+ def states(self, state: State, context: ApplicationContext, inputs: Dict[str, Any]) -> Generator[State, None, None]:
+ # You could easily have a list_prompts upstream action that writes to "prompts" in state
+ # And loop through those
+ # This hardcodes for simplicity
+ 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(sub_state["llm_output"])
+ 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"]
+
+Then, to run the application:
+
+.. code-block:: python
+
+ app = (
+ ApplicationBuilder()
+ .with_action(
+ prompt_generator=generate_prompts, # not defined above, this writes to prompts
+ multi_prompt_test=TestMultiplePrompts(),
+ ).with_transitions(
+ ("prompt_generator", "multi_prompt_test"),
+ )
+ .build()
+ )
+
+
+Run different actions over the same state
+-----------------------------------------
+
+For case (2) (mapping actions over the same state) you implement the ``MapActions`` class, doing the following:
+
+- We define a regular action ``query_llm()`` using the ``@action`` decorator. This takes in a model parameter (which we're going to bind later)
+- We also create a subclass of ``MapActions`` named ``TestMultipleModels``, which must implement ``.reads()``, ``.writes()``, ``.actions()``, ``.state()``, and ``.reduce()``.
+ - ``.reads()`` / ``.writes()`` define the state value it can interact with, just like the ``@action`` decorator
+ - ``.actions()`` leverages the ``query_llm()`` previously defined, binding with the different models we want to test
+ - ``.state()`` can read value from State and produces the state to pass to the actions produced by ``actions()``. In this case, it updates the prompt state value that's read by ``query_llm()``.
+ - ``.reduce()`` receives multiple states, one per result of the ``.actions()`` call, where the llm_output value is set by ``query_llm()`` in ``.actions()``. Then, it must set all_llm_output as specified in the ``MapStates.writes()`` method.
+- We pass an instance of the ``TestMultipleModels`` class to the ``ApplicationBuilder``, which will run the action over the states we provide.
+
+.. code-block:: python
+
+ from burr.core import action, state
+ from burr.core.parallelism import MapActions, RunnableGraph
+ from typing import Callable, Generator, List, Dict, Any
+
+ @action(reads=["prompt", "model"], writes=["llm_output"])
+ def query_llm(state: State, model: str) -> State:
+ # TODO -- implement _query_my_llm to call litellm or something
+ return state.update(llm_output=_query_my_llm(prompt=state["prompt"], model=model))
+
+ class TestMultipleModels(MapActions):
+
+ def actions(self, state: State, context: ApplicationContext, inputs: Dict[str, Any]) -> Generator[Action | Callable | RunnableGraph, None, None]:
+ # Make sure to add a name to the action if you use bind() with a function,
+ # note that these can be different actions, functions, etc...
+ # in this case we're using `.bind()` to create multiple actions, but we can use some mix of
+ # subgraphs, functions, action objects, etc...
+ 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 state(self, state: State, inputs: Dict[str, Any]) -> State:
+ return state.update(prompt="What is the meaning of life?")
+
+ def reduce(self, state: State, states: Generator[State, None, None]) -> State:
+ all_llm_outputs = []
+ for sub_state in states:
+ all_llm_outputs.append(sub_state["llm_output"])
+ return state.update(all_llm_outputs=all_llm_outputs)
+
+ @property
+ def reads(self) -> List[str]:
+ return ["prompt"] # we're just running this on a single prompt, for multiple actions
+
+ @property
+ def writes(self) -> List[str]:
+ return ["all_llm_outputs"]
+
+
+Then, it's almost identical to the ``MapStates`` case:
+
+.. code-block:: python
+
+ app = (
+ ApplicationBuilder()
+ .with_action(
+ prompt_generator=generate_prompts, # not defined above, this writes to prompts
+ multi_prompt_test=TestMultipleModels(),
+ ).with_transitions(
+ ("prompt_generator", "multi_prompt_test"),
+ )
+ .build()
+ )
+
+
+Full cartesian product
+----------------------
+
+If you want to run all possible combinations of actions/states, you can use the ``MapActionsAndStates`` class -- this is actually the
+base class for the above two classes. For this, you provide a generator of actions and a generator of states, and Burr will run all possible
+combinations.
+
+For tracking which states/actions belong to which actions, we recommend you use the values stored in the state (see example).
+
+.. code-block:: python
+
+ 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:
+ # TODO -- implement _query_my_llm to call litellm or something
+ 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]:
+ # make sure to add a name to the action
+ # This is not necessary for subgraphs, as actions will already have names
+ 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"]
+
+
+Subgraphs
+---------
+
+While we've been using individual actions above, we can also replace them with subgraphs (E.G. :ref:`using recursion <recursion>` inside applications).
+
+To do this, we use the Graph API and wrap it in a RunnableGraph:
+
+- The :py:class:`Graph <burr.core.Graph>` API allows us to tell the structure of the action
+- The ``RunnableGraph`` is a wrapper that tells the framework other things you need to know to run the graph:
+ - The entrypoint of the graph
+ - The exit points (corresponding to ``halt_after`` in :py:meth:`run <burr.core.Application.run>`)
+
+This might look as follows -- say we have a simple subflow that takes in a raw prompt from state and returns the LLM output:
+
+.. code-block:: python
+
+ from typing import Dict, Any
+ from burr.core import action, state
+ from burr.core.graph import Graph
+ from burr.core.parallelism import RunnableGraph
+
+ @action(reads=["prompt"], writes=["processed_prompt"])
+ def process_prompt(state: State) -> State:
+ processed_prompt = f"The user has asked: {state['prompt']}. Please respond directly to that prompt, but only in riddles."
+ return state.update(
+ processed_prompt=state["prompt"],
+ )
+
+ @action(reads=["processed_prompt"], writes=["llm_output"])
+ def query_llm(state: State) -> State:
+ return state.update(llm_output=_query_my_llm(prompt=state["processed_prompt"]))
+
+ graph = (
+ GraphBuilder()
+ .with_action(
+ process_prompt=process_prompt,
+ query_llm=query
+ ).with_transitions(
+ ("process_prompt", "query_llm")
+ ).build()
+ )
+
+ runnable_graph = RunnableGraph(
+ graph=graph,
+ entrypoint="process_prompt",
+ halt_after=["query_llm"]
+ )
+
+ class TestMultiplePromptsWithSubgraph(MapStates):
+
+ def action(self, state: State, inputs: Dict[str, Any]) -> Action | Callable | RunnableGraph:
+ return runnable_graph
+
+ 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)
+
+ ... # same as above
+
+In the code above, we're effectively treating the graph like an action -- due to the single ``entrypoint``/``halt_after`` condition we specified,
+it can run just as the single prompt we did above. Note this is also doable for running multiple actions over the same state.
+
+
+
+Passing inputs
+--------------
+
+Parallel actions can accept inputs in the same way that class-based actions do. In order to accept inputs you have to declare them in the class. As we're using the :ref:`class-based API <classbasedactions>`,
+this is done by declaring the ``inputs`` property -- a list of strings that are used in inputs. Note you have to use the superclasses
+inputs as well to ensure it has everything it needs -- we will likely be automating this.
+
+This looks as follows:
+
+.. code-block:: python
+
+ from burr.core import action, State, ApplicationContext
+ from burr.core.graph import Graph
+
+ @action(reads=["prompt"], writes=["processed_prompt"])
+ def process_prompt(state: State) -> State:
+ processed_prompt = f"The user has asked: {state['prompt']}. Please respond directly to that prompt, but only in riddles."
+ return state.update(
+ prompt=state["prompt"],
+ )
+
+ @action(reads=["processed_prompt"], writes=["llm_output"])
+ def query_llm(state: State, model: str) -> State:
+ return state.update(llm_output=_query_my_llm(prompt=state["processed_prompt"], model=model))
+
+ graph = (
+ GraphBuilder()
+ .with_action(
+ process_prompt=process_prompt,
+ query_llm=query
+ ).with_transitions(
+ ("process_prompt", "query_llm")
+ ).build()
+ )
+
+ runnable_graph = RunnableGraph(
+ graph=graph,
+ entrypoint="process_prompt",
+ halt_after="query_llm"
+ )
+
+ class TestMultiplePromptsWithSubgraph(MapStates):
+
+ def action(self, state: State, inputs: Dict[str, Any]) -> Action | Callable | RunnableGraph:
+ return runnable_graph
+
+ @property
+ def inputs(self) -> List[str]:
+ return ["prompts"] + super().inputs # make sure to include the superclass inputs
+
+ def states(self, state: State, context: ApplicationContext, inputs: Dict[str, Any]) -> Generator[Tuple[State, dict], None, None]:
+ for prompt in inputs["prompts"]:
+ yield state.update(prompt=prompt)
+
+ ... # same as above
+
+
+.. note::
+
+ Should ``MapOverInputs`` be its own class? Or should we have ``bind_from_state(prompt="prompt_field_in_state")`` that allows you to pass it in as
+ state and just use the mapping capabilities? Or are we happy as it currently is because we can pass in inputs through `MapStates`/`MapActions` (as shown above).
+
+
+Lower-level API
+===============
+
+The above compile into a set of "tasks" -- sub-applications to run. If, however, you want to have more control, you
+can use the lower-level API to simply define the tasks. This allows you to provide any combination of actions, input, and state
+to the tasks.
+
+All of the aforementioned high-level API are implemented as subclasses of TaskBasedParallelAction.
+You can subclass it directly and implement the ``.tasks()`` method that yields SubGraphTask,
+which can be actions or subgraphs. These tasks are then executed by the ``burr.Executor`` implementations
+
+This looks as follows:
+
+.. code-block:: python
+
+ from burr.core import action, state, ApplicationContext
+ from burr.core.parallelism import MapStates, RunnableGraph
+ from typing import Callable, Generator, List
+
+ @action(reads=["prompt", "model"], writes=["llm_output"])
+ def query_llm(state: State, model: str) -> State:
+ # TODO -- implement _query_my_llm to call litellm or something
+ return state.update(llm_output=_query_my_llm(prompt=state["prompt"], model=model))
+
+ class MultipleTaskExample(TaskBasedParallelAction):
+ def tasks(state: State, context: ApplicationContext) -> Generator[SubGraphTask, None, None]:
+ for prompt in state["prompts"]:
+ 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 SubGraphTask(
+ action=action, # can be a RunnableGraph as well
+ state=state.update(prompt=prompt),
+ inputs={},
+ # stable hash -- up to you to ensure uniqueness
+ application_id=hashlib.sha256(context.application_id + action.name + prompt).hexdigest(),
+ # a few other parameters we might add -- see advanced usage -- failure conditions, etc...
+ )
+
+ 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)
+
+
+Advanced Usage
+==============
+
+We anticipate the above should cover most of what you want to do, but we have a host of advanced tuning capabilities.
+
+
+Execution
+---------
+
+To enable execution, you need to pass a ``burr.Executor`` to the application, or to the actions themselves. This is just a python executor
+instance, although we will likely be adding a new interface.
+
+You can implement your own by subclassing the ``concurrent.futures.Executor`` class and passing to your favorite execution tool.
+
+This only makes sense for synchronous execution -- for async, it will use asyncio.gather. You can pass this either as a global executor for the application, or specify it as part of your class:
+
+Specifying it as part of the application -- will get routed as the default to all parallel actions:
+
+.. code-block:: python
+
+ app = (
+ ApplicationBuilder()
+ .with_parallel_executor(MultiThreadedExecutor(max_concurrency=10))
+ .build()
+ )
+
+Specifying it as part of the action -- will override the global executor (note this is not supported yet...).
+
+.. code-block:: python
+
+ class TestMultiplePrompts(MapStates):
+
+ def action(self, state: State, inputs: Dict[str, Any]) -> Action | Callable | RunnableGraph:
+ return runnable_graph
+
+ def executor(self) -> Executor:
+ return MultiThreadedExecutor(max_concurrency=10)
+
+ ... # same as above
+
+
+Persistence/Tracking
+--------------------
+
+By default, the trackers/persisters will be passed from the parent application to the child application. The application IDs
+will be created as a a stable hash of the parent ID + the index of the child ID, requiring the order to be constant to ensure that the same application ID is used for the same task every time.
+
+Note that we will be adding hte ability to create a unique key, per application, but that is not implemented yet.
+
+It will also utilize the same persister to load from the prior state, if that is used on the application level (see :ref:`state persistence <state-persistence>`).
+
+This enables the following:
+
+1. Tracking will automatically be associated with the same application (and sub-application) when reloaded
+2. If the concurrent application quits halfway through, the application will be able to pick up where it left off, as will all sub-applications
+
+The default behavior is to cascade all the following constructs through to the parallel child applications:
+
+1. Tracking (given by the :py:meth:`burr.core.application.ApplicationBuilder.with_tracker` function in :py:class:`ApplicationBuilder <burr.core.application.ApplicationBuilder>`)
+2. State saving/persistence (given by the :py:meth:`burr.core.application.ApplicationBuilder.with_state_persister` function in :py:class:`burr.core.application.ApplicationBuilder`)
+3. State loading (given by the :py:meth:`burr.core.application.ApplicationBuilder.initialize_from` function in :py:class:`burr.core.application.ApplicationBuilder`)
+
+These will then be passed ot the corresponding sub-application tasks. If any of these objects implement a `.copy()` method, that will be called. Otherwise the same instance will be created. Not the tracker/persister/loader must be serializable
+to work in non-multithreaded executors, and thread-safe to work in multithreaded instances.
+
+In mst cases, (2) and (3) above will be the same, however there all valid use-cases where you might want to use different persisters/loaders for the sub-application.
+Thus, the default will be to pass the same instance for (2) and (3), *if* they both exist and are specified.
+
+You can disable either tracking or persistence at the sub-application level by overriding one of the three methods (corresponding to the three adapter-types above),
+on any of the :py:class:`burr.core.parallelism.MapStatesAndActions`, :py:class:`burr.core.parallelism.MapStates`, or :py:class:`burr.core.parallelism.MapActions` classes.
+
+If you return `None`, then no tracker/persister will be used. If you return an object (E.G. a tracker or a persister), that will be used instead.
+The function will be called for every instance of the sub-application.
+
+1. Overriding the tracker :py:meth:`burr.core.parallelism.MapStatesAndAction.tracker`
+2. Overriding the persister :py:meth:`burr.core.parallelism.MapStatesAndAction.state_persister`
+3. Overriding the loader :py:meth:`burr.core.parallelism.MapStatesAndAction.state_initializer`
+
+You can also set the corresponding fields in the :py:class:`burr.core.parallelism.SubGraphTask` object on a per-task basis,
+if you are using the lower-level API.
+
+You can also disable it globally using the application builder:
+
+.. code-block:: python
+
+ class TestMultiplePrompts(MapStates):
+
+ def action(self, state: State, inputs: Dict[str, Any]) -> Action | Callable | RunnableGraph:
+ return runnable_graph
+
+ def tracker(self, context) -> TrackingBehavior | None:
+ # return "cascade" # default
+ # return None # no tracking
+ return LocalTrackingClient(...) # custom tracker
+
+ def state_persister(self) -> Persister | None:
+ # return "cascade" # default
+ # return None # no persistence
+ return SQLLitePersister(...) # custom persister
+
+ def state_initializer(self) -> StateInitializer | None:
+ return self.state_perssiter() # same as the persister
+
+ ... # same as above
+
+Other
+-----
+
+Things we will consider after the initial release:
+
+- Customizing execution on a per-action basis -- likely a parameter to ``RunnableGraph``
+- Customizing tracking keys for parallelism
+- Streaming -- interleaving parallel streaming actions and giving results as they come
+- More examples for inter-graph communication/cancellation of one action based on the result of another
+- Graceful failure of sub-actions
+
+Under the hood
+==============
+
+Beneath the APIs, all this does is simplify the :ref:`recursion <recursion>`: API to allow for multiple actions to be run in parallel.
+
+- ``RunnableGraph`` s are set as subgraphs, and recursively executed by the application, using the executor
+- an ``Action`` are turned into a ``RunnableGraph`` by the framework, and executed by the executor
+
+In the UI, this will show up as a "child" application -- see the :ref:`recursion <recursion>`: section for more details.
+
+
+Additional Use-cases
+====================
+
+As this is all just syntactic sugar for recursion, you can use the recursion to get more advanced capabilities.
+
+This involves instantiating a sub-application inside the action, and running it yourself.
+
+
+Interleaving Generators
+-----------------------
+
+Say you want to provide an agent that provides up-to-date progress on it's thoughts. For example, say you want to providea
+a planning agent with a similar interface to OpenAI's o1 model.
+
+
+To do this, you would typically call to :py:meth:`iterate <burr.core.application.Application.iterate>`. Now, say you wanted to run
+multiple in parallel!
+
+While this is not built to be easy with the APIs in this section, it's very doable with the underlying recursion API.
+
+The basics (code not present now):
+
+1. Create each sub-application using the ``with_parent_context`` method
+2. Run each sub-application in parallel using the executor
+3. Combine the generators in parallel, yielding results as they come out
+
+
+Inter-action communication
+---------------------------
+
+Say you have two LLMs answering the same question -- one that gives immediate results back to the user
+as they come in, and another that thinks for a while to give more sophisticated results. The user then has the option to say they're happy
+with the solution, or they want to wait for more. You may want to eagerly kick off the second LLM
+if you're concerned about latency -- thus if the user wants more or does not respond, the more sophisticated
+LLM might come up with a solution.
+
+To do this, you would:
+
+1. Run the sub-graph consisting of the first LLM using :py:meth:`iterate <burr.core.application.Application.iterate>`
+2. Simultaneously run the second LLM using :py:meth:`iterate <burr.core.application.Application.iterate>` as well
+3. Join them in parallel, waiting for any user-input if provided
+4. Decide after every step of the first graph whether you want to cancel the second graph or not -- E.G. is the user satisfied.
+
+
+Async Parallelism
+==================
+
+Burr also supports asynchronous parallelism. When working in an async context, you need to make a few adjustments to your parallel actions:
+
+1. Make your methods async
+---------------------------
+
+The `action`, `states`, `reduce`, and other methods should be defined as async:
+
+.. code-block:: python
+
+ class AsyncMapActionsAndStatesExample(MapActionsAndStates):
+
+ async def action(self, state: State, inputs: Dict[str, Any]) -> AsyncGenerator[Action, None]:
+ # Yield multiple model components to run in parallel
+ for i, model_config in enumerate(self._model_configs):
+ yield ModelResponse(config=model_config).with_name(f"model_{i}")
+
+ async def states(self, state: State, inputs: Dict[str, Any]) -> AsyncGenerator[State, None]:
+ # Prepare the state with the user query
+ 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)
+
+ async def reduce(self, state: State, states: AsyncGenerator[State, None]) -> State:
+ # Collect all model responses
+ all_responses = []
+ async for sub_state in states:
+ model_key = sub_state.get("model_key")
+ response = sub_state.get(model_key, [])[-1].get("content", "")
+ all_responses.append(response)
+
+ return state.update(ensemble_responses=all_responses)
+
+2. Implement the is_async method
+--------------------------------
+
+You must override the `is_async` method to return `True`:
+
+.. code-block:: python
+
+ class AsyncMapActionsAndStatesExample(MapActionsAndStates):
+
+ @property
+ def is_async(self) -> bool:
+ return True
+
+ # ... other methods ...
+
+3. Use async persisters with connection pools
+----------------------------------------------
+
+When using state persistence with async parallelism, make sure to use the async version of persisters and initialize them with a connection pool:
+
+.. code-block:: python
+
+ from burr.integrations.persisters.b_asyncpg import AsyncPGPersister
+
+ # Create an async persister with a connection pool
+ persister = AsyncPGPersister.from_values(
+ host="localhost",
+ port=5432,
+ user="postgres",
+ password="postgres",
+ database="burr",
+ use_pool=True # Important for parallelism!
+ )
+
+ app = (
+ ApplicationBuilder()
+ .with_state_persister(persister)
+ .with_action(
+ async_parallel_action=AsyncMapActionsAndStatesExample(),
+ )
+ .abuild()
+ )
+
+Connection pools are crucial for handling concurrent operations. Direct connections cannot be shared across different tasks and may cause errors in concurrent scenarios.
+
+Remember to properly clean up your async persisters when you're done with them:
+
+.. code-block:: python
+
+ # Using as a context manager
+ async with AsyncPGPersister.from_values(..., use_pool=True) as persister:
+ # Use persister here
+
+ # Or manual cleanup
+ persister = AsyncPGPersister.from_values(..., use_pool=True)
+ try:
+ # Use persister here
+ finally:
+ await persister.cleanup()
+
+
+Notes
+=====
+
+This is in an experimental release phase. While we anticipate the API to be stable, we reserve the right to make slight changes (and will obviously add new features).
+Caveats:
+
+1. Parallelism does not work with typed state (yet) -- we aim to fix this to work soon, but do not rely on sub-actions or parallel actions working together with typed state yet.
+
+
+Things that may change:
+
+1. We will likely alter the executor API to be more flexible, although we will probably allow for use of the current executor API
+2. We will be adding guard-rails for generator-types (sync versus async)
+3. The UI is a WIP -- we have more sophisticated capabilities but are still polishing them
+4. Support for action-level executors
diff --git a/content/_sources/concepts/planned-capabilities.rst.txt b/content/_sources/concepts/planned-capabilities.rst.txt
new file mode 100644
index 00000000..c7f45b3b
--- /dev/null
+++ b/content/_sources/concepts/planned-capabilities.rst.txt
@@ -0,0 +1,96 @@
+====================
+Planned Capabilities
+====================
+
+These are on the roadmap (and will be part of Burr in the imminent future), but have not been built yet.
+
+We build fast though, so let us know which ones you need and they'll be in there before you know it!
+
+
+------------------------------------
+Evaluation & Annotation integrations
+------------------------------------
+
+You can already annotate with the Burr UI. We have plans to:
+
+1. plug in metrics and custom callbacks/LLM as judge capabilities to enable you to evaluate what captured and annotated.
+2. SDK functionality to allow programmatic annotation and evaluation, as well as export and search of annotations.
+
+-----------------------------
+State Management/Immutability
+-----------------------------
+
+We plan the ability to manage state in a few ways:
+
+1. ``commit`` -- an internal tool to commit/compile a series of changes so that we have the latest state evaluated
+2. ``persist`` -- a user-facing API to persist state to a database. This will be pluggable by the user, and we will have a few built-in options (e.g. a simple in-memory store, a file store, a database store, etc...)
+3. ``hydrate`` -- a static method to hydrate state from a database. This will be pluggable by the user, and we will have a few built-in options that mirror those in ``persist`` options.
+
+Currently state is immutable, but it utilizes an inefficient copy mechanism. This is out of expedience -- we don't anticipate this will
+be painful for the time being, but plan to build a more efficient functional paradigm. We will likely have:
+
+1. Each state object be a node in a linked list, with a pointer to the previous state. It carries a diff of the changes from the previous state.
+2. An ability to ``checkpoint`` (allowing for state garbage collection), and store state in memory/kill out the pointers.
+
+We will also consider having the ability to have a state solely backed by redis (and not memory), but we are still thinking through the API.
+
+----------------------
+Compilation/Validation
+----------------------
+
+We currently do not validate that the chain of actions provide a valid state, although we plan to walk the graph to ensure that no "impossible"
+situation is reached. E.G. if an action reads from a state that is not written to (or not initialized), we will raise an error, likely upon calling `validate`.
+We may be changing the behavior with defaults over time.
+
+--------------------
+Exception Management
+--------------------
+
+Currently, exceptions will break the control flow of an action, stopping the program early. Thus,
+if an exception is expected, the program will stop early. We will be adding the ability to conditionally transition based
+on exceptions, which will allow you to transition to an error-handling (or retry) action that does not
+need the full outputs of the prior action.
+
+Here is what it would look like in the current API:
+
+.. code-block:: python
+
+ @action(reads=["attempts"], writes=["output", "attempts"])
+ def some_flaky_action(state: State, max_retries: int=3) -> State:
+ result = {"output": None, "attempts": state["attempts"] + 1}
+ try:
+ result["output"] = call_some_api(...)
+ except APIException as e:
+ if state["attempts"] >= max_retries:
+ raise e
+ return state.update(**result)
+
+One could imagine adding it as a condition (a few possibilities)
+
+.. code-block:: python
+
+ @action(reads=[], writes=["output"])
+ def some_flaky_action(state: State) -> State
+ result = {"output": call_some_api(...)}
+ return state.update(**result)
+
+ 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
+ )
+ )
+
+Will have to come up with ergonomic APIs -- the above are just some ideas.
+
+------------
+Integrations
+------------
+
+We already have support for Haystack, Hamilton, and LangChain.
+Please request any other integrations you'd like to see.
diff --git a/content/_sources/concepts/recursion.rst.txt b/content/_sources/concepts/recursion.rst.txt
new file mode 100644
index 00000000..e27dd06f
--- /dev/null
+++ b/content/_sources/concepts/recursion.rst.txt
@@ -0,0 +1,163 @@
+.. _recursion:
+
+======================
+Recursive Applications
+======================
+
+Burr supports sub-applications. E.G. running Burr within Burr.
+Currently this capability is only done with tracking (linking applications),
+but we are actively working on building first-level constructs that allow this.
+
+This can enable the following use-cases (among many others):
+
+1. Parallel results to different LLMs for comparison
+2. Multiple simultaneous tasks/chains for an LLM to answer at once
+3. Black-boxing an action as a subset of tasks
+4. Running multiple queries simultaneously and getting an LLM to synthesize
+
+And so on. These can be arbitrarily complex and nested, allowing agents consisting of agents consisting of agents...
+
+.. image:: ../_static/meme.png
+ :align: center
+
+You can find an example `here <https://github.com/dagworks-inc/burr/tree/main/examples/recursive>`_.
+
+--------
+Tracking
+--------
+
+Currently, to leverage nested Burr apps, you do the following:
+
+1. Create a parent app (as you normally would)
+2. Leverage tracking
+3. Create an action inside that
+4. Run your Burr sub-application inside that action
+5. Wire through tracker + parent data for the sub-application
+
+
+After creating a simple application (1)/(2) above, you can wire through the tracker like this (some pseudocode, see the example for a fully working application).
+
+
+.. code-block:: python
+
+ @action(reads=["parameters_to_map"], writes=["joined_results"])
+ def run_multiple_sub_apps(state: State, __context: ApplicationContext) -> State:
+ # for every parameter incoming, run a sub-application
+ results = []
+ for parameter_set in state["parameters_to_map"]:
+ subapp = (
+ ApplicationBuilder()
+ .with_actions(...)
+ .with_transitions(...)
+ .with_tracker(__context.tracker.copy())
+ .with_spawning_parent(
+ __context.app_id,
+ __context.sequence_id,
+ __context.partition_key,
+ )
+ .with_entrypoint(...)
+ .with_state(...)
+ .build()
+ )
+ results.append(subapp.run(...))
+
+ joined = _join(results)
+
+ return state.update(joined_results= _join(results))
+
+Note we're simply passing through the tracker to the next app, which sets a link between them.
+The tracker knows how to record the link so its in the UI.
+
+You can also run this in parallel -- this leverages `joblib <https://joblib.readthedocs.io/en/latest/>`_'s
+Parallel and delayed functions, but you can use whatever tooling you want (multiprocessing, threading, etc...).
+
+.. code-block:: python
+
+ @action(reads=["parameters_to_map"], writes=["joined_results"])
+ def run_multiple_sub_apps(state: State, __context: ApplicationContext) -> State:
+ def run_subapp(parameter_set):
+ subapp = (
+ ApplicationBuilder()
+ .with_actions(...)
+ .with_transitions(...)
+ .with_tracker(__context.tracker.copy() if __context.tracker is not None else None)
+ .with_spawning_parent(
+ __context.app_id,
+ __context.sequence_id,
+ __context.partition_key,
+ )
+ .with_entrypoint(...)
+ .with_state(...)
+ .build()
+ )
+ return subapp.run(...)
+
+ results = Parallel(n_jobs=-1)(delayed(run_subapp)(parameter_set) for parameter_set in state["parameters_to_map"])
+ joined = _join(results)
+
+ return state.update(joined_results=joined)
+
+Note the following:
+
+1. You don't have to pass in partition_key, but you need to pass in the app_id and sequence_id so the app can track
+2. You currently need to run ``__context.tracker.copy()`` to ensure the tracker is passed through correctly. We will likely be relaxing this, but it could corrupt the tracking state if you forget.
+3. You may want to have more control over the application ID's -- you may want to use a stable hash of the parameters and the parent App ID
+
+For persistence, you'll want to use (3) to ensure a stable hash -- have the sub-application load its own state from the ID, resuming where it left off.
+
+We currently don't wire through persisters, although we are planning to add that shortly. For the meanwhile, you'll want to leverage the right persister
+yourself.
+
+When you track in the UI, you will see the following (in this example we're generating poems of different styles in parallel):
+
+.. image:: ../_static/recursive_steps.png
+ :align: center
+
+Alternatives
+------------
+
+If, for some reason, you want to run a Burr application inside a burr application but can't effectively wire through the tracker/context
+(say, for instance, that it's run through levels of other frameworks), you can get access to the active context and tracker by using the following:
+
+.. code-block:: python
+
+ from burr import get_active_context, get_active_tracker, ApplicationContext
+
+ @action(reads=["parameters_to_map"], writes=["joined_results"])
+ def my_action(state: State) -> State:
+ context = ApplicationContext.get()
+ def run_subapp(parameter_set):
+ subapp = (
+ ApplicationBuilder()
+ .with_actions(...)
+ .with_transitions(...)
+ .with_tracker(context.tracker.copy() if context.tracker is not None else None)
+ .with_spawning_parent(
+ context.app_id,
+ context.sequence_id,
+ context.partition_key,
+ )
+ .with_entrypoint(...)
+ .with_state(...)
+ .build()
+ )
+ return subapp.run(...)
+
+ results = Parallel(n_jobs=-1)(delayed(run_subapp)(parameter_set) for parameter_set in state["parameters_to_map"])
+ joined = _join(results)
+
+ # do something with the context and tracker
+
+ return state
+
+-------------------
+Future Improvements
+-------------------
+
+We are working on the following:
+
+1. A first class way to define a recursive application application to allow for more ergonomic management of the above
+2. Static visualization of the recursive application when using (1)
+3. A more ergonomic way to pass through the tracker
+
+Stay tuned!
diff --git a/content/_sources/concepts/sdlc.rst.txt b/content/_sources/concepts/sdlc.rst.txt
new file mode 100644
index 00000000..0e58d302
--- /dev/null
+++ b/content/_sources/concepts/sdlc.rst.txt
@@ -0,0 +1,27 @@
+================================
+SDLC with LLMs
+================================
+If you're building an LLM-based application, you'll want to follow a slightly different software development lifecycle (SDLC)
+than you would for a traditional software project. Here's a rough outline of what that might look like:
+
+.. image:: ../_static/burr_sdlc.png
+ :alt: SDLC with LLMs
+ :align: center
+
+The two cycles that exist are:
+
+1. App Dev Loop.
+2. Test Driven Development Loop.
+
+and you will use one to feed into the other, etc.
+
+Walking through the diagram the SDLC looks like this:
+
+1. Write code with Burr.
+2. Use Burr's integrated observability, and trace all parts of your application.
+3. With the data collected, you can: (1) annotate what was captured and export it, or (2) create a pytest fixture with it.
+4. Create a data set from the annotated data or by running tests.
+5. Evaluate the data set.
+6. Analyze the results.
+7. Either adjust code or prompts, or ship the code.
+8. Iterate using one of the loops...
diff --git a/content/_sources/concepts/serde.rst.txt b/content/_sources/concepts/serde.rst.txt
new file mode 100644
index 00000000..37a84748
--- /dev/null
+++ b/content/_sources/concepts/serde.rst.txt
@@ -0,0 +1,113 @@
+.. _serde:
+
+================================
+Serialization / Deserialization
+================================
+
+Core to :ref:`state-persistence <state-persistence>` is the ability to serialize and deserialize objects.
+
+Burr comes with a pluggable serialization/deserialization mechanism.
+
+There are two approaches: class/type based, & field level registration. See :py:func:`serialize <burr.core.serde.serialize>` and :py:func:`deserialize <burr.core.serde.deserialize>` for reference details.
+
+How it works
+------------
+The :py:class:`State <burr.core.state.State>` object has a :py:meth:`serialize <burr.core.state.State.serialize>` method that returns a dictionary.
+The :py:class:`State <burr.core.state.State>` class also has a :py:meth:`deserialize <burr.core.state.State.deserialize>` method that takes a dictionary and returns a state object.
+
+It is then delegated to persisters and trackers to call these methods and store the serialized state.
+
+Underneath the State object delegates to the :py:func:`serialize <burr.core.serde.serialize>` and :py:func:`deserialize <burr.core.serde.deserialize>` functions.
+
+Customizing Serialization/Deserialization
+-----------------------------------------
+Here's a video walkthrough of how to add custom type and field serialization/deserialization:
+
+.. raw:: html
+
+ <div>
+ <iframe width="800" height="455" src="https://www.youtube.com/embed/Squ5IAeQBzc?si=6l6e0SJ0EqEjAW2K" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
+ </div>
+
+See `this example <https://github.com/DAGWorks-Inc/burr/tree/main/examples/custom-serde>`_ for the notebook.
+
+Type based serialization/deserialization
+_____________________________________________________
+To create your own python type based serialization/deserialization mechanism, you need to implement the following code. The assumption
+here is that you have some custom class/type you want to serialize/deserialize.
+
+.. code-block:: python
+
+ from typing import Any, Dict
+ from burr.core import serde
+
+ class MY_CLASS:
+ # your custom class/type
+ pass
+
+ @serde.serialize.register(MY_CLASS)
+ def serialize_myclass(value: cls, myclass_kwargs: dict = None, **kwargs) -> dict:
+ """Serializes the value using my custom methodology.
+
+ :param value: the value to serialize.
+ :param myclass_kwargs: not required. Optional.
+ :param kwargs:
+ :return: dictionary of serde.KEY and value
+ """
+ if myclass_kwargs is None:
+ myclass_kwargs = {}
+ return {
+ # required to identify how to deserialize
+ serde.KEY: "myclass",
+ # delegate to your custom serialization
+ "value": some_custom_serialization(value, **myclass_kwargs),
+ }
+
+ @serde.deserializer.register("myclass")
+ def deserialize_myclass(value: dict, myclass_kwargs: dict = None, **kwargs) -> cls:
+ """Deserializes the value using my custom methodology.
+
+ :param value: the value to deserialize from.
+ :param myclass_kwargs: not required. Optional.
+ :param kwargs:
+ :return: object of type cls
+ """
+ if myclass_kwargs is None:
+ myclass_kwargs = {}
+ # delegate to your custom deserialization
+ return some_custom_deserialization(value["value"], **myclass_kwargs)
+
+You'll need to this code to run/be imported so it can register itself.
+
+Field level Serialization/Deserialization
+_________________________________________
+
+.. _state-field-serialization:
+
+Field level serialization/deserialization is for when you want to further customize your state serialization
+strategy. E.g. you have two fields of the same type but want to serialize things differently.
+Registration of this approach is handled by a registration function in the `core.state` module.
+When serializing/deserializing state, field names will be first checked to see if there is a
+custom serializer/deserializer registered for that field, before delegating to the default
+type based serialization/deserialization mechanism.
+
+.. code-block:: python
+
+ from burr.core import state
+
+ def my_field_serializer(value: MyType, **kwargs) -> dict:
+ serde_value = _do_something_to_serialize(value)
+ return {"value": serde_value}
+
+ def my_field_deserializer(value: dict, **kwargs) -> MyType:
+ serde_value = value["value"]
+ return _do_something_to_deserialize(serde_value)
+
+ state.register_field_serde("my_field", my_field_serializer, my_field_deserializer)
+
+This will register a custom serializer/deserializer for the field "my_field".
+
+Requirements for the serializer/deserializer functions:
+
+ 1. The serializer function needs to return a dictionary.
+ 2. Both function signatures needs to have a ``**kwargs`` parameter to allow for custom arguments to be passed in. We advise namespacing the kwargs provided to avoid conflicts with other serializers/deserializers.
diff --git a/content/_sources/concepts/state-machine.rst.txt b/content/_sources/concepts/state-machine.rst.txt
new file mode 100644
index 00000000..d1bf5f87
--- /dev/null
+++ b/content/_sources/concepts/state-machine.rst.txt
@@ -0,0 +1,183 @@
+====================
+Applications
+====================
+
+.. _applications:
+
+.. note::
+
+ Applications tie everything in Burr together. They specify which actions should be included,
+ how they connect (including conditional edges), and how to run.
+
+Applications form the core representation of the state machine. You build them with the ``ApplicationBuilder``.
+Here is the minimum that is required:
+
+1. A ``**kwargs`` of actions passed to ``with_actions(...)``
+2. Any relevant transitions (with conditions)
+3. An entry point -- this is the first action to execute
+
+This is shown in the example from :ref:`getting started <simpleexample>`
+
+.. code-block:: python
+
+ from burr.core import ApplicationBuilder, default, expr
+ app = (
+ ApplicationBuilder()
+ .with_actions(human_input, ai_response)
+ .with_transitions(
+ ("human_input", "ai_response"),
+ ("ai_response", "human_input")
+ ).with_state(chat_history=[])
+ .with_entrypoint("human_input")
+ .build()
+ )
+
+-------
+Running
+-------
+
+There are four APIs for executing an application.
+
+``step``/``astep``
+------------------
+
+Returns the tuple of the action, the result of that action, and the new state. Call this if you want to run the application step-by-step.
+
+.. code-block:: python
+
+ action, result, state = application.step()
+
+If you're in an async context, you can run `astep` instead:
+
+.. code-block:: python
+
+ action, result, state = await application.astep()
+
+
+Step can also take in ``inputs`` as a dictionary, which will be passed to the action's run function as keyword arguments.
+This is specifically meant for a "human in the loop" scenario, where the action needs to ask for input from a user. In this case,
+the control flow is meant to be interrupted to allow for the user to provide input. See :ref:`inputs <inputref>` for more information.
+
+.. code-block:: python
+
+ action, result, state = application.step(inputs={"prompt": input()})
+
+``iterate``/``aiterate``
+------------------------
+
+Iterate just runs ``step`` in a row, functioning as a generator:
+
+.. code-block:: python
+
+ for action, result, state in application.iterate(halt_after=["final_action_1", "final_action_2"]):
+ print(action.name, result)
+
+You can also run ``aiterate`` in an async context:
+
+.. code-block:: python
+
+ async for action, result, state in application.aiterate():
+ print(action.name, result)
+
+
+In the synchronous context this also has a return value of a tuple of:
+
+
+#. the action that was specified in ``halt_after`` or ``halt_before``. In the `after` case the action will have already run. In the ``before`` case the action will not have run.
+
+#. The result of that action, in the ``halt_after`` case, else None in the ``halt_before`` case.
+
+#. The state of the application at the time of halting.
+
+You can access this by looking at the ``value`` variable of the ``StopIteration`` exception that is thrown
+at the end of the loop, as is standard for python generators.
+See the function implementation of ``run`` to show how this is done.
+
+In the async context, this does not return anything (asynchronous generators are not allowed a return value).
+
+If you want it to (attempt to) run forever, you can pass empty lists to ``halt_after`` and ``halt_before``.
+
+.. note::
+ You can add inputs to ``iterate``/``aiterate`` by passing in a dictionary of inputs through the ``inputs`` parameter.
+ This will only apply to the first action. Actions that are not the first but require inputs are considered undefined behavior.
+
+.. warning::
+ The state machine has the capability of halting even if it does not reach one of the specified conditions -- if there are
+ no more transitions to take. This, however, is not a supported feature (and will log a warning). This is considered undefined behavior
+ -- use at your own risk, and consider adding a halting condition, or a default transition to a terminal.
+
+``run``/``arun``
+----------------
+
+Run just calls out to ``iterate`` and returns the final state.
+
+The ``halt_after`` and ``halt_before`` keyword arguments specify when to break out of running the state machine
+and return control back. ``halt_after`` will stop after the specified action(s) has run, and ``halt_before`` will stop before the specified action(s) has run.
+If multiple are specified, it will stop after the first one encountered, and the return values will be for that action.
+
+.. code-block:: python
+
+ action, result, state = application.run(
+ halt_after=["final_action_1", "final_action_2"]
+ )
+
+
+In the async context, you can run ``arun``:
+
+.. code-block:: python
+
+ action, result, state = await application.arun(
+ halt_after=["final_action_1", "final_action_2"]
+ )
+
+.. note::
+ You can add inputs to ``run``/``arun`` in the same way as you can with ``iterate`` -- it will only apply to the first action.
+
+``run`` and ``arun`` largely have the same behavior as ``iterate`` and ``aiterate``.
+
+``stream_result``/``astream_result``
+------------------------------------
+
+These allow you to stream responses back. Note they work best with streaming actions.
+Read about them in the :ref:`streaming <streaming>` section.
+
+---------
+Graph API
+---------
+
+The application exposes a graph API, allowing you to specify it as a graph separate from application concerns.
+While you likely won't use this in your first few applications, it will become useful when you want to run it
+in a web-server (create a graph once, application many times), import the graph from another context, or run it in multiple contexts.
+
+
+.. code-block:: python
+
+ from burr.core import ApplicationBuilder, default, expr
+ graph = (
+ GraphBuilder()
+ .with_actions(human_input, ai_response)
+ .with_transitions(
+ ("human_input", "ai_response"),
+ ("ai_response", "human_input")
+ ).build()
+ )
+ app = (
+ ApplicationBuilder()
+ .with_graph(graph)
+ with_state(chat_history=[])
+ .with_entrypoint("human_input")
+ .build()
+ )
+
+You can read more about it in the :py:class:`GraphBuilder docs <burr.core.graph.GraphBuilder>`.
+
+----------
+Inspection
+----------
+
+You can ask various questions of the state machine using publicly-supported APIs:
+
+- ``application.graph`` will give you a static representation of the state machine with enough information to visualize
+- ``application.state`` will give you the current state of the state machine. Note that if you modify it the results will not show up -- state is immutable! Modify the state through actions.
+
+See the :ref:`application docs <applicationref>`
diff --git a/content/_sources/concepts/state-persistence.rst.txt b/content/_sources/concepts/state-persistence.rst.txt
new file mode 100644
index 00000000..e48c8690
--- /dev/null
+++ b/content/_sources/concepts/state-persistence.rst.txt
@@ -0,0 +1,268 @@
+=================
+State Persistence
+=================
+
+.. _state-persistence:
+
+.. note::
+
+ Burr comes with a core set of APIs that enable state `persistence` -- the ability
+ to save and load state automatically from a database. This enables you to launch an application,
+ pause it, and restart where you left off. The API is customizable, and works with any database you want.
+
+TL;DR
+-----
+
+Burr provides an API to save and load state from a database. This enables you to pause and restart applications where you left off.
+You can implement a custom state persister or use one of the pre-built ones. You can fork state from a previous run to enable debugging/loading.
+
+
+Persisting State
+----------------
+
+The key to writing a real life ``burr`` application is state persistence. For example, say you're building a chat bot and you
+want to store the conversation history and then reload it when you restart. Or, you have a long running process/series of agents,
+and you want to store the state of the process after each action, and then reload it if it fails, etc....
+
+``Burr`` provides a few simple interfaces to do this with minimal changes. Let's walk through a simple chatbot example as we're explaining concepts:
+
+Two notable assumptions:
+
+1. for library provided persisters, state needs to ultimately be JSON serializable. If it's not, you can use the :doc:`serde` API to customize serialization and deserialization.
+2. your general assumption should be that strings are/will be encoded as UTF-8, however this is dependent on the persister you use.
+
+State Keys
+----------
+Burr `applications` are, by default, keyed on two entities:
+
+1. ``app_id`` - A unique identifier for the application. This is used to identify the application in the persistence layer.
+2. ``partition_key`` - An identifier for grouping (partitioning) applications
+
+In the case of a chatbot, the ``app_id`` could be a uuid, and the ``partition_key`` could be the user's ID or email, etc.
+Note that ``partition_key`` can be `None` if this is not relevant. A UUID is always generated for the ``app_id`` if not provided.
+
+You set these values using the :py:meth:`with_identifiers() <burr.core.application.ApplicationBuilder.with_identifiers>` method.
+
+Note: to access application-level metadata such as ``app_id``, ``partition_key``, ``sequence_id``, and ``action_name`` in your running application, you can have the :py:class:`ApplicationContext <burr.core.application.ApplicationContext>`
+injected into your Burr Actions. This is done by adding ``__context`` to the action signature:
+
+.. code-block:: python
+
+ from burr.core import action, State, ApplicationContext
+
+ @action(reads=[...], writes=[...])
+ def my_action(state: State, __context: ApplicationContext) -> State:
+ app_id = __context.app_id
+ partition_key = __context.partition_key
+ action_name = __context.action_name
+ sequence_id = __context.sequence_id
+ ...
+
+
+Initializing state
+------------------
+
+To initialize state from a database, you can employ the :py:meth:`initialize_from <burr.core.application.ApplicationBuilder.initialize_from>` method
+in the :py:class:`ApplicationBuilder <burr.core.application.ApplicationBuilder>`. Important API note: unless `fork_from_app_id` is specified
+below, the values used to query for state are taken from the values provided to :py:meth:`with_identifiers() <burr.core.application.ApplicationBuilder.with_identifiers>`.
+
+This action takes in an initializer (an implementation of :py:class:`StateInitializer <burr.core.persistence.BaseStateLoader>`) a well as:
+
+- ``resume_at_next_action`` -- a boolean that says whether to start where you left off, or go back to the ``default_entrypoint``.
+- ``default_entrypoint`` -- the entry point to start at if ``resume_at_next_action`` is False, or no state is found
+- ``default_state`` -- the default state to use if no state is found
+- ``fork_from_app_id`` -- Optional. A prior app_id to fork state from. This is useful if you want to start from a previous application's state.
+- ``fork_from_partition_key`` -- Optional. The partition key to fork from. Goes with ``fork_from_app_id``.
+- ``fork_from_sequence_id`` -- Optional. The sequence_id to fork from. Goes with ``fork_from_app_id``.
+
+
+Note (1): that you cannot use this in conjunction with :py:meth:`with_state <burr.core.application.ApplicationBuilder.with_state>`
+or :py:meth:`with_entrypoint <burr.core.application.ApplicationBuilder.with_entrypoint>` -- these are mutually exclusive. If you're
+switching to use state persistence, all you need to do is remove the ``with_state`` and ``with_entrypoint`` calls and replace them with
+the default state and entrypoint in the ``initialize_from`` call.
+
+Note (2): The loader will not error if no state is found, it will use the default state in this case.
+
+Forking State
+_____________
+When loading you can also fork state from a previous application. This is useful if you want to start from a previous application's state,
+but don't want to add to it. The ``fork_from_app_id`` and ``fork_from_partition_key`` are used to identify the application to fork from, while
+``fork_from_sequence_id`` is used to identify the sequence_id to use. This is useful if you want to fork from a specific point in the application,
+rather than the latest state. This is especially useful for debugging, or building an application that enables you
+to rewind state and make different choices.
+
+When you use the ``fork_from_app_id`` to load state, the values passed to :py:meth:`with_identifiers() <burr.core.application.ApplicationBuilder.with_identifiers>`
+will then dictate where the new application state is ultimately stored.
+
+For a quick overview of using it, Ashis from `PeanutRobotics <https://peanutrobotics.com>`_ has kindly submitted a
+video on how they use this feature:
+
+.. raw:: html
+
+ <iframe width="800" height="455" src="https://www.youtube.com/embed/98vxhIcE6NI?si=w1vMHr9QUxjlVVgm" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
+
+
+Writing state
+_____________
+
+To write state to a database, you can use the :py:meth:`with_state_persister <burr.core.application.ApplicationBuilder.with_state_persister>` method in the
+:py:class:`ApplicationBuilder <burr.core.application.ApplicationBuilder>`. This takes in a persister (an implementation of
+:py:class:`StatePersister <burr.core.persistence.BaseStatePersister>`). It writes state to the database after each action.
+
+
+An example
+__________
+
+To make the above more concrete, let's look at a basic chatbot:
+
+.. code-block:: python
+
+ state_persister = SQLLitePersister.from_values(db_path=".sqllite.db", table_name="burr_state")
+ app = (
+ ApplicationBuilder()
+ .with_actions(
+ ai_converse=ai_converse,
+ human_converse=human_converse,
+ terminal=burr.core.Result("chat_history"),
+ )
+ .with_transitions(
+ ("ai_converse", "human_converse", default),
+ ("human_converse", "terminal", expr("'exit' in question")),
+ ("human_converse", "ai_converse", default),
+ )
+ .initialize_from(
+ state_persister,
+ resume_at_next_action=True,
+ default_state={"chat_history" : []},
+ default_entrypoint="human_converse"
+ )
+ .with_state_persister(state_persister)
+ .with_identifiers(app_id=app_id)
+ .build()
+ )
+
+In this case, we both read and write using the ``SQLLitePersistor``. Note that this is the most common case.
+However, if you want to just read (E.G. for debugging), or you want to just write (if you're always creating a new app),
+you can leave out ``initialize`` or ``with_state_persister`` respectively.
+
+
+
+Supported Persistence Backends
+______________________________
+See :ref:`available persisters here <persistersref>`.
+Note that the tracker also allows reloading from a file, but this is not recommended for production use.
+
+
+Persister Usage
+______________________________
+We follow the naming convention ``b_database-dependency-library``, where the ``b_`` is used to avoid name
+clashing with the underlying library. We chose the library name in case we implement the same database
+persister with different dependency libraries to keep the class naming convention.
+
+To initialize the persister we recommend using one of the two class methods: ``.from_config(...)`` or ``.from_values(...)`` that
+establish a connection to the database and return the persister object. In case you already have a database connection
+established (or using a connection pool), you can also initialize the persister directly and pass in the database
+connection as the first argument.
+
+We recommend that you manually handle the database connection cleanup. For now, in the case of
+synchronous persisters, the connection gets closed when the object is deleted. For asynchronous
+persisters this is not possible. Therefore, we suggest that you either use the persister as a context
+manager
+
+.. code-block:: python
+
+ with SQLLitePersister.from_values(
+ db_path=".sqllite.db",
+ table_name="burr_state"
+ ) as state_persister:
+
+ app = (
+ ApplicationBuilder()
+ .with_actions(...)
+ .with_transitions(...)
+ .initialize_from(
+ state_persister, ...
+ )
+ .with_state_persister(state_persister)
+ .with_identifiers(app_id=app_id)
+ .build()
+ )
+
+ *_, state = app.run(...)
+
+or manually close the connection to the database, by using the `.cleanup()` method of the persister
+
+.. code-block:: python
+
+ state_persister = SQLLitePersister.from_values(db_path=".sqllite.db", table_name="burr_state")
+ app = (
+ ApplicationBuilder()
+ .with_actions(...)
+ .with_transitions(...)
+ .initialize_from(
+ state_persister, ...
+ )
+ .with_state_persister(state_persister)
+ .with_identifiers(app_id=app_id)
+ .build()
+ )
+
+ try:
+ *_, state = app.run(...)
+ except Exception as e:
+ ...
+ finally:
+ state_persister.cleanup()
+
+to ensure no database connection leakage.
+
+
+Customizing State Persistence
+-----------------------------
+
+Burr exposes two APIs :py:class:`BaseStatePersister <burr.core.persistence.BaseStatePersister>` (sync) and
+:py:class:`AsyncBaseStatePersister <burr.core.persistence.AsyncBaseStatePersister>` (async) for custom state persistence. Implement,
+pass into the above functions, and you can write to whatever database you want! Please contribute back to the community if you do so.
+
+
+Loading from the Tracker
+------------------------
+
+You can use the tracking feature additionally as a persister. This enables you to load from prior
+tracked runs. This is useful for debugging, or building an application that enables you to rewind state and make different choices.
+
+.. code-block:: python
+
+ tracker = LocalTrackingClient(project=project_name)
+ app = (
+ ApplicationBuilder()
+ .with_actions(
+ ai_converse=ai_converse,
+ human_converse=human_converse,
+ terminal=burr.core.Result("chat_history"),
+ )
+ .with_transitions(
+ ("ai_converse", "human_converse", default),
+ ("human_converse", "terminal", expr("'exit' in question")),
+ ("human_converse", "ai_converse", default),
+ )
+ .initialize_from(
+ tracker,
+ resume_at_next_action=True,
+ default_state={"chat_history" : []},
+ default_entrypoint="human_converse
+ )
+ .with_tracker(tracker)
+ .with_identifiers(app_id=app_id)
+ .build()
+ )
+
+In this case the ``LocalTrackingClient`` is used both as a persister and a loader. It will persist as it is
+running (by tracking), and then load from the tracker if the application is restarted. This is useful for local development.
+
+Custom Serialization and Deserialization
+----------------------------------------
+See :doc:`serde` for more information on how to customize state serialization and deserialization.
+
+This includes how to register custom serializers and deserializers based on type, as well
+as :ref:`registering custom serializers and deserializers for a field<state-field-serialization>` in state.
diff --git a/content/_sources/concepts/state-typing.rst.txt b/content/_sources/concepts/state-typing.rst.txt
new file mode 100644
index 00000000..337f82b8
--- /dev/null
+++ b/content/_sources/concepts/state-typing.rst.txt
@@ -0,0 +1,123 @@
+.. _statetyping:
+
+============
+Typing State
+============
+
+.. note::
+
+ Burr allows you to specify types for your state. This enables more self-documenting, easy-to-write actions,
+ allows you to type your entire application object for downstream consumption, and provides a way to inspect
+ the state of your application prior to execution (for use by a web-server/other typing system). This is done
+ through the use of pydantic schemas.
+
+For a quick-start guide, see the `example <https://github.com/DAGWorks-Inc/burr/tree/main/examples/typed-state>`_
+
+
+Burr has two approaches to specifying a schema for a state. These can work together as long as they specify clashing state:
+
+1. Application-level typing
+2. Action-level typing
+
+These enable a host of other extensions/capabilities.
+
+While these are built for pydantic, the typing system is intended to be pluggable, and we plan to add further integrations
+(dataclasses, typed dicts, etc...).
+
+
+Application-level Typing
+------------------------
+
+To type at the application level, you can use the :py:meth:`ApplicationBuilder.with_typing burr.core.application.ApplicationBuilder.with_typing` method.
+This takes in a :py:class:`TypingSystem burr.core.typing.TypingSystem` object, which specifies how to gather types from/assign types to the state.
+
+This looks as follows:
+
+First, define a pydantic model for your application:
+
+.. code-block:: python
+
+ from pydantic import BaseModel
+
+ class ApplicationState(pydantic.BaseModel):
+ chat_history: List[dict[str, str]] = pydantic.Field(default_factory=list)
+ prompt: Optional[str] = None
+ mode: Optional[Literal["text", "image"]] = None
+ response: Optional[dict[str, str]] = None
+
+Then, we can use this model to type our application:
+
+.. code-block:: python
+
+ from burr.core import ApplicationBuilder
+ from burr.integrations.pydantic import PydanticTypingSystem
+
+ app = (
+ ApplicationBuilder()
+ .with_actions(...)
+ .with_entrypoint(...)
+ .with_transitions(...)
+ .with_typing(PydanticTypingSystem(ApplicationState))
+ .with_state(ApplicationState())
+ .build()
+ )
+
+Your application is now typed with that pydantic model. If you're using an appropriate typing
+integration in your IDE (E.G. pylance), it will know that the state of your application is of type
+``MyApplicationState``.
+
+When you have this you'll be able to run:
+
+.. code-block:: python
+
+ action_ran, result, state = app.run(inputs=...)
+ state.data # of type ApplicationState -- do what you want with this!
+
+
+Action-level typing
+-------------------
+
+You can also define type computations on on the action-level:
+
+.. code-block:: python
+
+ from burr.core import action
+
+ @action.pydantic(reads=["prompt", "chat_history"], writes=["response"])
+ def image_response(state: ApplicationState, model: str = "dall-e-2") -> ApplicationState:
+ client = _get_openai_client()
+ result = client.images.generate(
+ model=model, prompt=state.prompt, size="1024x1024", quality="standard", n=1
+ )
+ response = result.data[0].url
+ state.response = {"content": response, "type": MODES[state.mode], "role": "assistant"}
+ return state
+
+Note three interesting choices here:
+
+1. The state is typed as a pydantic model
+2. The return type is the same pydantic model
+3. We mutate the state in place, rather than returning a new state
+
+This is a different action API -- it effectively subsets the state on input,
+gives you that object, then subsets the state on output, and merges it back.
+
+Thus if you try to refer to a state variable that you didn't specify in the reads/writes,
+it will give an error.
+
+Mutating in place is OK as this produces a new object for each execution run. For now, you will
+want to be careful about lists/list pointers -- we are working on that.
+
+
+Application + Action-level typing
+---------------------------------
+
+Application-level typing has the benefit of giving you application-level IDE autocompletion and allowing you to specify
+the schema. Action level typing makes it easier to write actions, and allows for more flexible/modular actions without specifying
+the whole state in advance.
+
+You can use them both as long as the types match up. If not, this will error out.
+
+.. note::
+
+ We have not yet implemented validation on more than just the action level -- this is coming soon!
diff --git a/content/_sources/concepts/state.rst.txt b/content/_sources/concepts/state.rst.txt
new file mode 100644
index 00000000..b5e87413
--- /dev/null
+++ b/content/_sources/concepts/state.rst.txt
@@ -0,0 +1,62 @@
+=====
+State
+=====
+
+.. _state:
+
+.. note::
+
+ Burr's ``State`` API enables actions to talk to each other, and enables you to persist data.
+ Burr has a ``State`` API that allows you to manipulate state in a functional way.
+
+The :py:class:`State <burr.core.state.State>` class provides the ability to manipulate state for a given action. It is entirely immutable,
+meaning that you can only create new states from old ones, not modify them in place.
+
+
+State manipulation is done through calling methods on the ``State`` class. The most common write are:
+
+.. code-block:: python
+
+ state.update(foo=bar) # update the state with the key "foo" set to "bar"
+ state.append(foo=bar) # append "bar" to the list at "foo"
+ state.increment(foo=1) # increment the value at "foo" by 1
+ state.wipe(keep=["foo", "bar"]) # remove all keys except "foo" and "bar"
+ state.wipe(delete=["foo", "bar"]) # remove "foo" and "bar" from the state
+
+.. warning::
+
+ The ``State`` object can only be treated immutably! Calling ``state.update(foo=bar)`` will do nothing if you don't use the value returned by the call.
+
+.. warning::
+
+ State contains a set of "private" variables that start with `__`. -- E.G. `__SEQUENCE_ID`. These are internal to Burr, used by the application to track state.
+ Any modifications to them outside of the framework is considered undefined behavior -- it could be dropped, or error out (we reserve the right to alter this later).
+
+The read operations extend from those in the `Mapping <https://docs.python.org/3/library/collections.abc.html#collections.abc.Mapping>`_
+interface, but there are a few extra:
+
+.. code-block:: python
+
+ state.subset(["foo", "bar"]) # return a new state with only the keys "foo" and "bar"
+ state.get_all() # return a dictionary with every key/value of the state
+
+When an update action is run, the state is first subsetted to get just the keys that are being read from,
+then the action is run, and a new state is written to. This state is merged back into the original state
+after the action is complete. Pseudocode:
+
+.. code-block:: python
+
+ current_state = ...
+ read_state = current_state.subset(action.reads)
+ result = action.run(new_state)
+ write_state = current_state.subset(action.writes)
+ new_state = action.update(result, new_state)
+ current_state = current_state.merge(new_state)
+
+If you're used to thinking about version control, this is a bit like a commit/checkout/merge mechanism.
+
+Reloading Prior State
+---------------------
+Note, if state is serializable, it means that if stored, it can be reloaded. This is useful for
+reloading state from a previous invocation (for debugging or as part of the application), or for storing state in a database.
+We have capabilities here, see the :ref:`state-persistence <state-persistence>` section.
diff --git a/content/_sources/concepts/streaming-actions.rst.txt b/content/_sources/concepts/streaming-actions.rst.txt
new file mode 100644
index 00000000..28709345
--- /dev/null
+++ b/content/_sources/concepts/streaming-actions.rst.txt
@@ -0,0 +1,225 @@
+=================
+Streaming Actions
+=================
+.. _streaming:
+
+.. note::
+
+ Burr actions can stream results! This enables you to display results to the user as
+ tokens are streamed in.
+
+
+Actions can be implemented as streaming results. This enables a lower time-to-first-token and a more interactive
+interface in the case of AI applications or streaming in of metrics in a model-training application. Broadly,
+this is a tool to enable quicker user interaction in longer running actions that require user focus.
+
+Like other actions, these can be implemented both as functions and as classes, and can use synchronous or asynchrounous APIs.
+
+They are used differently from regular actions -- the application wraps their result in a
+:py:class:`StreamingResultContainer <burr.core.action.StreamingResultContainer>`, or a
+:py:class:`AsyncStreamingResultContainerAsync <burr.core.action.AsyncStreamingResultContainer>` for async streaming actions.
+
+----------
+Definition
+----------
+.. _streaming_actions:
+
+Streaming actions can be implemented as a class or a function, just like actions. However, they have a few additional rules:
+
+#. They give intermediate results to the user as they are produced in the form of a generator
+#. They are responsible for determining the relationship between the intermediate results and the final result
+#. They yield intermediate results to the framework, and the last yield is the final result (with everything joined). Don't forget the last yield!
+
+
+The function-based streaming action is fairly simple. Note that
+we yield a tuple of the result and the state update. The state update is ``None`` for intermediate results and
+the final state for the final result.
+
+.. code-block:: python
+
+ from burr.core.action import streaming_action
+
+ @streaming_action(reads=["prompt"], writes=["prompt"])
+ def streaming_chat_call(state: State, **run_kwargs) -> Generator[Tuple[dict, Optional[State], None, None]]:
+ client = openai.Client()
+ response = client.chat.completions.create(
+ model='gpt-3.5-turbo',
+ messages=[{
+ 'role': 'user',
+ 'content': state["prompt"]
+ }],
+ temperature=0,
+ stream=True,
+ )
+ buffer = []
+ for chunk in response:
+ delta = chunk.choices[0].delta.content
+ buffer.append(delta)
+ yield {'response': delta}, None # No state update on intermediate results
+ full_response = ''.join(buffer)
+ yield {'response': full_response}, state.append(response=full_response) # Update state on final results
+
+A class-based streaming action might look like this:
+
+.. code-block:: python
+
+ from burr.core.action import StreamingAction
+
+ class StreamingChatCall(StreamingAction):
+ def stream_run(self, state: State, **run_kwargs) -> Generator[dict, None, None]:
+ client = openai.Client()
+ response = client.chat.completions.create(
+ model='gpt-3.5-turbo',
+ messages=[{
+ 'role': 'user',
+ 'content': state["prompt"]
+ }],
+ temperature=0,
+ stream=True,
+ )
+ buffer = []
+ for chunk in response:
+ delta = chunk.choices[0].delta.content
+ buffer.append(delta)
+ yield {'response': delta}
+ full_response = ''.join(buffer)
+ yield {'response': full_response}
+
+ @property
+ def reads(self) -> list[str]:
+ return ["prompt"]
+
+ @property
+ def writes(self) -> list[str]:
+ return ["response"]
+
+ def update(self, result: dict, state: State) -> State:
+ return state.append(response=result["response"])
+
+The logic is split between ``stream_run``, which is responsible for generating the intermediate results and
+joining them into the final result, and update, which is responsible for collecting the final result and
+updating the state. The final ``yield`` statement in ``stream_run`` is used to return the final result to the framework,
+which is passed to ``update``. Note that the class-based variant separates out run/update into two methods, meaning
+that it only yields the ``result`` and not the state update. The function-based variant, above, combined the two.
+
+``async`` streaming actions are also supported. The corresponding function-based async looks like this:
+
+.. collapse:: <code>fn_based_async</code>
+
+ .. code-block:: python
+
+ from burr.core.action import streaming_action
+
+ @streaming_action(reads=["prompt"], writes=["prompt"])
+ async def streaming_chat_call(state: State, **run_kwargs) -> AsyncGenerator[Tuple[dict, Optional[State]], None]:
+ client = openai.AsyncClient()
+ response = await client.chat.completions.create(
+ model='gpt-3.5-turbo',
+ messages=[{
+ 'role': 'user',
+ 'content': state["prompt"]
+ }],
+ temperature=0,
+ stream=True,
+ )
+ buffer = []
+ async for chunk in response: # loop over in async
+ delta = chunk.choices[0].delta.content
+ buffer.append(delta)
+ yield {'response': delta}, None # No state update on intermediate results
+ full_response = ''.join(buffer)
+ yield {'response': full_response}, state.append(response=full_response) # Update state on final results
+
+The class-based async streaming action will look like this:
+
+.. collapse:: <code>class_based_async</code>
+
+ .. code-block:: python
+
+ from burr.core.action import StreamingAction
+
+ class StreamingChatCallAsync(StreamingAction):
+ async def stream_run(self, state: State, **run_kwargs) -> AsyncGenerator[dict, None]:
+ client = openai.Client()
+ response = await client.chat.completions.create(
+ model='gpt-3.5-turbo',
+ messages=[{
+ 'role': 'user',
+ 'content': state["prompt"]
+ }],
+ temperature=0,
+ stream=True,
+ )
+ buffer = []
+ async for chunk in response:
+ delta = chunk.choices[0].delta.content
+ buffer.append(delta)
+ yield {'response': delta}
+ full_response = ''.join(buffer)
+ yield {'response': full_response}
+
+ @property
+ def reads(self) -> list[str]:
+ return ["prompt"]
+
+ @property
+ def writes(self) -> list[str]:
+ return ["response"]
+
+ def update(self, result: dict, state: State) -> State:
+ return state.append(response=result["response"])
+
+-----
+Usage
+-----
+
+When you call out to :py:meth:`stream_result <burr.core.application.Application.stream_result>` (as well as its corresponding async implementation
+:py:meth:`astream_result <burr.core.application.Application.astream_result>` on a streaming action, you will get
+a :py:class:`StreamingResultContainer <burr.core.action.StreamingResultContainer>`, or a :py:class:`AsyncStreamingResultContainerAsync <burr.core.action.AsyncStreamingResultContainer>`
+object.
+
+This object is effectively a cached iterator. You can use it as follows:
+
+.. code-block:: python
+
+ action, streaming_result = application.stream_result(
+ halt_after='streaming_response', inputs={"prompt": prompt}
+ )
+ for result in streaming_result:
+ print(result) # one by one
+
+ result, state = streaming_result.get()
+ print(result) # get the result
+
+.. code-block:: python
+
+ action, async_streaming_result = await application.astream_result(
+ halt_after='streaming_response', inputs={"prompt": prompt}
+ )
+ async for result in async_streaming_result:
+ print(result) # one by one
+
+ result, state = await async_streaming_result.get()
+ print(result) # all at once
+
+
+Thus you can run this in a web-service, a streamlit app, etc...
+
+--------------
+Considerations
+--------------
+
+All hooks/state update will be called once the iterator completes, or an exception interrupts the iterator
+and it has to be cleaned up. You can call ``.stream_result()`` or ``.astream_result()`` on non-streaming
+results, and it will return a ``StreamingResultContainer`` with an empty iterator that returns the result.
+If streaming items are run as intermediate nodes in the graph, they will be run as normal actions
+(effectively fully exhausted), and the result will be returned as a single item. Currently
+you cannot use synchronous streaming actions as asynchronous streaming actions, but we will likely be
+adding a bridge.
+
+In version ``0.18.0`` we changed the synchronous method of streaming to
+be consistent with the asynchronous method. If you're using the old version, there are a few changes you'll have to make (for the function-based API):
+
+1. The return type of the streaming action should be ``Generator[Tuple[dict, Optional[State], None, None]]`` instead of ``Generator[dict, None, Tuple[dict, State]]``.
+2. All intermediate results should be yielded as ``yield {'response': delta}, None`` instead of ``yield {'response': delta}``.
+3. The final result will be a ``yield`` instead of a ``return``
diff --git a/content/_sources/concepts/sync-vs-async.rst.txt b/content/_sources/concepts/sync-vs-async.rst.txt
new file mode 100644
index 00000000..c0e3ad24
--- /dev/null
+++ b/content/_sources/concepts/sync-vs-async.rst.txt
@@ -0,0 +1,117 @@
+===========================
+Sync vs Async Applications
+===========================
+
+TL;DR
+------
+
+Burr gives you the ability to write synchronous (standard python) and asynchronous (``async def``/``await``) Burr applications. You then run these applications in some sort of python context (e.g. script, web-service, aws lambda, etc). Whether you choose to write your Burr application using Burr's synchronous or asynchronous features depends on where you plan to run your Burr application. At a high level:
+
+1. Use the ``async`` interfaces when you have I/O-heavy applications that require horizontal scaling, and have available asynchronous APIs (E.G. async LLM APIs in a web-service like FastAPI)
+
+ * :py:meth:`.abuild() <.ApplicationBuilder.abuild()>`
+ * :py:meth:`.aiterate() <.Application.aiterate()>`
+ * :py:meth:`.arun() <.Application.arun()>`
+ * :py:meth:`.astream_result() <.Application.astream_result()>`
+
+2. Use the synchronous interfaces otherwise -- when you have high CPU-bound applications (running models locally), or do not have async APIs to use:
+
+ * :py:meth:`.build() <.ApplicationBuilder.build()>`
+ * :py:meth:`.iterate() <.Application.iterate()>`
+ * :py:meth:`.run() <.Application.run()>`
+ * :py:meth:`.stream_result() <.Application.stream_result()>`
+
+Checklist for Async Applications
+-----------------------------------
+
+When building asynchronous applications with Burr, ensure you:
+
+1. **Use async action implementations**:
+ * Implement ``async def run`` methods in your actions
+ * Override the ``is_async`` property to return ``True`` in all async class-based actions
+ * Use ``await`` for all I/O operations inside your actions
+
+2. **Use async builder and application methods**:
+ * Use ``.abuild()`` instead of ``.build()``
+ * Use ``.arun()``, ``.aiterate()``, and ``.astream_result()`` instead of their sync counterparts
+
+3. **Use async hooks and persisters**:
+ * Implement async hooks with ``async def`` methods
+ * Use async persisters (e.g., ``AsyncPGPersister`` instead of ``PGPersister``)
+ * Properly clean up async resources using context managers or explicit cleanup calls
+
+4. **For parallel actions**:
+ * Make ``actions``, ``states``, and ``reduce`` methods async
+ * Override ``is_async`` to return ``True``
+ * Use ``AsyncGenerator`` return types
+ * Use async persisters with connection pools
+
+Comparison
+----------
+
+A synchronous Python application processes tasks sequentially for every thread/process it executes, blocking entirely on the result
+of the prior call. When using Burr, this means that two (or more) separate Burr applications, if they are to run concurrently, have to be run in separate threads/processes which you manage / control.
+
+Specifically for Burr, this means that you have a 1:1 app -> thread/process mapping (unless you're using :ref:`parallelism <parallelism>` and explicitly multithreading sub-actions).
+
+An asynchronous application can parallelize multiple I/O bound tasks within the confines of a single thread. At the time that
+a task blocks on I/O it can give control back to the process, allow it to run other tasks simultaneously.
+
+In the case of Burr, Burr supports this model in running multiple Burr applications, in parallel, on the same as thread (i.e. the asyncio event loop).
+Note, however, that you have to ensure your Burr application is async all the way down -- E.G. that every blocking call
+is called using `await` -- if it blocks the event loop through a slow, synchronous call, it will block *all* current
+applications.
+
+In general, Burr gives you the constructs for synchronous and asynchronous execution. We usually do that by
+providing both methods (see specific references for more detail and reach out if you feel like we
+are missing a specific implementation). Furthermore, Burr suports the following APIs for both synchronous/asynchronous interfaces:
+
+- :ref:`hooks <hooksref>`
+- :ref:`persisters <persistersref>`
+
+Nuances of Sync + Async together
+--------------------------------
+
+We encourage to make a decision to either commit fully to sync or async. That said,
+there are cases where a hybrid situation may be desirable or unavoidable (testing, prototyping,
+legacy code, ...) and we give some options to handle that. The table bellow shows the
+possibilities Burr now supports -- combining the set of synchronous/asynchronous.
+
+
+.. table:: Cases Burr supports
+ :widths: auto
+
+ +------------------------------------------------+----------+----------------------------------+
+ | Cases | Works? | Comment |
+ +================================================+==========+==================================+
+ | Sync Hooks <> Sync Builder <> Sync App Run | ✅ | This is a standard use |
+ | | | case highlighted |
+ | | | in sync applications |
+ +------------------------------------------------+----------+----------------------------------+
+ | Sync Hooks <> Sync Builder <> Async App Run | ⚠️ | This will work for now, but it is|
+ | | | not recommended because there |
+ | | | will be blocking functions |
+ +------------------------------------------------+----------+----------------------------------+
+ | Async Hooks <> Sync Builder <> Sync App Run | ⚠️ | This will run (if the async hook |
+ | | | is not a persister), but the |
+ | | | async hooks are ignored -- will |
+ | | | be deprecated |
+ +------------------------------------------------+----------+----------------------------------+
+ | Async Hooks <> Sync Builder <> Async App Run | ⚠️ | This will run (if the async hook |
+ | | | is not a persister), but you |
+ | | | should really use the async |
+ | | | builder |
+ +------------------------------------------------+----------+----------------------------------+
+ | Async Hooks <> Async Builder <> Async App Run | ✅ | This is a standard use case |
+ | | | highlighted in async |
+ | | | applications |
+ +------------------------------------------------+----------+----------------------------------+
+ | Async Hooks <> Async Builder <> Sync App Run | ❌ | Use async run methods |
+ +------------------------------------------------+----------+----------------------------------+
+ | Sync Hooks <> Async Builder <> Sync App Run | ❌ | Use sync builder |
+ +------------------------------------------------+----------+----------------------------------+
+ | Sync Hooks <> Async Builder <> Async App Run | ⚠️ | This will run (if the sync hook |
+ | | | is not a persister), but you |
+ | | | should really use the sync |
+ | | | builder |
+ +------------------------------------------------+----------+----------------------------------+
diff --git a/content/_sources/concepts/tracking.rst.txt b/content/_sources/concepts/tracking.rst.txt
new file mode 100644
index 00000000..01047d32
--- /dev/null
+++ b/content/_sources/concepts/tracking.rst.txt
@@ -0,0 +1,114 @@
+.. _tracking:
+
+=============
+Tracking Burr
+=============
+
+.. note::
+
+ Burr's telemetry system is built in and easy to integrate. It allows you to understand
+ the flow of your application, and watch it make decisions in real time. You can run it
+ with sample projects by running ``burr`` in the terminal after ``pip install "burr[start]"``.
+
+Burr comes with a telemetry system that allows tracking a variety of information for debugging,
+both in development and production.
+
+The data model for tracking is simple:
+
+1. **Projects** are the top-level grouping of data (the first page). This is specified in the constructor for the
+ :py:meth:`with_tracker <burr.core.application.ApplicationBuilder.with_tracker>`, as the only required argument.
+2. **Applications** get logged to projects. An application would be considered similar to a "trace" in distributed
+ tracing systems. This is (optionally) specified as the ``app_id`` argument for the :py:meth:`with_tracker <burr.core.application.ApplicationBuilder.with_tracker>`
+ method. A single ``application`` has shared state path across all its steps.
+3. **Steps** are the individual steps that are executed in the state machine. The Burr UI will show the state of the
+ state machine at the time of the step execution, as well as the input to and results of the step.
+
+.. _trackingclientref:
+
+---------------
+Tracking Client
+---------------
+
+When you use :py:meth:`with_tracker <burr.core.application.ApplicationBuilder.with_tracker>`, you add a tracker to Burr.
+This is a lifecycle hook that does the following:
+
+#. Logs the static representation of the state machine
+#. Logs any information before/after step execution, including
+ - The step name
+ - The step input
+ - The state at time of execution
+ - The timestamps
+
+This currently defaults to (and only supports) the :py:class:`LocalTrackingClient <burr.tracking.LocalTrackingClient>` class, which
+writes to a local file system, although we will be making it pluggable in the future. It will, by default, write to the directory
+``~/.burr``.
+
+Debugging via Reloading Prior State
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Because the tracking client writes to the file system, you can reload the state of the state machine at any time. This is
+useful for debugging, because you can quickly recreate the issue by running the state machine with the same point in time.
+
+To do so, you'd use the classmethod _load()_ on the :py:class:`LocalTrackingClient <burr.tracking.LocalTrackingClient>`.
+
+For example, to debug your Burr Application, you'd have some control flow like this using the `.initialize_from(...)` and `fork_from_*` functionality:
+
+.. code-block:: python
+
+ from burr.tracking import LocalTrackingClient
+
+ prior_app_id = ... # some value
+ sequence_id = ... # None or some prior step
+ partition_key = "SOME_VALUE" # use None if not using a partition key
+
+ project_name = "PROJECT_NAME"
+ tracker = LocalTrackingClient(project=project_name)
+ app = (
+ ApplicationBuilder()
+ .with_graph(base_graph) # your graph
+ .initialize_from(
+ tracker, # local tracker above
+ resume_at_next_action=True,
+ default_state={}, # your default state
+ default_entrypoint="SOME_DEFAULT", # your default entry point
+ fork_from_app_id=prior_app_id,
+ fork_from_sequence_id=sequence_id,
+ fork_from_partition_key=partition_key
+ )
+ .with_tracker(tracker) # tracking + checkpointing; one line 🪄.
+ .build()
+ )
+
+---------------
+Tracking Server
+---------------
+
+The tracking server (now) is meant for visualizing the state machine and the steps that have been executed. You can
+run it with the following command:
+
+.. code-block:: bash
+
+ burr
+
+This will start a server on port 7241, and open up a browser window with the UI for you to explore.
+
+
+Launch Burr UI from a notebook
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+You can launch the Burr UI from a notebook or Google Colab using the ``%burr_ui`` "IPython magic".
+This will print the URL to access the Burr UI web app.
+
+.. code-block:: python
+
+ # in one cell - expose the port:
+ %load_ext burr.integrations.notebook
+ %burr_ui
+
+ "Burr UI: http://127.0.0.1:7241"
+
+.. code-block:: python
+
+ # But you need to expose the port to the browser:
+ from google.colab import output
+ output.serve_kernel_port_as_window(7241) # this will open a new window
+ output.serve_kernel_port_as_iframe(7241) # this will inline in an iframe
diff --git a/content/_sources/concepts/transitions.rst.txt b/content/_sources/concepts/transitions.rst.txt
new file mode 100644
index 00000000..efd19175
--- /dev/null
+++ b/content/_sources/concepts/transitions.rst.txt
@@ -0,0 +1,59 @@
+====================
+Transitions
+====================
+
+.. _transitions:
+
+.. note::
+
+ While actions form the steps taken in an `application`, transitions decide which one to do next.
+ They make decisions based on state. You can use them to specify which model to call, whether a conversation is
+ over, or any other decision that needs to be made based on the current state.
+
+Transitions define explicitly how actions are connected and which action is available next for any given state.
+You can think of them as edges in a graph.
+
+They have three main components:
+- The ``from`` state
+- The ``to`` state
+- The ``condition`` that must be met to move from the ``from`` state to the ``to``
+
+----------
+Conditions
+----------
+
+Conditions have a few APIs, but the most common are the three convenience functions:
+
+.. code-block:: python
+
+ from burr.core import when, expr, default
+ with_transitions(
+ ("from", "to", when(foo="bar"), # will evaluate when the state has the variable "foo" set to the value "bar"
+ ("from", "to", expr('epochs>100')) # will evaluate to True when epochs is greater than 100
+ ("from", "to", default) # will always evaluate to True
+ ("from", "to") # leaving out a third conditions we allow defaults
+ )
+
+
+Conditions are evaluated in the order they are specified, and the first one that evaluates to True will be the transition that is selected
+when determining which action to run next. If no condition evaluates to ``True``, the application execution will stop early.
+
+The ``~`` operator will invert a condition. For instance:
+
+.. code-block:: python
+
+ from burr.core import when, expr
+ with_transitions(
+ ("from", "to", ~when(foo="baz"), # will evaluate to True when foo != baz
+ ("from", "to", ~expr('epochs<=100')) # will evaluate to True when epochs hits 101
+ )
+
+All keys present in the condition (E.G. ``foo`` and ``epochs`` above) must be present in the state for the condition to work. It will error otherwise.
+
+.. note::
+
+ The ``default`` condition is a special case, and will always evaluate to ``True``. It is useful for defining a "catch-all" transition
+ that will be selected if no other condition is met. If you pass a tuple of length 2 to :py:meth:`with_transitions <burr.core.application.ApplicationBuilder.with_transitions>`, the
+ default condition will be used.
+
+See the :ref:`transition docs <transitionref>` for more information on the transition API.
diff --git a/content/_sources/contributing/architecture.rst.txt b/content/_sources/contributing/architecture.rst.txt
new file mode 100644
index 00000000..4af63fec
--- /dev/null
+++ b/content/_sources/contributing/architecture.rst.txt
@@ -0,0 +1,38 @@
+============
+Architecture
+============
+
+Some notes on the design/implementation of Burr:
+
+--------------
+Python Package
+--------------
+
+Dependencies
+------------
+A note on dependencies:
+
+- The core Burr library will have zero dependencies. Currently its only dependency in hamilton, but that will be removed in the future.
+- Any other extensions (the server, the CLI, etc...) are allowed dependencies -- specify these as install targets in ``pyproject.toml``
+- The dependencies/plugins will live alongside the core library, and contain guards to ensure that the right libraries are installed. You can do this with ``burr.integrations.base.require_plugins``
+
+Coding style
+------------
+We use type hints for function parameters (and in rare cases inline) to aid development but it is not enforced -- use your best judgement
+
+Versioning
+----------
+
+We adhere to `sem-var <https://semver.org/>`_ for versioning. We ensure that:
+
+- Every public facing function/class/variable should:
+ - have a docstring
+ - have type-hints
+ - be exposed through the documentation
+
+If it is not exposed through the documentation it is assumed to be private, and thus will
+not be subject to sem-var rules.
+
+We currently are not versioning the server or CLI, but are versioning the core library. Note
+that it starts at ``0.x``, which means that we are allowed to make a backwards-incompatible change.
+We will make every effort not to do so -- and will provide a migration guide/script if we do.
diff --git a/content/_sources/contributing/contributing.rst.txt b/content/_sources/contributing/contributing.rst.txt
new file mode 100644
index 00000000..f8002f28
--- /dev/null
+++ b/content/_sources/contributing/contributing.rst.txt
@@ -0,0 +1,85 @@
+.. _contributing:
+
+============
+Contributing
+============
+
+Please read the `code of conduct <https://github.com/apache/burr?tab=coc-ov-file#readme>`_
+prior to contributing. Then follow these guidelines:
+
+#. Create a fork of the repository.
+#. Ensure all tests pass.
+#. Make a PR to the main repository.
+#. Ping one of the maintainers to review your PR.
+
+-----------------------
+Contribution guidelines
+-----------------------
+
+Please:
+
+#. Keep your commits modular
+#. Add descriptive commit messages
+#. Attach a PR to an issue if applicable
+#. Ensure all new features have tests
+#. Add documentation for new features
+
+Examples
+--------
+
+All examples must have the following files:
+1. A ``README.md`` file that describes the example -- this should follow the pattern of the others
+2. A ``requirements.txt`` file that lists the dependencies for the example
+3. An ``application.py`` file whose mainline runs a simple version of the example + generates the DAG to
+4. A ``statemachine.png`` file that shows the DAG for the example
+3. A `notebook.ipynb` file that uses the example in a
+
+The ``notebook.ipynb`` can import stuff from the ``application.py``, or redefine it as an example. If you have something
+that does not fit this, or is a prototype but you want to push it in, you can fit in under the ``half-baked`` directory.
+
+---------------
+Developer notes
+---------------
+
+CLI
+---
+
+Burr comes with a `cli` that is both user/developer facing.
+
+**this is required in order to publish, do not do so otherwise**
+
+This will be turned into a `Makefile`, but for now we have a set of commands in `pyproject.toml` that are used to
+publish, etc...
+
+To run the just the server for development:
+
+.. code-block:: bash
+
+ $ burr --dev-mode --no-open # will run the server on port 7241
+
+To publish -- this will build the FE + publish the BE to the ``pypi`` prod instance. Note you have to have ``pypi`` credentials to do this:
+
+.. code-block:: bash
+
+ $ burr-admin-publish --prod
+
+To generate the demo data (if you make a change to the schema, ideally forward-compatible):
+
+.. code-block:: bash
+
+ $ burr-admin-generate-demo-data
+
+Not part of the CLI (yet), but running just the UI is simple:
+
+.. code-block:: bash
+
+ $ cd burr/ui
+ $ npm run start
+
+Package data
+------------
+
+Several static assets are included in the python package so we can run the UI. Namely:
+
+1. The `examples` directory is symlinked from ``burr/examples`` to allow for package-style imports
+2. The `build/` directory is symlinked from ``burr/tracking/server`` to allow for static assets referred to by the server to be included in the package. Note that this does not get committed -- this requires use of the CLI above.
diff --git a/content/_sources/contributing/index.rst.txt b/content/_sources/contributing/index.rst.txt
new file mode 100644
index 00000000..5bd4064a
--- /dev/null
+++ b/content/_sources/contributing/index.rst.txt
@@ -0,0 +1,18 @@
+=========================
+Contributing
+=========================
+
+.. _examples:
+
+Instructions to develop/get started with `Burr`! If you don't know where
+to start, you can always reach out to us:
+- Start an issue or discussion on our `GitHub <https://github.com/dagworks-inc/burr>`_.
+- Reach out to us by `email <ma...@dagworks.io>`_
+
+.. toctree::
+ :maxdepth: 2
+
+ setup
+ iterating
+ architecture
+ contributing
diff --git a/content/_sources/contributing/iterating.rst.txt b/content/_sources/contributing/iterating.rst.txt
new file mode 100644
index 00000000..20f47425
--- /dev/null
+++ b/content/_sources/contributing/iterating.rst.txt
@@ -0,0 +1,77 @@
+==========
+Developing
+==========
+
+-------------
+Python Module
+-------------
+
+The python module is located in the ``burr/`` directory. Tests are located in the
+``tests/`` directory. To run the tests, use the following command:
+
+.. code-block:: bash
+
+ python -m pytest tests/
+
+
+---------------
+Tracking server
+---------------
+
+The tracking server is located inside the python package. To run the server, you can use the utility cli:
+
+.. code-block:: bash
+
+ BURR_SERVE_STATIC=false burr-admin-server --no-open --dev-mode
+
+This will start the server on port 7241. Note that this is just a python wrapper -- this also captures stdout.
+We will be fixing this, but if you want to run it directly (which is best for debugging), you'll want to
+run it with uvicorn:
+
+.. code-block:: bash
+
+ BURR_SERVE_STATIC=false uvicorn burr.tracking.server.run:app --port 7241 --reload
+
+We disable serving static files as we want to be able to develop simultaneously with the UI and
+npm's reloading capabilities do not work with the static python server.
+
+-----------
+Tracking UI
+-----------
+
+The tracking UI lives within ``telemetry/ui`` To run the tracking UI, you will need to have npm installed.
+Then, you can run the following commands:
+
+.. code-block:: bash
+
+ $ cd telemetry/ui
+ $ npm install
+ $ npm run start
+
+This will start the UI on port 3000. You can then navigate to ``http://localhost:3000`` to see the UI.
+It currently assumes that the tracking server is running on port 7241. The proxy is set up in the
+``package.json`` file.
+
+Note that if you want to just develop off the server you'll first have to build the UI and symlink the
+``tracking/server/build`` directory to the ``telemetry/ui/build`` directory. We have this structure to
+enable easy inclusion of package data in ``pyproject.toml``.
+
+You can do this with the following command:
+
+.. code-block:: bash
+
+ $ burr-admin-build-ui
+
+-------------
+Documentation
+-------------
+
+The documentation is located in the ``docs/`` directory. To build and serve the documentation,
+you can use the following sphinx command:
+
+.. code-block:: bash
+
+ $ sphinx-autobuild -b dirhtml -W -E -T --watch docs/ -a docs /tmp/mydocs
+
+This will start the server on port 8000. You can then navigate to ``http://localhost:8000`` to see the documentation.
+Heed any warnings and fix them!
diff --git a/content/_sources/contributing/setup.rst.txt b/content/_sources/contributing/setup.rst.txt
new file mode 100644
index 00000000..81d4b48f
--- /dev/null
+++ b/content/_sources/contributing/setup.rst.txt
@@ -0,0 +1,55 @@
+=====
+Setup
+=====
+
+These instructions will be assuming use of `pip <https://pip.pypa.io/en/stable/>`_ and `virtualenv <https://virtualenv.pypa.io/>`_.
+Replace with your package manager of choice if you prefer.
+
+----------
+Clone/fork
+----------
+
+To get started, create a fork of Burr on the github UI, and clone it to your local machine.
+
+.. code-block:: bash
+
+ git clone https://github.com/<your_space>/burr.git
+
+
+----------
+Installing
+----------
+
+Next you'll want to ``cd`` into the directory and install
+``burr`` in developer mode:
+
+.. code-block:: bash
+
+ cd burr
+ pip install -e ".[developer]"
+
+This will install all potential dependencies. Burr will work with ``python >=3.9``.
+
+------------------
+Linting/Pre-Commit
+------------------
+
+Burr has pre-commit hooks enabled. This comes with the ``developer`` extras.
+You can always run the pre-commit hooks manually (on all files). Do this
+if it somehow wasn't configured and its in a bad state.
+
+.. code-block:: bash
+
+ pre-commit run --all
+
+For the UI, we leverage husky and lint-staged to run the pre-commit hooks on the client side.
+This actually runs pre-commits for the whole repository, so you can run through husky if you want.
+
+You can also run the pre-commit hooks for the UI manually:
+
+.. code-block:: bash
+
+ npm run lint:fix
+ npm run format:fix
+
+from within the ``telemetry/ui`` directory.
diff --git a/content/_sources/examples/agents/agent-patterns.md.txt b/content/_sources/examples/agents/agent-patterns.md.txt
new file mode 100644
index 00000000..8f1df814
--- /dev/null
+++ b/content/_sources/examples/agents/agent-patterns.md.txt
@@ -0,0 +1,31 @@
+# Agent patterns
+
+We have the bones of a few applications that can be used to create agents. These are not fully fleshed out, but they
+can be used as a starting point for creating your own agents.
+
+We have the following templates:
+
+## Multimodal agent
+
+[Code template](https://github.com/DAGWorks-Inc/burr/tree/main/examples/templates/multi_modal_agent.py)
+
+
+
+## Multi-agent collaboration
+
+[Code template](https://github.com/DAGWorks-Inc/burr/tree/main/examples/templates/multi_agent_collaboration.py)
+
+
+
+## Supervisor agent
+
+[Code template](https://github.com/DAGWorks-Inc/burr/tree/main/examples/templates/agent_supervisor.py)
+
+
+
+
+## Hierarchical teams
+
+[Code template](https://github.com/DAGWorks-Inc/burr/tree/main/examples/templates/hierarchical_agent_teams.py)
+
+
diff --git a/content/_sources/examples/agents/divide-and-conquer.md.txt b/content/_sources/examples/agents/divide-and-conquer.md.txt
new file mode 100644
index 00000000..7d59f183
--- /dev/null
+++ b/content/_sources/examples/agents/divide-and-conquer.md.txt
@@ -0,0 +1,26 @@
+# Divide and Conquer
+
+A single agent can usually operate effectively using a handful of tools within a single domain, but even using powerful models like `gpt-4`, it can be less effective at using many tools.
+
+One way to approach complicated tasks is through a "divide-and-conquer" approach: create a "specialized agent" for each task or domain and route tasks to the correct "expert". This means that each agent can become a sequence of LLM calls that chooses how to use a specific "tool".
+
+The examples we link to below are inspired by the paper [AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation by Wu, et. al.](https://arxiv.org/abs/2308.08155).
+They can be found in [this part of our repository](https://github.com/DAGWorks-Inc/burr/tree/main/examples/multi-agent-collaboration).
+
+
+
+We provide two implementations of this idea: with [Hamilton](https://github.com/DAGWorks-Inc/burr/tree/main/examples/multi-agent-collaboration/hamilton) and with [LangChain LCEL](https://github.com/DAGWorks-Inc/burr/tree/main/examples/multi-agent-collaboration/lcel). From Burr's standpoint, both look similar and you're free to use your preferred framework within an `action`.
+
+## With Hamilton
+
+<a target="_blank" href="https://colab.research.google.com/github/dagworks-inc/burr/blob/main/examples/multi-agent-collaboration/hamilton/notebook.ipynb">
+ <img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab"/>
+</a>
+
+
+
+## With LangChain Expression Language (LCEL)
+
+<a target="_blank" href="https://colab.research.google.com/github/dagworks-inc/burr/blob/main/examples/multi-agent-collaboration/lcel/notebook.ipynb">
+ <img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab"/>
+</a>
diff --git a/content/_sources/examples/agents/index.rst.txt b/content/_sources/examples/agents/index.rst.txt
new file mode 100644
index 00000000..01f83174
--- /dev/null
+++ b/content/_sources/examples/agents/index.rst.txt
@@ -0,0 +1,11 @@
+==========
+🤖 Agents
+==========
+
+Burr allows you to create agents that can interact with each other via State.
+
+.. toctree::
+ :maxdepth: 2
+
+ divide-and-conquer
+ agent-patterns
diff --git a/content/_sources/examples/chatbots/basic-chatbot.ipynb.txt b/content/_sources/examples/chatbots/basic-chatbot.ipynb.txt
new file mode 100644
index 00000000..38e7e95f
--- /dev/null
+++ b/content/_sources/examples/chatbots/basic-chatbot.ipynb.txt
@@ -0,0 +1,453 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "61278d3e-958b-49ee-94b8-2ee1f5e58773",
+ "metadata": {},
+ "source": [
+ "# Basic\n",
+ "## Setup"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "46cb1e2e-5527-4acf-a1d0-5085eb621f4e",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import uuid\n",
+ "from typing import Tuple\n",
+ "\n",
+ "import openai # replace with your favorite LLM client library\n",
+ "\n",
+ "from burr.core import action, State, ApplicationBuilder, when, persistence"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "406b410d-0091-4368-ad08-b24220f728dd",
+ "metadata": {},
+ "source": [
+ "## Define Actions\n",
+ "\n",
+ "We define two actions:\n",
+ "1. `human_input` -- this is the first one, it accepts a prompt from the outside and adds it to the state\n",
+ "2. `ai_response` -- this is the second one, it takes the prompt + chat history and queries OpenAI.\n",
+ "\n",
+ "While this is just a pass-through to OpenAI (and is thus a little pointless) -- its a start. We'll be adding more later."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "206222f0-245b-4941-8976-11f9dcc7fd37",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "@action(reads=[], writes=[\"prompt\", \"chat_history\"])\n",
+ "def human_input(state: State, prompt: str) -> Tuple[dict, State]:\n",
+ " \"\"\"Pulls human input from the outside world and massages it into a standard chat format.\n",
+ " Note we're adding it into the chat history (with an `append` operation). This \n",
+ " is just for convenience of reference -- we could easily just store the chat history\n",
+ " and access it.\n",
+ " \"\"\"\n",
+ " \n",
+ " chat_item = {\n",
+ " \"content\": prompt,\n",
+ " \"role\": \"user\"\n",
+ " }\n",
+ " # return the prompt as the result\n",
+ " # put the prompt in state and update the chat_history\n",
+ " return (\n",
+ " {\"prompt\": prompt}, \n",
+ " state.update(prompt=prompt).append(chat_history=chat_item)\n",
+ " )\n",
+ "\n",
+ "@action(reads=[\"chat_history\"], writes=[\"response\", \"chat_history\"])\n",
+ "def ai_response(state: State) -> Tuple[dict, State]:\n",
+ " \"\"\"Queries OpenAI with the chat. You could easily use langchain, etc... to handle this,\n",
+ " but we wanted to keep it simple to demonstrate\"\"\"\n",
+ " client = openai.Client() # replace with your favorite LLM client library\n",
+ " content = client.chat.completions.create(\n",
+ " model=\"gpt-3.5-turbo\",\n",
+ " messages=state[\"chat_history\"],\n",
+ " ).choices[0].message.content\n",
+ " chat_item = {\n",
+ " \"content\": content,\n",
+ " \"role\": \"assistant\"\n",
+ " }\n",
+ " # return the response as the result\n",
+ " # put the response in state and update the chat history\n",
+ " return (\n",
+ " {\"response\": content}, \n",
+ " state.update(response=content).append(chat_history=chat_item)\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d3798307-6137-40ea-9818-471acafe3e1a",
+ "metadata": {},
+ "source": [
+ "## Create the app\n",
+ "\n",
+ "We create our app by adding our actions (they're `**kwargs`, the name of the action is the key), then adding transitions. \n",
+ "In this case, the transitions are simple -- we just go in a loop. Why a loop? We want to be able to continue the chat. Although the control flow\n",
+ "will pause (move from the application to the caller) after every `ai_response` call, we want to keep state, etc..."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6e75d7ae-c9b0-4e58-812d-3da3e9d5226d",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "app = (\n",
+ " ApplicationBuilder().with_actions(\n",
+ " human_input=human_input,\n",
+ " ai_response=ai_response\n",
+ " ).with_transitions(\n",
+ " (\"human_input\", \"ai_response\"),\n",
+ " (\"ai_response\", \"human_input\")\n",
+ " ).with_state(chat_history=[])\n",
+ " .with_entrypoint(\"human_input\")\n",
+ " .build()\n",
+ ")\n",
+ "app.visualize(output_file_path=\"digraph_initial\", format=\"png\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7cae8a7c-b696-44c6-bf32-babca8fdbe4f",
+ "metadata": {},
+ "source": [
+ "## Run the app\n",
+ "\n",
+ "To run the app, we call the `.run` function, passing in a stopping condition. In this case, we want it to halt after `ai_response`. \n",
+ "It returns the action it ran, the result it got, and the state. We use te state variable of response to print out the output, although in a react-like frontend system we may elect to return the entire chat history and render it all for the user."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "3bac2b00-bcb0-41e1-8e34-722e303a2cff",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "final_action, result, state = app.run(\n",
+ " halt_after=[\"ai_response\"], \n",
+ " inputs={\"prompt\" : \"Who was Aaron Burr?\"}\n",
+ ")\n",
+ "print(state['response'])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "119f2ce1-a4e0-429d-a617-754c92f343cc",
+ "metadata": {},
+ "source": [
+ "## Add a decision making step\n",
+ "\n",
+ "Let's add a step to check if the prompt is \"safe\". In this case OpenAI does this automatically, so we're going to simulate it by marking it as unsafe it the word \"unsafe\" is in the response.\n",
+ "We're going to add one step that checks for safety, and another that drafts a response in the case that it is unsafe.\n",
+ "\n",
+ "Then, we're going to add a \"conditional\" transition, allowing us to respond differently depending on the value of `safe`. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "69a2ef61-75c3-4a23-9c0e-87dcf1bfbf6b",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "@action(reads=[\"prompt\"], writes=[\"safe\"])\n",
+ "def safety_check(state: State) -> Tuple[dict, State]:\n",
+ " safe = \"unsafe\" not in state[\"prompt\"]\n",
+ " return {\"safe\": safe}, state.update(safe=safe)\n",
+ "\n",
+ "\n",
+ "@action(reads=[], writes=[\"response\", \"chat_history\"])\n",
+ "def unsafe_response(state: State) -> Tuple[dict, State]:\n",
+ " content = \"I'm sorry, my overlords have forbidden me to respond.\"\n",
+ " new_state = (\n",
+ " state\n",
+ " .update(response=content)\n",
+ " .append(\n",
+ " chat_history={\"content\": content, \"role\": \"assistant\"})\n",
+ " )\n",
+ " return {\"response\": content}, new_state"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "e5480950-916d-45b7-ab70-fdbb8865de8d",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "safe_app = (\n",
+ " ApplicationBuilder().with_actions(\n",
+ " human_input=human_input,\n",
+ " ai_response=ai_response,\n",
+ " safety_check=safety_check,\n",
+ " unsafe_response=unsafe_response\n",
+ " ).with_transitions(\n",
+ " (\"human_input\", \"safety_check\"),\n",
+ " (\"safety_check\", \"unsafe_response\", when(safe=False)),\n",
+ " (\"safety_check\", \"ai_response\", when(safe=True)),\n",
+ " ([\"unsafe_response\", \"ai_response\"], \"human_input\"),\n",
+ " ).with_state(chat_history=[])\n",
+ " .with_entrypoint(\"human_input\")\n",
+ " .build()\n",
+ ")\n",
+ "safe_app.visualize(output_file_path=\"digraph_safe\", include_conditions=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "3c102eaf-4e4b-4bf3-ad60-7078bfe499d8",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "action, result, state = safe_app.run(\n",
+ " halt_after=[\"ai_response\", \"unsafe_response\"], \n",
+ " inputs={\"prompt\": \"Who was Aaron Burr, sir (unsafe)?\"}\n",
+ ")\n",
+ "print(state[\"response\"])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "002fe5f3-1076-40a8-92b4-7345aa96a2b7",
+ "metadata": {},
+ "source": [
+ "## Tracking\n",
+ "\n",
+ "OK, now let's interact with telemetry! All we have to do is add `with_tracker` call. We can also just grab the `builder` from the prior app and start where we left off. We'll run on quite a few prompts to test this out:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "acb2d83b-de78-4b5a-9514-4e1e2633bcd5",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "app_with_tracker = (\n",
+ " ApplicationBuilder().with_actions(\n",
+ " human_input=human_input,\n",
+ " ai_response=ai_response,\n",
+ " safety_check=safety_check,\n",
+ " unsafe_response=unsafe_response\n",
+ " ).with_transitions(\n",
+ " (\"human_input\", \"safety_check\"),\n",
+ " (\"safety_check\", \"unsafe_response\", when(safe=False)),\n",
+ " (\"safety_check\", \"ai_response\", when(safe=True)),\n",
+ " ([\"unsafe_response\", \"ai_response\"], \"human_input\"),\n",
+ " ).with_state(chat_history=[])\n",
+ " .with_entrypoint(\"human_input\")\n",
+ " .with_tracker(\n",
+ " \"local\", project=\"demo_getting_started\"\n",
+ " ).build()\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "e7a6d792-e872-4bf1-a9f7-c0d545481859",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "app_with_tracker.uid"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "4497d659-ca22-45ca-a085-0510903b6906",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "for prompt in [\n",
+ " \"Who was Aaron Burr, sir?\",\n",
+ " \"Who was Aaron Burr, sir (unsafe)?\",\n",
+ " \"If you had ml/ai libraries called 'Hamilton' and 'Burr', what would they do?\",\n",
+ " \"Who was Aaron Burr, sir?\",\n",
+ " \"Who was Aaron Burr, sir (unsafe)?\",\n",
+ " \"If you had ml/ai libraries called 'Hamilton' and 'Burr', what would they do?\",\n",
+ "]:\n",
+ " action_we_ran, result, state = app_with_tracker.run(\n",
+ " halt_after=[\"ai_response\", \"unsafe_response\"], \n",
+ " inputs={\"prompt\": prompt}\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "48c70315-32cd-4845-92a9-a40738322310",
+ "metadata": {},
+ "source": [
+ "## Tracking Server\n",
+ "\n",
+ "You can run the tracking server by running `burr` in the terminal. If you want to see it live, you can run the subsequence cell (which does some magic to run it for you). If the embedding in the notebook gets annoying, navigate to the link to the UI, outputted by the next cell. \n",
+ "\n",
+ "Press the refresh button (🔄) by \"Live\" to watch it live. Run the above cell to query."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "d96936ef-fb09-4443-be9c-56d5b7ecae69",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from IPython.display import Markdown\n",
+ "url = f\"[Link to UI](http://localhost:7241/project/demo_getting_started/{app_with_tracker.uid})\"\n",
+ "Markdown(url)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "fa4e1d34-4eb8-4d1c-9708-6269612b7209",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import os\n",
+ "import time\n",
+ "from IPython.display import HTML\n",
+ "get_ipython().system = os.system\n",
+ "!burr --no-open > /dev/null 2>&1 &\n",
+ "time.sleep(3) # quick trick to wait for the server to start\n",
+ "url = f\"http://localhost:7241/project/demo_getting_started/{app_with_tracker.uid}\"\n",
+ "iframe = f'<iframe src=\"{url}\" width=\"100%\" height=\"1000px\"></iframe>'\n",
+ "display(HTML(iframe))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d4a6591c-fb54-4211-8808-c4aab0e38040",
+ "metadata": {},
+ "source": [
+ "## Persistence\n",
+ "\n",
+ "While the tracking we showed above does do storage/persistence, Burr has a host of other capabilities to help with state persistence and reloading.\n",
+ "\n",
+ "The use case is this -- you have quite a few simultaneous conversations, each with their own state/assigned to their own users. You want to be able to store them, pause them when the user logs off, and reload them when the user logs back on. You can do this with persisters. There are two interfaces to them:\n",
+ "\n",
+ "1. A set of pre-build persisters (postgres, sqllite3, redis, etc...) that you can use\n",
+ "2. A custom persistor class that you write\n",
+ "\n",
+ "To add a persistor, you have to tell it to load from a state (`.initialize(...)`) on the builder, and tell it to save to a state (`.with_state_persister`).\n",
+ "\n",
+ "More about persistence [here](https://burr.dagworks.io/concepts/state-persistence/)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "53f25edd-d483-4c41-b716-95ac3b0cbcc4",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "app_id = f\"unique_app_id_{uuid.uuid4()}\" # unique app ID -- we create it here but this will be generated for you\n",
+ "partition_key = \"new_burr_user\" # this can be anything. In a chatbot it will "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "468d4d53-9343-48b3-ba1f-651bba3e1a3c",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# we're going to be creating this multiple times to demonstrate so let's stick it in a function\n",
+ "def create_persistable_app():\n",
+ " sqllite_persister = persistence.SQLLitePersister(db_path=\"./sqlite.db\", table_name=\"burr_state\")\n",
+ " sqllite_persister.initialize()\n",
+ " return (\n",
+ " ApplicationBuilder().with_actions(\n",
+ " human_input=human_input,\n",
+ " ai_response=ai_response,\n",
+ " safety_check=safety_check,\n",
+ " unsafe_response=unsafe_response\n",
+ " ).with_transitions(\n",
+ " (\"human_input\", \"safety_check\"),\n",
+ " (\"safety_check\", \"unsafe_response\", when(safe=False)),\n",
+ " (\"safety_check\", \"ai_response\", when(safe=True)),\n",
+ " ([\"unsafe_response\", \"ai_response\"], \"human_input\"),\n",
+ " ).initialize_from(\n",
+ " initializer=sqllite_persister,\n",
+ " resume_at_next_action=True,\n",
+ " default_state={\"chat_history\": []},\n",
+ " default_entrypoint=\"human_input\"\n",
+ " ).with_state_persister(sqllite_persister)\n",
+ " .with_identifiers(app_id=app_id, partition_key=partition_key)\n",
+ " .with_tracker(\n",
+ " \"local\", project=\"demo_getting_started\"\n",
+ " ).build()\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "ec967861-133a-4151-a3e0-ac5238bc67f5",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "app_initial = create_persistable_app()\n",
+ "action, result, state = app_initial.run(\n",
+ " halt_after=[\"ai_response\", \"unsafe_response\"], \n",
+ " inputs={\"prompt\": \"Who was Aaron Burr, sir?\"}\n",
+ ")\n",
+ "for item in state['chat_history']:\n",
+ " print(item['role'] + ':' + item['content'] + '\\n')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "dbd18904-2407-41e0-8151-e17bba3824ef",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "del app_initial\n",
+ "app_reloaded = create_persistable_app()\n",
+ "\n",
+ "action, result, state = app_reloaded.run(\n",
+ " halt_after=[\"ai_response\", \"unsafe_response\"], \n",
+ " inputs={\"prompt\": \"Who was Alexander Hamilton?\"}\n",
+ ")\n",
+ "for item in state['chat_history']:\n",
+ " print(item['role'] + ':' + item['content'] + '\\n')"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.10.4"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/content/_sources/examples/chatbots/gpt-like-chatbot.ipynb.txt b/content/_sources/examples/chatbots/gpt-like-chatbot.ipynb.txt
new file mode 100644
index 00000000..cde600f9
--- /dev/null
+++ b/content/_sources/examples/chatbots/gpt-like-chatbot.ipynb.txt
@@ -0,0 +1,1039 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "f63df3e4b8d4fdba",
+ "metadata": {},
+ "source": [
+ "# GPT-like multimodal chatbot\n",
+ "\n",
+ "\n",
+ "<img src=\"https://github.com/user-attachments/assets/2ab9b499-7ca2-4ae9-af72-ccc775f30b4e\" width=\"100\" align=\"left\" /> + \n",
+ "<img src=\"https://cdn.mos.cms.futurecdn.net/VgGxJABA8DcfAMpPPwdv6a.jpg\" width=\"200\" align=\"center\"/>\n",
+ "\n",
+ "[https://github.com/dagworks-inc/burr](https://github.com/dagworks-inc/burr) by DAGWorks Inc. (YCW23 & StartX).\n",
+ "\n",
+ "Take🏠:\n",
+ "\n",
+ " - high level what is Burr\n",
+ " - what you can do with Burr (observing state & being able to debug a particular point in time)\n",
+ " - watch a walkthrough of this [notebook here](https://youtu.be/hqutVJyd3TI)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d725b4dd413d01e8",
+ "metadata": {},
+ "source": [
+ "## Agentic Problems\n",
+ "1. Why did this LLM call fail?\n",
+ "2. Oh crap my code broke, why?\n",
+ "3. Things went off the rails, but where?\n",
+ "4. etc\n",
+ "\n",
+ "## Monitoring for the win, right?\n",
+ "Well but ... monitoring doesn't help you debug & complete your dev loop\n",
+ "\n",
+ "1. How do I debug that quickly?\n",
+ "2. How do I fix the inputs/code, and restart my agent?\n",
+ "3. What if my agent was 20+ steps in … do I have to restart from step 0? or can I go to a specific point in time?\n",
+ "\n",
+ "\n",
+ "## Solution: Burr\n",
+ "(Complements our other framework [Hamilton](https://github.com/dagWorks-Inc/hamilton))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3422aed8-323f-45dd-ade1-b1f0906c35f3",
+ "metadata": {},
+ "source": [
+ "## 1. Agent application is modeled as State + Actions --> Graph\n",
+ "Straightforward multi-modal example below:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "id": "7953fe759ae0f510",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-08-05T19:18:03.050318Z",
+ "start_time": "2024-08-05T19:18:01.630987Z"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "import copy\n",
+ "\n",
+ "from IPython.display import Image, display\n",
+ "from IPython.core.display import HTML \n",
+ "import openai\n",
+ "\n",
+ "from burr.core import ApplicationBuilder, State, default, graph, when\n",
+ "from burr.core.action import action\n",
+ "from burr.tracking import LocalTrackingClient\n",
+ "\n",
+ "MODES = {\n",
+ " \"answer_question\": \"text\",\n",
+ " \"generate_image\": \"image\",\n",
+ " \"generate_code\": \"code\",\n",
+ " \"unknown\": \"text\",\n",
+ "}\n",
+ "\n",
+ "\n",
+ "@action(reads=[], writes=[\"chat_history\", \"prompt\"])\n",
+ "def process_prompt(state: State, prompt: str) -> State:\n",
+ " result = {\"chat_item\": {\"role\": \"user\", \"content\": prompt, \"type\": \"text\"}}\n",
+ " state = state.append(chat_history=result[\"chat_item\"])\n",
+ " state = state.update(prompt=prompt)\n",
+ " return state\n",
+ "\n",
+ "\n",
+ "@action(reads=[\"prompt\"], writes=[\"mode\"])\n",
+ "def choose_mode(state: State) -> State:\n",
+ " prompt = (\n",
+ " f\"You are a chatbot. You've been prompted this: {state['prompt']}. \"\n",
+ " f\"You have the capability of responding in the following modes: {', '.join(MODES)}. \"\n",
+ " \"Please respond with *only* a single word representing the mode that most accurately \"\n",
+ " \"corresponds to the prompt. Fr instance, if the prompt is 'draw a picture of a cat', \"\n",
+ " \"the mode would be 'generate_image'. If the prompt is \"\n",
+ " \"'what is the capital of France', the mode would be 'answer_question'.\"\n",
+ " \"If none of these modes apply, please respond with 'unknown'.\"\n",
+ " )\n",
+ "\n",
+ " llm_result = openai.Client().chat.completions.create(\n",
+ " model=\"gpt-4\",\n",
+ " messages=[\n",
+ " {\"role\": \"system\", \"content\": \"You are a helpful assistant\"},\n",
+ " {\"role\": \"user\", \"content\": prompt},\n",
+ " ],\n",
+ " )\n",
+ " content = llm_result.choices[0].message.content\n",
+ " mode = content.lower()\n",
+ " if mode not in MODES:\n",
+ " mode = \"unknown\"\n",
+ " result = {\"mode\": mode}\n",
+ " return state.update(**result)\n",
+ "\n",
+ "\n",
+ "@action(reads=[\"prompt\", \"chat_history\"], writes=[\"response\"])\n",
+ "def prompt_for_more(state: State) -> State:\n",
+ " result = {\n",
+ " \"response\": {\n",
+ " \"content\": \"None of the response modes I support apply to your question. \"\n",
+ " \"Please clarify?\",\n",
+ " \"type\": \"text\",\n",
+ " \"role\": \"assistant\",\n",
+ " }\n",
+ " }\n",
+ " return state.update(**result)\n",
+ "\n",
+ "\n",
+ "@action(reads=[\"prompt\", \"chat_history\", \"mode\"], writes=[\"response\"])\n",
+ "def chat_response(\n",
+ " state: State, prepend_prompt: str, model: str = \"gpt-3.5-turbo\"\n",
+ ") -> State:\n",
+ " \n",
+ " chat_history = copy.deepcopy(state[\"chat_history\"])\n",
+ " chat_history[-1][\"content\"] = f\"{prepend_prompt}: {chat_history[-1]['content']}\"\n",
+ " chat_history_api_format = [\n",
+ " {\n",
+ " \"role\": chat[\"role\"],\n",
+ " \"content\": chat[\"content\"],\n",
+ " }\n",
+ " for chat in chat_history\n",
+ " ]\n",
+ " client = openai.Client()\n",
+ " result = client.chat.completions.create(\n",
+ " model=model,\n",
+ " messages=chat_history_api_format,\n",
+ " )\n",
+ " text_response = result.choices[0].message.content\n",
+ " result = {\"response\": {\"content\": text_response, \"type\": MODES[state[\"mode\"]], \"role\": \"assistant\"}}\n",
+ " return state.update(**result)\n",
+ "\n",
+ "\n",
+ "@action(reads=[\"prompt\", \"chat_history\", \"mode\"], writes=[\"response\"])\n",
+ "def image_response(state: State, model: str = \"dall-e-2\") -> State:\n",
+ " \"\"\"Generates an image response to the prompt. Optional save function to save the image to a URL.\"\"\"\n",
+ " # raise ValueError(\"Demo error\")\n",
+ " client = openai.Client()\n",
+ " result = client.images.generate(\n",
+ " model=model, prompt=state[\"prompt\"], size=\"1024x1024\", quality=\"standard\", n=1\n",
+ " )\n",
+ " image_url = result.data[0].url\n",
+ " result = {\"response\": {\"content\": image_url, \"type\": MODES[state[\"mode\"]], \"role\": \"assistant\"}}\n",
+ " return state.update(**result)\n",
+ "\n",
+ "\n",
+ "@action(reads=[\"response\", \"mode\"], writes=[\"chat_history\"])\n",
+ "def response(state: State) -> State:\n",
+ " # you'd do something specific here based on prior state\n",
+ " result = {\"chat_item\": state[\"response\"]}\n",
+ " return state.append(chat_history=result[\"chat_item\"])\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "id": "add99d139d3d72a0",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-07-16T18:23:11.529415Z",
+ "start_time": "2024-07-16T18:23:11.008552Z"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "image/svg+xml": [
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
+ "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
+ " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
+ "<!-- Generated by graphviz version 12.0.0 (20240704.0754)\n",
+ " -->\n",
+ "<!-- Pages: 1 -->\n",
+ "<svg width=\"497pt\" height=\"304pt\"\n",
+ " viewBox=\"0.00 0.00 497.41 304.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
+ "<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 300)\">\n",
+ "<polygon fill=\"white\" stroke=\"none\" points=\"-4,4 -4,-300 493.41,-300 493.41,4 -4,4\"/>\n",
+ "<!-- prompt -->\n",
+ "<g id=\"node1\" class=\"node\">\n",
+ "<title>prompt</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M408,-231C408,-231 376.25,-231 376.25,-231 370.25,-231 364.25,-225 364.25,-219 364.25,-219 364.25,-207 364.25,-207 364.25,-201 370.25,-195 376.25,-195 376.25,-195 408,-195 408,-195 414,-195 420,-201 420,-207 420,-207 420,-219 420,-219 420,-225 414,-231 408,-231\"/>\n",
+ "<text text-anchor=\"middle\" x=\"392.12\" y=\"-207.95\" font-family=\"Times,serif\" font-size=\"14.00\">prompt</text>\n",
+ "</g>\n",
+ "<!-- decide_mode -->\n",
+ "<g id=\"node3\" class=\"node\">\n",
+ "<title>decide_mode</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M318.5,-166C318.5,-166 253.75,-166 253.75,-166 247.75,-166 241.75,-160 241.75,-154 241.75,-154 241.75,-142 241.75,-142 241.75,-136 247.75,-130 253.75,-130 253.75,-130 318.5,-130 318.5,-130 324.5,-130 330.5,-136 330.5,-142 330.5,-142 330.5,-154 330.5,-154 330.5,-160 324.5,-166 318.5,-166\"/>\n",
+ "<text text-anchor=\"middle\" x=\"286.12\" y=\"-142.95\" font-family=\"Times,serif\" font-size=\"14.00\">decide_mode</text>\n",
+ "</g>\n",
+ "<!-- prompt->decide_mode -->\n",
+ "<g id=\"edge5\" class=\"edge\">\n",
+ "<title>prompt->decide_mode</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M364,-195.28C352.07,-188.19 337.97,-179.82 325.07,-172.15\"/>\n",
+ "<polygon fill=\"black\" stroke=\"black\" points=\"327.18,-169.33 316.8,-167.23 323.6,-175.35 327.18,-169.33\"/>\n",
+ "</g>\n",
+ "<!-- input__prompt -->\n",
+ "<g id=\"node2\" class=\"node\">\n",
+ "<title>input__prompt</title>\n",
+ "<ellipse fill=\"none\" stroke=\"black\" stroke-dasharray=\"5,2\" cx=\"392.12\" cy=\"-278\" rx=\"62.1\" ry=\"18\"/>\n",
+ "<text text-anchor=\"middle\" x=\"392.12\" y=\"-272.95\" font-family=\"Times,serif\" font-size=\"14.00\">input: prompt</text>\n",
+ "</g>\n",
+ "<!-- input__prompt->prompt -->\n",
+ "<g id=\"edge1\" class=\"edge\">\n",
+ "<title>input__prompt->prompt</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M392.12,-259.78C392.12,-254.37 392.12,-248.24 392.12,-242.26\"/>\n",
+ "<polygon fill=\"black\" stroke=\"black\" points=\"395.63,-242.71 392.13,-232.71 388.63,-242.71 395.63,-242.71\"/>\n",
+ "</g>\n",
+ "<!-- generate_image -->\n",
+ "<g id=\"node4\" class=\"node\">\n",
+ "<title>generate_image</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M90.25,-101C90.25,-101 12,-101 12,-101 6,-101 0,-95 0,-89 0,-89 0,-77 0,-77 0,-71 6,-65 12,-65 12,-65 90.25,-65 90.25,-65 96.25,-65 102.25,-71 102.25,-77 102.25,-77 102.25,-89 102.25,-89 102.25,-95 96.25,-101 90.25,-101\"/>\n",
+ "<text text-anchor=\"middle\" x=\"51.12\" y=\"-77.95\" font-family=\"Times,serif\" font-size=\"14.00\">generate_image</text>\n",
+ "</g>\n",
+ "<!-- decide_mode->generate_image -->\n",
+ "<g id=\"edge6\" class=\"edge\">\n",
+ "<title>decide_mode->generate_image</title>\n",
+ "<path fill=\"none\" stroke=\"black\" stroke-dasharray=\"5,2\" d=\"M241.38,-132.38C238.59,-131.55 235.82,-130.75 233.12,-130 182.69,-115.94 166.94,-115.1 113.33,-101.5\"/>\n",
+ "<polygon fill=\"black\" stroke=\"black\" points=\"114.5,-98.19 103.94,-99.08 112.75,-104.96 114.5,-98.19\"/>\n",
+ "</g>\n",
+ "<!-- generate_code -->\n",
+ "<g id=\"node6\" class=\"node\">\n",
+ "<title>generate_code</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M202.12,-101C202.12,-101 132.12,-101 132.12,-101 126.12,-101 120.12,-95 120.12,-89 120.12,-89 120.12,-77 120.12,-77 120.12,-71 126.12,-65 132.12,-65 132.12,-65 202.12,-65 202.12,-65 208.12,-65 214.12,-71 214.12,-77 214.12,-77 214.12,-89 214.12,-89 214.12,-95 208.12,-101 202.12,-101\"/>\n",
+ "<text text-anchor=\"middle\" x=\"167.12\" y=\"-77.95\" font-family=\"Times,serif\" font-size=\"14.00\">generate_code</text>\n",
+ "</g>\n",
+ "<!-- decide_mode->generate_code -->\n",
+ "<g id=\"edge7\" class=\"edge\">\n",
+ "<title>decide_mode->generate_code</title>\n",
+ "<path fill=\"none\" stroke=\"black\" stroke-dasharray=\"5,2\" d=\"M253.29,-129.62C239.9,-122.53 224.22,-114.23 209.95,-106.67\"/>\n",
+ "<polygon fill=\"black\" stroke=\"black\" points=\"211.66,-103.62 201.19,-102.03 208.39,-109.81 211.66,-103.62\"/>\n",
+ "</g>\n",
+ "<!-- answer_question -->\n",
+ "<g id=\"node7\" class=\"node\">\n",
+ "<title>answer_question</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M326.5,-101C326.5,-101 243.75,-101 243.75,-101 237.75,-101 231.75,-95 231.75,-89 231.75,-89 231.75,-77 231.75,-77 231.75,-71 237.75,-65 243.75,-65 243.75,-65 326.5,-65 326.5,-65 332.5,-65 338.5,-71 338.5,-77 338.5,-77 338.5,-89 338.5,-89 338.5,-95 332.5,-101 326.5,-101\"/>\n",
+ "<text text-anchor=\"middle\" x=\"285.12\" y=\"-77.95\" font-family=\"Times,serif\" font-size=\"14.00\">answer_question</text>\n",
+ "</g>\n",
+ "<!-- decide_mode->answer_question -->\n",
+ "<g id=\"edge8\" class=\"edge\">\n",
+ "<title>decide_mode->answer_question</title>\n",
+ "<path fill=\"none\" stroke=\"black\" stroke-dasharray=\"5,2\" d=\"M285.85,-129.78C285.77,-124.37 285.67,-118.24 285.57,-112.26\"/>\n",
+ "<polygon fill=\"black\" stroke=\"black\" points=\"289.08,-112.65 285.42,-102.71 282.08,-112.76 289.08,-112.65\"/>\n",
+ "</g>\n",
+ "<!-- prompt_for_more -->\n",
+ "<g id=\"node8\" class=\"node\">\n",
+ "<title>prompt_for_more</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M457.88,-101C457.88,-101 368.38,-101 368.38,-101 362.38,-101 356.38,-95 356.38,-89 356.38,-89 356.38,-77 356.38,-77 356.38,-71 362.38,-65 368.38,-65 368.38,-65 457.88,-65 457.88,-65 463.88,-65 469.88,-71 469.88,-77 469.88,-77 469.88,-89 469.88,-89 469.88,-95 463.88,-101 457.88,-101\"/>\n",
+ "<text text-anchor=\"middle\" x=\"413.12\" y=\"-77.95\" font-family=\"Times,serif\" font-size=\"14.00\">prompt_for_more</text>\n",
+ "</g>\n",
+ "<!-- decide_mode->prompt_for_more -->\n",
+ "<g id=\"edge9\" class=\"edge\">\n",
+ "<title>decide_mode->prompt_for_more</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M321.16,-129.62C335.74,-122.39 352.84,-113.91 368.31,-106.23\"/>\n",
+ "<polygon fill=\"black\" stroke=\"black\" points=\"369.44,-109.58 376.84,-102 366.33,-103.31 369.44,-109.58\"/>\n",
+ "</g>\n",
+ "<!-- response -->\n",
+ "<g id=\"node9\" class=\"node\">\n",
+ "<title>response</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M304.75,-36C304.75,-36 265.5,-36 265.5,-36 259.5,-36 253.5,-30 253.5,-24 253.5,-24 253.5,-12 253.5,-12 253.5,-6 259.5,0 265.5,0 265.5,0 304.75,0 304.75,0 310.75,0 316.75,-6 316.75,-12 316.75,-12 316.75,-24 316.75,-24 316.75,-30 310.75,-36 304.75,-36\"/>\n",
+ "<text text-anchor=\"middle\" x=\"285.12\" y=\"-12.95\" font-family=\"Times,serif\" font-size=\"14.00\">response</text>\n",
+ "</g>\n",
+ "<!-- generate_image->response -->\n",
+ "<g id=\"edge10\" class=\"edge\">\n",
+ "<title>generate_image->response</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M102.5,-67.38C105.41,-66.57 108.31,-65.77 111.12,-65 155.72,-52.79 206.9,-39.32 242.15,-30.14\"/>\n",
+ "<polygon fill=\"black\" stroke=\"black\" points=\"242.75,-33.6 251.55,-27.69 240.99,-26.82 242.75,-33.6\"/>\n",
+ "</g>\n",
+ "<!-- input__model -->\n",
+ "<g id=\"node5\" class=\"node\">\n",
+ "<title>input__model</title>\n",
+ "<ellipse fill=\"none\" stroke=\"black\" stroke-dasharray=\"5,2\" cx=\"165.12\" cy=\"-148\" rx=\"58.52\" ry=\"18\"/>\n",
+ "<text text-anchor=\"middle\" x=\"165.12\" y=\"-142.95\" font-family=\"Times,serif\" font-size=\"14.00\">input: model</text>\n",
+ "</g>\n",
+ "<!-- input__model->generate_image -->\n",
+ "<g id=\"edge2\" class=\"edge\">\n",
+ "<title>input__model->generate_image</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M137.53,-131.75C124.04,-124.29 107.52,-115.16 92.55,-106.89\"/>\n",
+ "<polygon fill=\"black\" stroke=\"black\" points=\"94.26,-103.84 83.82,-102.07 90.88,-109.97 94.26,-103.84\"/>\n",
+ "</g>\n",
+ "<!-- input__model->generate_code -->\n",
+ "<g id=\"edge3\" class=\"edge\">\n",
+ "<title>input__model->generate_code</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M165.67,-129.78C165.84,-124.37 166.04,-118.24 166.23,-112.26\"/>\n",
+ "<polygon fill=\"black\" stroke=\"black\" points=\"169.71,-112.81 166.53,-102.71 162.72,-112.59 169.71,-112.81\"/>\n",
+ "</g>\n",
+ "<!-- input__model->answer_question -->\n",
+ "<g id=\"edge4\" class=\"edge\">\n",
+ "<title>input__model->answer_question</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M193.87,-131.91C208.2,-124.38 225.84,-115.13 241.77,-106.76\"/>\n",
+ "<polygon fill=\"black\" stroke=\"black\" points=\"243.38,-109.87 250.6,-102.12 240.12,-103.67 243.38,-109.87\"/>\n",
+ "</g>\n",
+ "<!-- generate_code->response -->\n",
+ "<g id=\"edge12\" class=\"edge\">\n",
+ "<title>generate_code->response</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M199.68,-64.62C213.08,-57.47 228.76,-49.09 243.01,-41.48\"/>\n",
+ "<polygon fill=\"black\" stroke=\"black\" points=\"244.57,-44.62 251.75,-36.82 241.28,-38.44 244.57,-44.62\"/>\n",
+ "</g>\n",
+ "<!-- answer_question->response -->\n",
+ "<g id=\"edge11\" class=\"edge\">\n",
+ "<title>answer_question->response</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M285.12,-64.78C285.12,-59.37 285.12,-53.24 285.12,-47.26\"/>\n",
+ "<polygon fill=\"black\" stroke=\"black\" points=\"288.63,-47.71 285.13,-37.71 281.63,-47.71 288.63,-47.71\"/>\n",
+ "</g>\n",
+ "<!-- prompt_for_more->response -->\n",
+ "<g id=\"edge13\" class=\"edge\">\n",
+ "<title>prompt_for_more->response</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M377.81,-64.62C362,-56.84 343.24,-47.61 326.76,-39.49\"/>\n",
+ "<polygon fill=\"black\" stroke=\"black\" points=\"328.7,-36.55 318.19,-35.27 325.61,-42.83 328.7,-36.55\"/>\n",
+ "</g>\n",
+ "<!-- response->prompt -->\n",
+ "<g id=\"edge14\" class=\"edge\">\n",
+ "<title>response->prompt</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M317.03,-21.86C366.8,-27.05 459.27,-39.88 479.12,-65 510.58,-104.78 461.19,-157.01 424.93,-187.3\"/>\n",
+ "<polygon fill=\"black\" stroke=\"black\" points=\"422.78,-184.54 417.22,-193.56 427.19,-189.97 422.78,-184.54\"/>\n",
+ "</g>\n",
+ "</g>\n",
+ "</svg>\n"
+ ],
+ "text/plain": [
+ "<graphviz.graphs.Digraph at 0x12b8d55d0>"
+ ]
+ },
+ "execution_count": 14,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Built the graph.\n",
+ "base_graph = (\n",
+ " graph.GraphBuilder()\n",
+ " .with_actions(\n",
+ " # these are the \"nodes\" \n",
+ " prompt=process_prompt,\n",
+ " decide_mode=choose_mode,\n",
+ " generate_image=image_response,\n",
+ " generate_code=chat_response.bind(\n",
+ " prepend_prompt=\"Please respond with *only* code and no other text (at all) to the following:\",\n",
+ " ),\n",
+ " answer_question=chat_response.bind(\n",
+ " prepend_prompt=\"Please answer the following question:\",\n",
+ " ),\n",
+ " prompt_for_more=prompt_for_more,\n",
+ " response=response,\n",
+ " )\n",
+ " .with_transitions(\n",
+ " # these are the edges between nodes, based on state.\n",
+ " (\"prompt\", \"decide_mode\", default),\n",
+ " (\"decide_mode\", \"generate_image\", when(mode=\"generate_image\")),\n",
+ " (\"decide_mode\", \"generate_code\", when(mode=\"generate_code\")),\n",
+ " (\"decide_mode\", \"answer_question\", when(mode=\"answer_question\")),\n",
+ " (\"decide_mode\", \"prompt_for_more\", default),\n",
+ " (\n",
+ " [\"generate_image\", \"answer_question\", \"generate_code\", \"prompt_for_more\"],\n",
+ " \"response\",\n",
+ " ),\n",
+ " (\"response\", \"prompt\", default),\n",
+ " )\n",
+ " .build()\n",
+ ")\n",
+ "base_graph.visualize()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "63b26ad97a43444",
+ "metadata": {},
+ "source": [
+ "## 2. Build application --> built in checkpointing & tracking"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "id": "4f0bf205944272ff",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-07-16T18:27:12.136291Z",
+ "start_time": "2024-07-16T18:27:11.518066Z"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "image/svg+xml": [
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
+ "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
+ " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
+ "<!-- Generated by graphviz version 12.0.0 (20240704.0754)\n",
+ " -->\n",
+ "<!-- Pages: 1 -->\n",
+ "<svg width=\"576pt\" height=\"328pt\"\n",
+ " viewBox=\"0.00 0.00 575.76 327.50\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
+ "<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 323.5)\">\n",
+ "<polygon fill=\"white\" stroke=\"none\" points=\"-4,4 -4,-323.5 571.76,-323.5 571.76,4 -4,4\"/>\n",
+ "<!-- prompt -->\n",
+ "<g id=\"node1\" class=\"node\">\n",
+ "<title>prompt</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M505.39,-252.5C505.39,-252.5 473.64,-252.5 473.64,-252.5 467.64,-252.5 461.64,-246.5 461.64,-240.5 461.64,-240.5 461.64,-228.5 461.64,-228.5 461.64,-222.5 467.64,-216.5 473.64,-216.5 473.64,-216.5 505.39,-216.5 505.39,-216.5 511.39,-216.5 517.39,-222.5 517.39,-228.5 517.39,-228.5 517.39,-240.5 517.39,-240.5 517.39,-246.5 511.39,-252.5 505.39,-252.5\"/>\n",
+ "<text text-anchor=\"middle\" x=\"489.52\" y=\"-229.45\" font-family=\"Times,serif\" font-size=\"14.00\">prompt</text>\n",
+ "</g>\n",
+ "<!-- decide_mode -->\n",
+ "<g id=\"node3\" class=\"node\">\n",
+ "<title>decide_mode</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M436.89,-185.5C436.89,-185.5 372.14,-185.5 372.14,-185.5 366.14,-185.5 360.14,-179.5 360.14,-173.5 360.14,-173.5 360.14,-161.5 360.14,-161.5 360.14,-155.5 366.14,-149.5 372.14,-149.5 372.14,-149.5 436.89,-149.5 436.89,-149.5 442.89,-149.5 448.89,-155.5 448.89,-161.5 448.89,-161.5 448.89,-173.5 448.89,-173.5 448.89,-179.5 442.89,-185.5 436.89,-185.5\"/>\n",
+ "<text text-anchor=\"middle\" x=\"404.52\" y=\"-162.45\" font-family=\"Times,serif\" font-size=\"14.00\">decide_mode</text>\n",
+ "</g>\n",
+ "<!-- prompt->decide_mode -->\n",
+ "<g id=\"edge5\" class=\"edge\">\n",
+ "<title>prompt->decide_mode</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M466.74,-216.08C457.4,-208.94 446.43,-200.55 436.33,-192.82\"/>\n",
+ "<polygon fill=\"black\" stroke=\"black\" points=\"438.63,-190.18 428.56,-186.89 434.38,-195.74 438.63,-190.18\"/>\n",
+ "</g>\n",
+ "<!-- input__prompt -->\n",
+ "<g id=\"node2\" class=\"node\">\n",
+ "<title>input__prompt</title>\n",
+ "<ellipse fill=\"none\" stroke=\"black\" stroke-dasharray=\"5,2\" cx=\"489.52\" cy=\"-301.5\" rx=\"62.1\" ry=\"18\"/>\n",
+ "<text text-anchor=\"middle\" x=\"489.52\" y=\"-296.45\" font-family=\"Times,serif\" font-size=\"14.00\">input: prompt</text>\n",
+ "</g>\n",
+ "<!-- input__prompt->prompt -->\n",
+ "<g id=\"edge1\" class=\"edge\">\n",
+ "<title>input__prompt->prompt</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M489.52,-283.08C489.52,-277.25 489.52,-270.59 489.52,-264.14\"/>\n",
+ "<polygon fill=\"black\" stroke=\"black\" points=\"493.02,-264.48 489.52,-254.48 486.02,-264.48 493.02,-264.48\"/>\n",
+ "</g>\n",
+ "<!-- generate_image -->\n",
+ "<g id=\"node4\" class=\"node\">\n",
+ "<title>generate_image</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M130.64,-103C130.64,-103 52.39,-103 52.39,-103 46.39,-103 40.39,-97 40.39,-91 40.39,-91 40.39,-79 40.39,-79 40.39,-73 46.39,-67 52.39,-67 52.39,-67 130.64,-67 130.64,-67 136.64,-67 142.64,-73 142.64,-79 142.64,-79 142.64,-91 142.64,-91 142.64,-97 136.64,-103 130.64,-103\"/>\n",
+ "<text text-anchor=\"middle\" x=\"91.52\" y=\"-79.95\" font-family=\"Times,serif\" font-size=\"14.00\">generate_image</text>\n",
+ "</g>\n",
+ "<!-- decide_mode->generate_image -->\n",
+ "<g id=\"edge6\" class=\"edge\">\n",
+ "<title>decide_mode->generate_image</title>\n",
+ "<path fill=\"none\" stroke=\"black\" stroke-dasharray=\"5,2\" d=\"M359.79,-166.05C290.41,-164.61 160.83,-158.64 122.27,-134.5 114.19,-129.44 107.85,-121.39 103.09,-113.3\"/>\n",
+ "<polygon fill=\"black\" stroke=\"black\" points=\"106.28,-111.85 98.57,-104.58 100.06,-115.07 106.28,-111.85\"/>\n",
+ "<text text-anchor=\"middle\" x=\"185.14\" y=\"-121.2\" font-family=\"Times,serif\" font-size=\"14.00\">mode=generate_image</text>\n",
+ "</g>\n",
+ "<!-- generate_code -->\n",
+ "<g id=\"node6\" class=\"node\">\n",
+ "<title>generate_code</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M278.52,-103C278.52,-103 208.52,-103 208.52,-103 202.52,-103 196.52,-97 196.52,-91 196.52,-91 196.52,-79 196.52,-79 196.52,-73 202.52,-67 208.52,-67 208.52,-67 278.52,-67 278.52,-67 284.52,-67 290.52,-73 290.52,-79 290.52,-79 290.52,-91 290.52,-91 290.52,-97 284.52,-103 278.52,-103\"/>\n",
+ "<text text-anchor=\"middle\" x=\"243.52\" y=\"-79.95\" font-family=\"Times,serif\" font-size=\"14.00\">generate_code</text>\n",
+ "</g>\n",
+ "<!-- decide_mode->generate_code -->\n",
+ "<g id=\"edge7\" class=\"edge\">\n",
+ "<title>decide_mode->generate_code</title>\n",
+ "<path fill=\"none\" stroke=\"black\" stroke-dasharray=\"5,2\" d=\"M359.74,-161.19C323.45,-156.06 276.03,-147.17 261.52,-134.5 255.43,-129.18 251.39,-121.61 248.72,-114.02\"/>\n",
+ "<polygon fill=\"black\" stroke=\"black\" points=\"252.16,-113.31 246.11,-104.61 245.41,-115.18 252.16,-113.31\"/>\n",
+ "<text text-anchor=\"middle\" x=\"320.02\" y=\"-121.2\" font-family=\"Times,serif\" font-size=\"14.00\">mode=generate_code</text>\n",
+ "</g>\n",
+ "<!-- answer_question -->\n",
+ "<g id=\"node7\" class=\"node\">\n",
+ "<title>answer_question</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M402.89,-103C402.89,-103 320.14,-103 320.14,-103 314.14,-103 308.14,-97 308.14,-91 308.14,-91 308.14,-79 308.14,-79 308.14,-73 314.14,-67 320.14,-67 320.14,-67 402.89,-67 402.89,-67 408.89,-67 414.89,-73 414.89,-79 414.89,-79 414.89,-91 414.89,-91 414.89,-97 408.89,-103 402.89,-103\"/>\n",
+ "<text text-anchor=\"middle\" x=\"361.52\" y=\"-79.95\" font-family=\"Times,serif\" font-size=\"14.00\">answer_question</text>\n",
+ "</g>\n",
+ "<!-- decide_mode->answer_question -->\n",
+ "<g id=\"edge8\" class=\"edge\">\n",
+ "<title>decide_mode->answer_question</title>\n",
+ "<path fill=\"none\" stroke=\"black\" stroke-dasharray=\"5,2\" d=\"M397.41,-149.12C393.38,-139.74 388.05,-128.04 382.52,-118 381.59,-116.32 380.61,-114.61 379.59,-112.9\"/>\n",
+ "<polygon fill=\"black\" stroke=\"black\" points=\"382.65,-111.2 374.38,-104.59 376.72,-114.92 382.65,-111.2\"/>\n",
+ "<text text-anchor=\"middle\" x=\"454.39\" y=\"-121.2\" font-family=\"Times,serif\" font-size=\"14.00\">mode=answer_question</text>\n",
+ "</g>\n",
+ "<!-- prompt_for_more -->\n",
+ "<g id=\"node8\" class=\"node\">\n",
+ "<title>prompt_for_more</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M534.27,-103C534.27,-103 444.77,-103 444.77,-103 438.77,-103 432.77,-97 432.77,-91 432.77,-91 432.77,-79 432.77,-79 432.77,-73 438.77,-67 444.77,-67 444.77,-67 534.27,-67 534.27,-67 540.27,-67 546.27,-73 546.27,-79 546.27,-79 546.27,-91 546.27,-91 546.27,-97 540.27,-103 534.27,-103\"/>\n",
+ "<text text-anchor=\"middle\" x=\"489.52\" y=\"-79.95\" font-family=\"Times,serif\" font-size=\"14.00\">prompt_for_more</text>\n",
+ "</g>\n",
+ "<!-- decide_mode->prompt_for_more -->\n",
+ "<g id=\"edge9\" class=\"edge\">\n",
+ "<title>decide_mode->prompt_for_more</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M449.11,-160.23C478.92,-155.02 514.31,-146.51 523.52,-134.5 529.09,-127.23 526.67,-119.14 521.13,-111.69\"/>\n",
+ "<polygon fill=\"black\" stroke=\"black\" points=\"523.84,-109.47 514.52,-104.43 518.67,-114.18 523.84,-109.47\"/>\n",
+ "</g>\n",
+ "<!-- response -->\n",
+ "<g id=\"node9\" class=\"node\">\n",
+ "<title>response</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M381.14,-36C381.14,-36 341.89,-36 341.89,-36 335.89,-36 329.89,-30 329.89,-24 329.89,-24 329.89,-12 329.89,-12 329.89,-6 335.89,0 341.89,0 341.89,0 381.14,0 381.14,0 387.14,0 393.14,-6 393.14,-12 393.14,-12 393.14,-24 393.14,-24 393.14,-30 387.14,-36 381.14,-36\"/>\n",
+ "<text text-anchor=\"middle\" x=\"361.52\" y=\"-12.95\" font-family=\"Times,serif\" font-size=\"14.00\">response</text>\n",
+ "</g>\n",
+ "<!-- generate_image->response -->\n",
+ "<g id=\"edge10\" class=\"edge\">\n",
+ "<title>generate_image->response</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M142.94,-71.62C193.82,-59.37 270.83,-40.83 318.55,-29.34\"/>\n",
+ "<polygon fill=\"black\" stroke=\"black\" points=\"319.35,-32.75 328.25,-27.01 317.71,-25.95 319.35,-32.75\"/>\n",
+ "</g>\n",
+ "<!-- input__model -->\n",
+ "<g id=\"node5\" class=\"node\">\n",
+ "<title>input__model</title>\n",
+ "<ellipse fill=\"none\" stroke=\"black\" stroke-dasharray=\"5,2\" cx=\"58.52\" cy=\"-167.5\" rx=\"58.52\" ry=\"18\"/>\n",
+ "<text text-anchor=\"middle\" x=\"58.52\" y=\"-162.45\" font-family=\"Times,serif\" font-size=\"14.00\">input: model</text>\n",
+ "</g>\n",
+ "<!-- input__model->generate_image -->\n",
+ "<g id=\"edge2\" class=\"edge\">\n",
+ "<title>input__model->generate_image</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M49.26,-149.64C45.25,-139.97 42.5,-127.85 47.52,-118 48.79,-115.5 50.36,-113.14 52.15,-110.94\"/>\n",
+ "<polygon fill=\"black\" stroke=\"black\" points=\"54.25,-113.79 58.83,-104.24 49.29,-108.85 54.25,-113.79\"/>\n",
+ "</g>\n",
+ "<!-- input__model->generate_code -->\n",
+ "<g id=\"edge3\" class=\"edge\">\n",
+ "<title>input__model->generate_code</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M59.1,-149.33C60.4,-138.81 63.82,-125.9 72.52,-118 72.79,-117.75 135.74,-106.02 185.17,-96.83\"/>\n",
+ "<polygon fill=\"black\" stroke=\"black\" points=\"185.76,-100.28 194.95,-95.01 184.48,-93.4 185.76,-100.28\"/>\n",
+ "</g>\n",
+ "<!-- input__model->answer_question -->\n",
+ "<g id=\"edge4\" class=\"edge\">\n",
+ "<title>input__model->answer_question</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M111.9,-159.9C143.3,-155.01 183.4,-146.98 217.52,-134.5 231.81,-129.27 233.43,-123.76 247.52,-118 250.2,-116.9 272.65,-110.56 296.93,-103.81\"/>\n",
+ "<polygon fill=\"black\" stroke=\"black\" points=\"297.62,-107.25 306.32,-101.2 295.74,-100.5 297.62,-107.25\"/>\n",
+ "</g>\n",
+ "<!-- generate_code->response -->\n",
+ "<g id=\"edge12\" class=\"edge\">\n",
+ "<title>generate_code->response</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M275.14,-66.58C288.89,-59 305.2,-50.02 319.92,-41.91\"/>\n",
+ "<polygon fill=\"black\" stroke=\"black\" points=\"321.41,-45.09 328.48,-37.2 318.03,-38.96 321.41,-45.09\"/>\n",
+ "</g>\n",
+ "<!-- answer_question->response -->\n",
+ "<g id=\"edge11\" class=\"edge\">\n",
+ "<title>answer_question->response</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M361.52,-66.58C361.52,-60.75 361.52,-54.09 361.52,-47.64\"/>\n",
+ "<polygon fill=\"black\" stroke=\"black\" points=\"365.02,-47.98 361.52,-37.98 358.02,-47.98 365.02,-47.98\"/>\n",
+ "</g>\n",
+ "<!-- prompt_for_more->response -->\n",
+ "<g id=\"edge13\" class=\"edge\">\n",
+ "<title>prompt_for_more->response</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M455.22,-66.58C439.27,-58.48 420.17,-48.78 403.38,-40.26\"/>\n",
+ "<polygon fill=\"black\" stroke=\"black\" points=\"405.13,-37.22 394.62,-35.81 401.96,-43.46 405.13,-37.22\"/>\n",
+ "</g>\n",
+ "<!-- response->prompt -->\n",
+ "<g id=\"edge14\" class=\"edge\">\n",
+ "<title>response->prompt</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M393.44,-22.2C443.23,-27.91 535.74,-41.73 555.52,-67 589.35,-110.23 545.13,-173.01 514.42,-207.83\"/>\n",
+ "<polygon fill=\"black\" stroke=\"black\" points=\"512.11,-205.17 507.98,-214.93 517.29,-209.87 512.11,-205.17\"/>\n",
+ "</g>\n",
+ "</g>\n",
+ "</svg>\n"
+ ],
+ "text/plain": [
+ "<burr.core.application.Application at 0x12afc6b90>"
+ ]
+ },
+ "execution_count": 11,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "tracker = LocalTrackingClient(project=\"agent-demo\")\n",
+ "app = (\n",
+ " ApplicationBuilder()\n",
+ " .with_graph(base_graph)\n",
+ " .initialize_from(\n",
+ " tracker, \n",
+ " resume_at_next_action=True, \n",
+ " default_state={\"chat_history\": []},\n",
+ " default_entrypoint=\"prompt\",\n",
+ " )\n",
+ " .with_tracker(tracker) # tracking + checkpointing; one line 🪄.\n",
+ " .build()\n",
+ ")\n",
+ "app"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b4c48e44fbf36f4d",
+ "metadata": {},
+ "source": [
+ "## 3. Comes with a UI\n",
+ "View runs in the UI; Let's run the app first."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "id": "48745f4c04b9d28d",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Hi, how can I help? what is the capital of France?\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "🤖: The capital of France is Paris.\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Hi, how can I help? write hello world in java\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "🤖: ```\n",
+ "public class HelloWorld {\n",
+ " public static void main(String[] args) {\n",
+ " System.out.println(\"Hello, World!\");\n",
+ " }\n",
+ "}\n",
+ "```\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Hi, how can I help? draw a pen\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "********************************************************************************\n",
+ "-------------------------------------------------------------------\n",
+ "Oh no an error! Need help with Burr?\n",
+ "Join our discord and ask for help! https://discord.gg/4FxBMyzW5n\n",
+ "-------------------------------------------------------------------\n",
+ "> Action: `generate_image` encountered an error!<\n",
+ "> State (at time of action):\n",
+ "{'__PRIOR_STEP': 'decide_mode',\n",
+ " '__SEQUENCE_ID': 10,\n",
+ " 'chat_history': \"[{'role': 'user', 'content': 'what is the capital ...\",\n",
+ " 'mode': 'generate_image',\n",
+ " 'prompt': 'draw a pen',\n",
+ " 'response': \"{'content': '```\\\\npublic class HelloWorld {\\\\n p...\"}\n",
+ "> Inputs (at time of action):\n",
+ "{'prompt': 'draw a pen'}\n",
+ "********************************************************************************\n",
+ "Traceback (most recent call last):\n",
+ " File \"/Users/stefankrawczyk/dagworks/burr/burr/core/application.py\", line 534, in _step\n",
+ " result, new_state = _run_single_step_action(\n",
+ " File \"/Users/stefankrawczyk/dagworks/burr/burr/core/application.py\", line 233, in _run_single_step_action\n",
+ " action.run_and_update(state, **inputs), action.name\n",
+ " File \"/Users/stefankrawczyk/dagworks/burr/burr/core/action.py\", line 533, in run_and_update\n",
+ " return self._fn(state, **self._bound_params, **run_kwargs)\n",
+ " File \"/var/folders/gv/q39lb_1s26x7gbyyypqc3dkm0000gn/T/ipykernel_43564/1354917547.py\", line 94, in image_response\n",
+ " raise ValueError(\"Demo error\")\n",
+ "ValueError: Demo error\n"
+ ]
+ },
+ {
+ "ename": "ValueError",
+ "evalue": "Demo error",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)",
+ "Cell \u001b[0;32mIn[12], line 5\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mquit\u001b[39m\u001b[38;5;124m\"\u001b[39m \u001b[38;5;241m==\u001b[39m user_input\u001b[38;5;241m.\u001b[39mlower():\n\u001b[1;32m 4\u001b[0m \u001b[38;5;28;01mbreak\u001b[39;00m\n\u001b[0;32m----> 5\u001b[0m last_action, action_result, app_state \u001b[38;5;241m=\u001b[39m \u001b[43mapp\u001b[49m\u001b[38;5;241;43m.\u001b[3 [...]
+ "File \u001b[0;32m~/dagworks/burr/burr/telemetry.py:273\u001b[0m, in \u001b[0;36mcapture_function_usage.<locals>.wrapped_fn\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 270\u001b[0m \u001b[38;5;129m@functools\u001b[39m\u001b[38;5;241m.\u001b[39mwraps(call_fn)\n\u001b[1;32m 271\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mwrapped_fn\u001b[39m(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs):\n\u001b[1;32m 27 [...]
+ "File \u001b[0;32m~/dagworks/burr/burr/core/application.py:878\u001b[0m, in \u001b[0;36mApplication.run\u001b[0;34m(self, halt_before, halt_after, inputs)\u001b[0m\n\u001b[1;32m 876\u001b[0m \u001b[38;5;28;01mwhile\u001b[39;00m \u001b[38;5;28;01mTrue\u001b[39;00m:\n\u001b[1;32m 877\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 878\u001b[0m \u001b[38;5;28;43mnext\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mgen\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1 [...]
+ "File \u001b[0;32m~/dagworks/burr/burr/core/application.py:823\u001b[0m, in \u001b[0;36mApplication.iterate\u001b[0;34m(self, halt_before, halt_after, inputs)\u001b[0m\n\u001b[1;32m 820\u001b[0m prior_action: Optional[Action] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[1;32m 821\u001b[0m \u001b[38;5;28;01mwhile\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mhas_next_action():\n\u001b[1;32m 822\u001b[0m \u001b[38;5;66;03 [...]
+ "File \u001b[0;32m~/dagworks/burr/burr/core/application.py:495\u001b[0m, in \u001b[0;36mApplication.step\u001b[0;34m(self, inputs)\u001b[0m\n\u001b[1;32m 492\u001b[0m \u001b[38;5;66;03m# we need to increment the sequence before we start computing\u001b[39;00m\n\u001b[1;32m 493\u001b[0m \u001b[38;5;66;03m# that way if we're replaying from state, we don't get stuck\u001b[39;00m\n\u001b[1;32m 494\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_increment_sequ [...]
+ "File \u001b[0;32m~/dagworks/burr/burr/core/application.py:548\u001b[0m, in \u001b[0;36mApplication._step\u001b[0;34m(self, inputs, _run_hooks)\u001b[0m\n\u001b[1;32m 546\u001b[0m exc \u001b[38;5;241m=\u001b[39m e\n\u001b[1;32m 547\u001b[0m logger\u001b[38;5;241m.\u001b[39mexception(_format_BASE_ERROR_MESSAGE(next_action, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_state, inputs))\n\u001b[0;32m--> 548\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m e\n\ [...]
+ "File \u001b[0;32m~/dagworks/burr/burr/core/application.py:534\u001b[0m, in \u001b[0;36mApplication._step\u001b[0;34m(self, inputs, _run_hooks)\u001b[0m\n\u001b[1;32m 532\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 533\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m next_action\u001b[38;5;241m.\u001b[39msingle_step:\n\u001b[0;32m--> 534\u001b[0m result, new_state \u001b[38;5;241m=\u001b[39m \u001b[43m_run_single_step_action\u001b[49m\u001b[43m(\u001b[49m [...]
+ "File \u001b[0;32m~/dagworks/burr/burr/core/application.py:233\u001b[0m, in \u001b[0;36m_run_single_step_action\u001b[0;34m(action, state, inputs)\u001b[0m\n\u001b[1;32m 230\u001b[0m \u001b[38;5;66;03m# TODO -- guard all reads/writes with a subset of the state\u001b[39;00m\n\u001b[1;32m 231\u001b[0m action\u001b[38;5;241m.\u001b[39mvalidate_inputs(inputs)\n\u001b[1;32m 232\u001b[0m result, new_state \u001b[38;5;241m=\u001b[39m _adjust_single_step_output(\n\u001b[0;32m--> 2 [...]
+ "File \u001b[0;32m~/dagworks/burr/burr/core/action.py:533\u001b[0m, in \u001b[0;36mFunctionBasedAction.run_and_update\u001b[0;34m(self, state, **run_kwargs)\u001b[0m\n\u001b[1;32m 532\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mrun_and_update\u001b[39m(\u001b[38;5;28mself\u001b[39m, state: State, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mrun_kwargs) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m \u001b[38;5;28mtuple\u001b[39m[\u001b[38;5;28mdic [...]
+ "Cell \u001b[0;32mIn[8], line 94\u001b[0m, in \u001b[0;36mimage_response\u001b[0;34m(state, model)\u001b[0m\n\u001b[1;32m 91\u001b[0m \u001b[38;5;129m@action\u001b[39m(reads\u001b[38;5;241m=\u001b[39m[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mprompt\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mchat_history\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mmode\u001b[39m\u001b[38;5;124m\"\u001b[39m], writes [...]
+ "\u001b[0;31mValueError\u001b[0m: Demo error"
+ ]
+ }
+ ],
+ "source": [
+ "while True:\n",
+ " user_input = input(\"Hi, how can I help?\")\n",
+ " if \"quit\" == user_input.lower():\n",
+ " break\n",
+ " last_action, action_result, app_state = app.run(\n",
+ " halt_after=[\"response\"], \n",
+ " inputs={\"prompt\": user_input}\n",
+ " )\n",
+ " last_message = app_state[\"chat_history\"][-1]\n",
+ " if last_message['type'] == 'image':\n",
+ " display(Image(url=last_message[\"content\"]))\n",
+ " else:\n",
+ " print(f\"🤖: {last_message['content']}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5efe2430b34bf8e5",
+ "metadata": {},
+ "source": [
+ "[Open the UI - http://localhost:7241/](http://localhost:7241)\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "49bfbd6bf99f7b14",
+ "metadata": {},
+ "source": [
+ "## But something broke / I want to debug\n",
+ "\n",
+ "Use:\n",
+ "\n",
+ "* Application ID"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "id": "f5588457208604d4",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "app_id = \"a6d74912-9ad6-42f0-9a18-bc17c5e77eaf\""
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "id": "b4abab5aa575628d",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "resumed_app = (\n",
+ " ApplicationBuilder()\n",
+ " .with_graph(base_graph)\n",
+ " .initialize_from(\n",
+ " tracker,\n",
+ " resume_at_next_action=True, \n",
+ " default_state={\"chat_history\": []},\n",
+ " default_entrypoint=\"prompt\",\n",
+ " )\n",
+ " .with_tracker(tracker)\n",
+ " .with_identifiers(app_id=app_id)\n",
+ " .build()\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "id": "4b21ade9-051c-453d-b3fd-f191f970c7b4",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[{'role': 'user', 'content': 'what is the capital of France?', 'type': 'text'},\n",
+ " {'content': 'The capital of France is Paris.',\n",
+ " 'type': 'text',\n",
+ " 'role': 'assistant'},\n",
+ " {'role': 'user', 'content': 'write hello world in java', 'type': 'text'},\n",
+ " {'content': '```\\npublic class HelloWorld {\\n public static void main(String[] args) {\\n System.out.println(\"Hello, World!\");\\n }\\n}\\n```',\n",
+ " 'type': 'code',\n",
+ " 'role': 'assistant'},\n",
+ " {'role': 'user', 'content': 'draw a pen', 'type': 'text'}]"
+ ]
+ },
+ "execution_count": 17,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "resumed_app.state[\"chat_history\"]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "id": "2186ef09eaff58d9",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Hi, how can I help? \n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "<img src=\"https://oaidalleapiprodscus.blob.core.windows.net/private/org-UENfV68Ccazb3rdeYDLRWmoF/user-KU9L39oZGkNLkg6ZfSLMc4AV/img-nONYcLQepD3jBslMG4staoEb.png?st=2024-08-05T20%3A52%3A15Z&se=2024-08-05T22%3A52%3A15Z&sp=r&sv=2023-11-03&sr=b&rscd=inline&rsct=image/png&skoid=6aaadede-4fb3-4698-a8f6-684d7786b067&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skt=2024-08-05T12%3A52%3A48Z&ske=2024-08-06T12%3A52%3A48Z&sks=b&skv=2023-11-03&sig=qsXqOHTMZhItTdL35Nd4Zic1/Q4LHaDg%2BlFG05rExkw%3D\"/>"
+ ],
+ "text/plain": [
+ "<IPython.core.display.Image object>"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Hi, how can I help? what is the capital of England?\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "🤖: The capital of England is London.\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Hi, how can I help? quit\n"
+ ]
+ }
+ ],
+ "source": [
+ "while True:\n",
+ " user_input = input(\"Hi, how can I help?\")\n",
+ " if \"quit\" == user_input.lower():\n",
+ " break\n",
+ " last_action, action_result, app_state = resumed_app.run(\n",
+ " halt_after=[\"response\"], \n",
+ " inputs={\"prompt\": user_input}\n",
+ " )\n",
+ " last_message = app_state[\"chat_history\"][-1]\n",
+ " if last_message['type'] == 'image':\n",
+ " display(Image(url=last_message[\"content\"]))\n",
+ " else:\n",
+ " print(f\"🤖: {last_message['content']}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "6bec09d1e740a30d",
+ "metadata": {},
+ "source": [
+ "## Actually what if I want to go back to a certain point in time?\n",
+ "\n",
+ "* Fork: Start with state from any checkpoint"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "id": "c008089be309c098",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-07-16T18:49:55.910255Z",
+ "start_time": "2024-07-16T18:49:55.906299Z"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "app_id = \"a6d74912-9ad6-42f0-9a18-bc17c5e77eaf\"\n",
+ "sequence_id = 4\n",
+ "# partition_key = \"\""
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "id": "a8c5631cfebe8e48",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "forked_app = (\n",
+ " ApplicationBuilder()\n",
+ " .with_graph(base_graph) # this could be different...\n",
+ " .initialize_from(\n",
+ " tracker,\n",
+ " resume_at_next_action=True, \n",
+ " default_state={\"chat_history\": []},\n",
+ " default_entrypoint=\"prompt\",\n",
+ " fork_from_app_id=app_id,\n",
+ " fork_from_sequence_id=sequence_id,\n",
+ " # fork_from_partition_key=partition_key\n",
+ " )\n",
+ " .with_tracker(tracker)\n",
+ " .build()\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 21,
+ "id": "2a725130e0532f0c",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-07-16T18:58:04.223560Z",
+ "start_time": "2024-07-16T18:58:04.218274Z"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[{'role': 'user', 'content': 'what is the capital of France?', 'type': 'text'},\n",
+ " {'content': 'The capital of France is Paris.',\n",
+ " 'type': 'text',\n",
+ " 'role': 'assistant'},\n",
+ " {'role': 'user', 'content': 'write hello world in java', 'type': 'text'}]"
+ ]
+ },
+ "execution_count": 21,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# show prior forked state\n",
+ "forked_app.state[\"chat_history\"]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "e27a17968c62aa98",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Hi, how can I help? \n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "🤖: ```java\n",
+ "public class HelloWorld {\n",
+ " public static void main(String[] args) {\n",
+ " System.out.println(\"Hello, World!\");\n",
+ " }\n",
+ "}\n",
+ "```\n"
+ ]
+ }
+ ],
+ "source": [
+ "while True:\n",
+ " user_input = input(\"Hi, how can I help?\")\n",
+ " if \"quit\" == user_input.lower():\n",
+ " break\n",
+ " last_action, action_result, app_state = forked_app.run(\n",
+ " halt_after=[\"response\"], \n",
+ " inputs={\"prompt\": user_input}\n",
+ " )\n",
+ " last_message = app_state[\"chat_history\"][-1]\n",
+ " if last_message['type'] == 'image':\n",
+ " display(Image(url=last_message[\"content\"]))\n",
+ " else:\n",
+ " print(f\"🤖: {last_message['content']}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e3eb81d1f240d926",
+ "metadata": {},
+ "source": [
+ "## Want to know more?\n",
+ "\n",
+ "[Link to video walking through this notebook](https://youtu.be/hqutVJyd3TI).\n",
+ "\n",
+ "[https://github.com/dagworks-inc/burr](https://github.com/dagworks-inc/burr)\n",
+ "<img src=\"burr_qrcode.png\" width=\"125\"/>\n",
+ "\n",
+ "[Time Travel blog post & video:](https://blog.dagworks.io/p/travel-back-in-time-with-burr)\n",
+ "\n",
+ "<a href=\"https://www.youtube.com/embed/98vxhIcE6NI?si=oV1BbSUCKa1UvX5P\"><img src=\"https://img.youtube.com/vi/98vxhIcE6NI/0.jpg\"> \n",
+ "</img>\n",
+ "<a href=\"https://www.youtube.com/embed/98vxhIcE6NI?si=oV1BbSUCKa1UvX5P\">\n",
+ "\n",
+ "More blogs @ `blog.dagworks.io` e.g. [async & streaming](https://blog.dagworks.io/p/streaming-chatbot-with-burr-fastapi)\n",
+ "\n",
+ "More [examples](https://github.com/DAGWorks-Inc/burr/tree/main/examples/):\n",
+ "\n",
+ "- e.g. [test case creation](https://burr.dagworks.io/examples/guardrails/creating_tests/)\n",
+ "- e.g. [multi-agent collaboration](https://github.com/DAGWorks-Inc/burr/tree/main/examples/multi-agent-collaboration)\n",
+ "\n",
+ "Follow on Twitter & LinkedIn:\n",
+ "\n",
+ "- https://x.com/burr_framework\n",
+ "- https://x.com/dagworks\n",
+ "- https://x.com/stefkrawczyk\n",
+ "- https://www.linkedin.com/in/skrawczyk/"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.10.4"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/content/_sources/examples/chatbots/index.rst.txt b/content/_sources/examples/chatbots/index.rst.txt
new file mode 100644
index 00000000..5e1b99a5
--- /dev/null
+++ b/content/_sources/examples/chatbots/index.rst.txt
@@ -0,0 +1,10 @@
+============
+📞 Chatbots
+============
+
+.. toctree::
+ :maxdepth: 2
+
+ basic-chatbot
+ gpt-like-chatbot
+ rag-chatbot-hamilton
diff --git a/content/_sources/examples/chatbots/rag-chatbot-hamilton.ipynb.txt b/content/_sources/examples/chatbots/rag-chatbot-hamilton.ipynb.txt
new file mode 100644
index 00000000..870e2b78
--- /dev/null
+++ b/content/_sources/examples/chatbots/rag-chatbot-hamilton.ipynb.txt
@@ -0,0 +1,801 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "1622e1563a35aa32",
+ "metadata": {
+ "collapsed": false,
+ "jupyter": {
+ "outputs_hidden": false
+ }
+ },
+ "source": [
+ "# Conversational RAG with Burr and Hamilton\n",
+ "\n",
+ "See [GitHub example](https://github.com/DAGWorks-Inc/burr/tree/main/examples/conversational-rag/simple_example) and the accompanying video walkthrough:\n",
+ "\n",
+ "<div>\n",
+ " <iframe width=\"600\" height=\"380\" src=\"https://www.youtube.com/embed/t54DCiOH270?si=QpPNs7m2t0L0V8Va\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen></iframe>\n",
+ "</div>"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "9069a73d207fd136",
+ "metadata": {
+ "collapsed": false,
+ "jupyter": {
+ "outputs_hidden": false
+ }
+ },
+ "outputs": [],
+ "source": [
+ "!pip install burr[start] sf-hamilton[visualization] openai "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "initial_id",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-05-10T21:02:28.908380Z",
+ "start_time": "2024-05-10T21:02:18.236814Z"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "# Importing the necessary libraries\n",
+ "import pprint\n",
+ "from typing import Tuple\n",
+ "from hamilton import dataflows, driver\n",
+ "import burr.core\n",
+ "from burr.core import ApplicationBuilder, State, default, expr\n",
+ "from burr.core.action import action\n",
+ "from burr.tracking import LocalTrackingClient\n",
+ "import uuid"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "8f1d578469a918b1",
+ "metadata": {
+ "collapsed": false,
+ "jupyter": {
+ "outputs_hidden": false
+ }
+ },
+ "source": [
+ "## Load your \"chain\" or conversational RAG \"pipeline\"\n",
+ "\n",
+ "We use Hamilton here. But you could use LangChain, etc., or forgo them and write your own code."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "c6a018aff1154f0b",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-03-26T20:47:42.057246Z",
+ "start_time": "2024-03-26T20:47:40.500075Z"
+ },
+ "collapsed": false,
+ "jupyter": {
+ "outputs_hidden": false
+ }
+ },
+ "outputs": [],
+ "source": [
+ "# Loads Hamilton DAG\n",
+ "conversational_rag = dataflows.import_module(\"conversational_rag\")\n",
+ "conversational_rag_driver = (\n",
+ " driver.Builder()\n",
+ " .with_config({}) # replace with configuration as appropriate\n",
+ " .with_modules(conversational_rag)\n",
+ " .build()\n",
+ ")\n",
+ "conversational_rag_driver.display_all_functions()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "82b3515afd2de6e4",
+ "metadata": {
+ "collapsed": false,
+ "jupyter": {
+ "outputs_hidden": false
+ }
+ },
+ "source": [
+ "## Create the actions that will constitute our application\n",
+ "\n",
+ "We will use the functional (vs class) approach to declaring actions here. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "6433dad5abc6eb16",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-03-26T20:47:42.065785Z",
+ "start_time": "2024-03-26T20:47:42.059539Z"
+ },
+ "collapsed": false,
+ "jupyter": {
+ "outputs_hidden": false
+ }
+ },
+ "outputs": [],
+ "source": [
+ "@action(\n",
+ " reads=[\"question\", \"chat_history\"],\n",
+ " writes=[\"chat_history\"],\n",
+ ")\n",
+ "def ai_converse(state: State, vector_store: object) -> Tuple[dict, State]:\n",
+ " \"\"\"AI conversing step. Uses Hamilton to execute the conversational pipeline.\"\"\"\n",
+ " result = conversational_rag_driver.execute(\n",
+ " [\"conversational_rag_response\"],\n",
+ " inputs={\n",
+ " \"question\": state[\"question\"],\n",
+ " \"chat_history\": state[\"chat_history\"],\n",
+ " },\n",
+ " # we use overrides here because we want to pass in the vector store\n",
+ " overrides={\n",
+ " \"vector_store\": vector_store,\n",
+ " }\n",
+ " )\n",
+ " new_history = f\"AI: {result['conversational_rag_response']}\"\n",
+ " return result, state.append(chat_history=new_history)\n",
+ "\n",
+ "\n",
+ "@action(\n",
+ " reads=[],\n",
+ " writes=[\"question\", \"chat_history\"],\n",
+ ")\n",
+ "def human_converse(state: State, user_question: str) -> Tuple[dict, State]:\n",
+ " \"\"\"Human converse step -- make sure we get input, and store it as state.\"\"\"\n",
+ " state = state.update(question=user_question).append(chat_history=f\"Human: {user_question}\")\n",
+ " return {\"question\": user_question}, state"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "4439e0dd39441414",
+ "metadata": {
+ "collapsed": false,
+ "jupyter": {
+ "outputs_hidden": false
+ }
+ },
+ "source": [
+ "## Add a hook to print the steps -- optional but shows that Burr is pluggable"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "e3c3af84fab14f39",
+ "metadata": {
+ "collapsed": false,
+ "jupyter": {
+ "outputs_hidden": false
+ }
+ },
+ "outputs": [],
+ "source": [
+ "from burr.core import Action\n",
+ "from burr.lifecycle import PostRunStepHook, PreRunStepHook\n",
+ "\n",
+ "class PrintStepHook(PostRunStepHook, PreRunStepHook):\n",
+ " \"\"\"Custom hook to print the action/result after each step.\"\"\"\n",
+ "\n",
+ " def pre_run_step(self, action: Action, **future_kwargs):\n",
+ " if action.name == \"ai_converse\":\n",
+ " print(\"🤔 AI is thinking...\")\n",
+ " if action.name == \"human_converse\":\n",
+ " print(\"⏳Processing input from user...\")\n",
+ "\n",
+ " def post_run_step(self, *, state: \"State\", action: Action, result: dict, **future_kwargs):\n",
+ " if action.name == \"human_converse\":\n",
+ " print(\"🎙💬\", result[\"question\"], \"\\n\")\n",
+ " if action.name == \"ai_converse\":\n",
+ " print(\"🤖💬\", result[\"conversational_rag_response\"], \"\\n\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9579b47aac2c53a0",
+ "metadata": {
+ "collapsed": false,
+ "jupyter": {
+ "outputs_hidden": false
+ }
+ },
+ "source": [
+ "## Create the application\n",
+ "\n",
+ "We now create the application, which is a collection of actions, and then set the transitions between the actions based on values in State.\n",
+ "\n",
+ "We also initialize initial values etc to populate the application with."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "77e9f67b660a0953",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-03-26T20:50:25.642660Z",
+ "start_time": "2024-03-26T20:50:25.616245Z"
+ },
+ "collapsed": false,
+ "jupyter": {
+ "outputs_hidden": false
+ }
+ },
+ "outputs": [],
+ "source": [
+ "# what we will do RAG over.\n",
+ "initial_documents = [\n",
+ " \"harrison worked at kensho\",\n",
+ " \"stefan worked at Stitch Fix\",\n",
+ " \"stefan likes tacos\",\n",
+ " \"elijah worked at TwoSigma\",\n",
+ " \"elijah likes mango\",\n",
+ " \"stefan used to work at IBM\",\n",
+ " \"elijah likes to go biking\",\n",
+ " \"stefan likes to bake sourdough\",\n",
+ "]\n",
+ "# bootstrap the vector store;\n",
+ "vector_store = conversational_rag_driver.execute(\n",
+ " [\"vector_store\"],\n",
+ " inputs={\"input_texts\": initial_documents})[\"vector_store\"]\n",
+ "# what we will initialize the application with\n",
+ "initial_state = {\n",
+ " \"question\": \"\",\n",
+ " \"chat_history\": [],\n",
+ "}\n",
+ "\n",
+ "app_id = str(uuid.uuid4())\n",
+ "app = (\n",
+ " ApplicationBuilder()\n",
+ " # add the actions\n",
+ " .with_actions(\n",
+ " # bind the vector store to the AI conversational step\n",
+ " ai_converse=ai_converse.bind(vector_store=vector_store),\n",
+ " human_converse=human_converse,\n",
+ " terminal=burr.core.Result(\"chat_history\"),\n",
+ " )\n",
+ " # set the transitions between actions\n",
+ " .with_transitions(\n",
+ " (\"ai_converse\", \"human_converse\", default),\n",
+ " (\"human_converse\", \"terminal\", expr(\"'exit' in question\")),\n",
+ " (\"human_converse\", \"ai_converse\", default),\n",
+ " )\n",
+ " # add identifiers that will help track the application\n",
+ " .with_identifiers(app_id=app_id, partition_key=\"sample_user\")\n",
+ " # initialize the state\n",
+ " .with_state(**initial_state)\n",
+ " # say what the initial action is\n",
+ " .with_entrypoint(\"human_converse\")\n",
+ " # add a hook to print the steps -- optional but shows that Burr is pluggable\n",
+ " .with_hooks(PrintStepHook())\n",
+ " # add tracking -- this will show up in the BURR UI.\n",
+ " .with_tracker(project=\"demo:conversational-rag\")\n",
+ " # build the application\n",
+ " .build()\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "bf5d1e084a791fa5",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-03-26T20:48:39.379712Z",
+ "start_time": "2024-03-26T20:48:38.701659Z"
+ },
+ "collapsed": false,
+ "jupyter": {
+ "outputs_hidden": false
+ }
+ },
+ "outputs": [],
+ "source": [
+ "# let's visualize what we have\n",
+ "app.visualize(include_conditions=True)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "430bab287b6ad9a",
+ "metadata": {
+ "collapsed": false,
+ "jupyter": {
+ "outputs_hidden": false
+ }
+ },
+ "source": [
+ "## Let's run the app. \n",
+ "\n",
+ "Let's run it a step at a time."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "8bcfe9ca48f87618",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-03-26T20:50:38.782857Z",
+ "start_time": "2024-03-26T20:50:28.797204Z"
+ },
+ "collapsed": false,
+ "jupyter": {
+ "outputs_hidden": false
+ }
+ },
+ "outputs": [],
+ "source": [
+ "app.reset_to_entrypoint() # reset the app to the entrypoint\n",
+ "user_question = input(\"Ask something (or type exit to quit): \")\n",
+ "previous_action, result, state = app.step(\n",
+ " inputs={\"user_question\": user_question},\n",
+ ")\n",
+ "print(f\"Ran action {previous_action.name} with result:\\n {pprint.pformat(result)} \\n and state:\\n {pprint.pformat(state.get_all())}\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "id": "81940578d58fd602",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-03-26T20:50:44.662755Z",
+ "start_time": "2024-03-26T20:50:41.919782Z"
+ },
+ "collapsed": false,
+ "jupyter": {
+ "outputs_hidden": false
+ }
+ },
+ "outputs": [],
+ "source": [
+ "# now let's run the AI conversational step\n",
+ "previous_action, result, state = app.step()\n",
+ "print(f\"Ran action {previous_action.name} with result:\\n {pprint.pformat(result)} \\n and state:\\n {pprint.pformat(state.get_all())}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "36ec2f4908c2dde2",
+ "metadata": {
+ "collapsed": false,
+ "jupyter": {
+ "outputs_hidden": false
+ }
+ },
+ "source": [
+ "## Let's now run the app to completion\n",
+ "\n",
+ "You could do the above for each action. Or you could tell the app to run until certain\n",
+ "actions/conditions are met."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "id": "be6c573158b65cb1",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-03-26T20:52:21.364028Z",
+ "start_time": "2024-03-26T20:50:52.382808Z"
+ },
+ "collapsed": false,
+ "jupyter": {
+ "outputs_hidden": false
+ }
+ },
+ "outputs": [],
+ "source": [
+ "print(f\"Running RAG with initial state:\\n {pprint.pformat(app.state.get_all())}\")\n",
+ "while True:\n",
+ " user_question = input(\"Ask something (or type exit to quit): \")\n",
+ " previous_action, result, state = app.run(\n",
+ " halt_before=[\"human_converse\"],\n",
+ " halt_after=[\"terminal\"],\n",
+ " inputs={\"user_question\": user_question},\n",
+ " )\n",
+ " if previous_action.name == \"terminal\":\n",
+ " # reached the end\n",
+ " pprint.pprint(result)\n",
+ " break"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "169946a65f977df9",
+ "metadata": {
+ "collapsed": false,
+ "jupyter": {
+ "outputs_hidden": false
+ }
+ },
+ "source": [
+ "## Reloading from prior state\n",
+ "\n",
+ "Burr makes it easy to reload from a prior state. In this example we'll just use what is logged to the tracker to \"go back in time\" and reload the application to that state. \n",
+ "\n",
+ "This is useful for debugging, building the application itself, etc.\n",
+ "\n",
+ "There are two ways to load prior state:\n",
+ "1. Load the state outside the Burr Application. i.e. pass it in as initial state.\n",
+ "2. Use the ApplicationBuilder .initialize_from() method. You can optionally \"fork\" or continue an existing application this way.\n",
+ "\n",
+ "The difference between them, is that the first method is more flexible and gives you total control.\n",
+ "The second method gives you two options: \n",
+ "(a) to explicitly \"fork\" i.e. create a new application from a certain point in time from an existing application_id.\n",
+ "(b) allows you to \"pick up where you left off\" with an existing application_id, e.g. in the case of a crash, or if you wanted to start from the last conversation with a user, and continue.\n",
+ "\n",
+ "Below we show how to do the first method. Then after that the second method, to show how to fork the conversation,\n",
+ "as well as pick up the prior conversation from where it left off. \n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "id": "c7f4dd64f73ed2d8",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-03-26T20:52:37.419848Z",
+ "start_time": "2024-03-26T20:52:37.393869Z"
+ },
+ "collapsed": false,
+ "jupyter": {
+ "outputs_hidden": false
+ }
+ },
+ "outputs": [],
+ "source": [
+ "# set up for rewinding to a prior state -- loading it in as initial state\n",
+ "prior_app_id = app_id\n",
+ "last_sequence_id = app.sequence_id\n",
+ "rewind_to_sequence_id = last_sequence_id - 2\n",
+ "new_app_id = str(uuid.uuid4())\n",
+ "\n",
+ "project_name = \"demo:conversational-rag\"\n",
+ "# we use the tracking client here to get the state of the application at a prior sequence_id\n",
+ "tracker = LocalTrackingClient(project=project_name)\n",
+ "persisted_state = tracker.load(partition_key=\"sample_user\", \n",
+ " app_id=prior_app_id, \n",
+ " sequence_id=rewind_to_sequence_id)\n",
+ "state_values = persisted_state['state'].get_all()\n",
+ "print(f\"Loaded state from app_id:{prior_app_id}, \"\n",
+ " f\"sequence_id:{rewind_to_sequence_id}::\\n \"\n",
+ " f\"{pprint.pformat(state_values)}\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "id": "5ee618e3-15c0-403b-bc96-3a2faaea457e",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# build our other application using the state we just loaded.\n",
+ "other_app = (\n",
+ " ApplicationBuilder()\n",
+ " # add the actions\n",
+ " .with_actions(\n",
+ " # bind the vector store to the AI conversational step\n",
+ " ai_converse=ai_converse.bind(vector_store=vector_store),\n",
+ " human_converse=human_converse,\n",
+ " terminal=burr.core.Result(\"chat_history\"),\n",
+ " )\n",
+ " # set the transitions between actions\n",
+ " .with_transitions(\n",
+ " (\"ai_converse\", \"human_converse\", default),\n",
+ " (\"human_converse\", \"terminal\", expr(\"'exit' in question\")),\n",
+ " (\"human_converse\", \"ai_converse\", default),\n",
+ " )\n",
+ " # add identifiers that will help track the application\n",
+ " .with_identifiers(app_id=new_app_id, partition_key=\"sample_user\")\n",
+ " # set state to prior state\n",
+ " .with_state(**persisted_state[\"state\"].get_all())\n",
+ " # say where we want to start\n",
+ " .with_entrypoint(\"human_converse\")\n",
+ " # add a hook to print the steps -- optional but shows that Burr is pluggable\n",
+ " .with_hooks(PrintStepHook())\n",
+ " # add tracking -- this will show up in the BURR UI.\n",
+ " .with_tracker(tracker)\n",
+ " # build the application\n",
+ " .build()\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "id": "34140c5864b940dc",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-03-26T20:54:24.035153Z",
+ "start_time": "2024-03-26T20:53:19.237522Z"
+ },
+ "collapsed": false,
+ "jupyter": {
+ "outputs_hidden": false
+ }
+ },
+ "outputs": [],
+ "source": [
+ "# We can now change test, debug, etc. from this prior state.\n",
+ "print(f\"Running RAG with loaded state:\\n {pprint.pformat(state_values)}\")\n",
+ "while True:\n",
+ " user_question = input(\"Ask something (or type exit to quit): \")\n",
+ " previous_action, result, state = other_app.run(\n",
+ " halt_before=[\"human_converse\"],\n",
+ " halt_after=[\"terminal\"],\n",
+ " inputs={\"user_question\": user_question},\n",
+ " )\n",
+ " if previous_action and previous_action.name == \"terminal\":\n",
+ " # reached the end\n",
+ " pprint.pprint(result)\n",
+ " break\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1d7979bc-a09b-4abd-b22b-02ca38d4a26c",
+ "metadata": {},
+ "source": [
+ "### Picking up where we left off from a prior conversation with `initialize_from()`"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "id": "fc62a033644c7b80",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-03-26T21:04:56.748649Z",
+ "start_time": "2024-03-26T21:04:56.742019Z"
+ },
+ "collapsed": false,
+ "jupyter": {
+ "outputs_hidden": false
+ }
+ },
+ "outputs": [],
+ "source": [
+ "# Now let's show how to use the ApplicationBuilder.initialize_from() method to pick up where we left off.\n",
+ "# This is useful if you want to continue a conversation with a user, or if you had a crash, etc.\n",
+ "\n",
+ "# set up for rewinding to a prior state -- loading it in as initial state\n",
+ "prior_app_id = app_id\n",
+ "\n",
+ "project_name = \"demo:conversational-rag\"\n",
+ "# we use the tracking client here to get the state of the application at a prior sequence_id\n",
+ "tracker = LocalTrackingClient(project=project_name)\n",
+ "pick_up_where_we_left_off_app = (\n",
+ " ApplicationBuilder()\n",
+ " # add the actions\n",
+ " .with_actions(\n",
+ " # bind the vector store to the AI conversational step\n",
+ " ai_converse=ai_converse.bind(vector_store=vector_store),\n",
+ " human_converse=human_converse,\n",
+ " terminal=burr.core.Result(\"chat_history\"),\n",
+ " )\n",
+ " # set the transitions between actions\n",
+ " .with_transitions(\n",
+ " (\"ai_converse\", \"human_converse\", default),\n",
+ " (\"human_converse\", \"terminal\", expr(\"'exit' in question\")),\n",
+ " (\"human_converse\", \"ai_converse\", default),\n",
+ " )\n",
+ " # add identifiers that will help track the application\n",
+ " .with_identifiers(app_id=prior_app_id, partition_key=\"sample_user\")\n",
+ " .initialize_from(\n",
+ " initializer=tracker,\n",
+ " resume_at_next_action=False, # we want to always start at human_converse; our entrypoint\n",
+ " default_entrypoint=\"human_converse\",\n",
+ " default_state=initial_state, # set some default state incase we can't find the prior state\n",
+ " )\n",
+ " # add a hook to print the steps -- optional but shows that Burr is pluggable\n",
+ " .with_hooks(PrintStepHook())\n",
+ " # add tracking -- this will show up in the BURR UI.\n",
+ " .with_tracker(tracker)\n",
+ " # build the application\n",
+ " .build()\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "id": "b6d23d6d6a6643d0",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-03-26T21:05:41.246005Z",
+ "start_time": "2024-03-26T21:05:02.855430Z"
+ },
+ "collapsed": false,
+ "jupyter": {
+ "outputs_hidden": false
+ }
+ },
+ "outputs": [],
+ "source": [
+ "print(f\"Running RAG with loaded state:\\n {pprint.pformat(pick_up_where_we_left_off_app.state.get_all())}\")\n",
+ "while True:\n",
+ " user_question = input(\"Ask something (or type exit to quit): \")\n",
+ " previous_action, result, state = pick_up_where_we_left_off_app.run(\n",
+ " halt_before=[\"human_converse\"],\n",
+ " halt_after=[\"terminal\"],\n",
+ " inputs={\"user_question\": user_question},\n",
+ " )\n",
+ " if previous_action and previous_action.name == \"terminal\":\n",
+ " # reached the end\n",
+ " pprint.pprint(result)\n",
+ " break"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e51159c1-04a1-4b55-8615-97dc6d5c6975",
+ "metadata": {
+ "collapsed": false,
+ "jupyter": {
+ "outputs_hidden": false
+ }
+ },
+ "source": [
+ "### Forking the application using prior state with `initialize_from()`"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 22,
+ "id": "84d533ba-fd85-4b67-a6c3-30356cc9b048",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Now let's show how to use the ApplicationBuilder.initialize_from() method to fork a new application.\n",
+ "# This is useful if you want to debug/reuse prior state, rewind and try something else, etc.\n",
+ "\n",
+ "# set up for rewinding to a prior state -- loading it in as initial state\n",
+ "prior_app_id = app_id\n",
+ "new_app_id = str(uuid.uuid4())\n",
+ "\n",
+ "project_name = \"demo:conversational-rag\"\n",
+ "# we use the tracking client here to get the state of the application at a prior sequence_id\n",
+ "tracker = LocalTrackingClient(project=project_name)\n",
+ "forking_new_application = (\n",
+ " ApplicationBuilder()\n",
+ " # add the actions\n",
+ " .with_actions(\n",
+ " # bind the vector store to the AI conversational step\n",
+ " ai_converse=ai_converse.bind(vector_store=vector_store),\n",
+ " human_converse=human_converse,\n",
+ " terminal=burr.core.Result(\"chat_history\"),\n",
+ " )\n",
+ " # set the transitions between actions\n",
+ " .with_transitions(\n",
+ " (\"ai_converse\", \"human_converse\", default),\n",
+ " (\"human_converse\", \"terminal\", expr(\"'exit' in question\")),\n",
+ " (\"human_converse\", \"ai_converse\", default),\n",
+ " )\n",
+ " # add identifiers that will help track the application\n",
+ " .with_identifiers(app_id=new_app_id, partition_key=\"sample_user\")\n",
+ " .initialize_from(\n",
+ " initializer=tracker,\n",
+ " resume_at_next_action=False, # we want to always start at human_converse; our entrypoint\n",
+ " default_entrypoint=\"human_converse\",\n",
+ " default_state=initial_state, # set some default state incase we can't find the prior state\n",
+ " fork_from_app_id=prior_app_id, # <---- The new addition \n",
+ " fork_from_partition_key=\"sample_user\", # <---- The new addition \n",
+ " fork_from_sequence_id=5, # <---- The new addition \n",
+ " )\n",
+ " # add a hook to print the steps -- optional but shows that Burr is pluggable\n",
+ " .with_hooks(PrintStepHook())\n",
+ " # add tracking -- this will show up in the BURR UI.\n",
+ " .with_tracker(tracker)\n",
+ " # build the application\n",
+ " .build()\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 23,
+ "id": "a9086e58-eeaa-4886-a113-5c7fd5fe0af0",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "print(f\"Running RAG with loaded state:\\n {pprint.pformat(forking_new_application.state.get_all())}\")\n",
+ "while True:\n",
+ " user_question = input(\"Ask something (or type exit to quit): \")\n",
+ " previous_action, result, state = forking_new_application.run(\n",
+ " halt_before=[\"human_converse\"],\n",
+ " halt_after=[\"terminal\"],\n",
+ " inputs={\"user_question\": user_question},\n",
+ " )\n",
+ " if previous_action and previous_action.name == \"terminal\":\n",
+ " # reached the end\n",
+ " pprint.pprint(result)\n",
+ " break"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "d6523a54-e333-4d5e-941d-d3b28b940500",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# this should print some warnings:\n",
+ "new_app_id = str(uuid.uuid4())\n",
+ "\n",
+ "project_name = \"demo:conversational-rag\"\n",
+ "# we use the tracking client here to get the state of the application at a prior sequence_id\n",
+ "tracker = LocalTrackingClient(project=project_name)\n",
+ "bad_fork = (\n",
+ " ApplicationBuilder()\n",
+ " # add the actions\n",
+ " .with_actions(\n",
+ " # bind the vector store to the AI conversational step\n",
+ " ai_converse=ai_converse.bind(vector_store=vector_store),\n",
+ " human_converse=human_converse,\n",
+ " terminal=burr.core.Result(\"chat_history\"),\n",
+ " )\n",
+ " # set the transitions between actions\n",
+ " .with_transitions(\n",
+ " (\"ai_converse\", \"human_converse\", default),\n",
+ " (\"human_converse\", \"terminal\", expr(\"'exit' in question\")),\n",
+ " (\"human_converse\", \"ai_converse\", default),\n",
+ " )\n",
+ " # add identifiers that will help track the application\n",
+ " .with_identifiers(app_id=new_app_id, partition_key=\"sample_user\")\n",
+ " .initialize_from(\n",
+ " initializer=tracker,\n",
+ " resume_at_next_action=False, # we want to always start at human_converse; our entrypoint\n",
+ " default_entrypoint=\"human_converse\",\n",
+ " default_state=initial_state, # set some default state incase we can't find the prior state\n",
+ " fork_from_app_id=\"NO_SUCH_APP_ID\", # <---- The new addition \n",
+ " )\n",
+ " # add a hook to print the steps -- optional but shows that Burr is pluggable\n",
+ " .with_hooks(PrintStepHook())\n",
+ " # add tracking -- this will show up in the BURR UI.\n",
+ " .with_tracker(tracker)\n",
+ " # build the application\n",
+ " .build()\n",
+ ")"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.10.4"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/content/_sources/examples/data-science/index.rst.txt b/content/_sources/examples/data-science/index.rst.txt
new file mode 100644
index 00000000..327d91ac
--- /dev/null
+++ b/content/_sources/examples/data-science/index.rst.txt
@@ -0,0 +1,9 @@
+===============
+🧪 Data science
+===============
+
+.. toctree::
+ :maxdepth: 2
+
+ ml_training
+ simulation
diff --git a/content/_sources/examples/data-science/ml_training.md.txt b/content/_sources/examples/data-science/ml_training.md.txt
new file mode 100644
index 00000000..9ab034e9
--- /dev/null
+++ b/content/_sources/examples/data-science/ml_training.md.txt
@@ -0,0 +1,203 @@
+# Hyperparameter tuning
+
+Hyperparameter tuning is a key part of training machine learning models. For use-cases ranging from grid-search
+to gaussian processes, ``burr`` is a simple way to implement robust, failure-resistant hyperparameter optimization. This involves keeping
+track of hyperparameters and metrics and feeding these into a decision function to determine where to look next.
+``burr``'s state machine can help you write and manage that process.
+
+This is a WIP! Please see the placeholder/example sketch in the [repository](https://github.com/DAGWorks-Inc/burr/tree/main/examples/ml-training) and contribute back if you have ideas via associated issue [here](https://github.com/DAGWorks-Inc/burr/issues/138).
+
+## High-level view
+
+A machine learning training system can easily be modeled as a state machine.
+
+While often, this is more of a DAG (E.G. `query_data` -> `feature_engineer` -> `train_model` -> `evaluate_model`), there
+are some interesting reasons this as a state machine.
+
+## More granular training routines
+
+If, rather than treating `train` as a action, you treat `train_epoch` as an action, you can utilize the abstractions
+burr represents to get the following benefits:
+
+### Checkpointing
+
+You can store the current params + best model (or pointers to them). Then go back to the failure point. This allows you
+to recover from failure in a generic way.
+
+### Decoupling training logic from termination condition
+
+You can have a condition that checks training completeness based on metric history, which is different from the training logic itself.
+This allows you to easily swap in and out termination conditions (testing them against each other or running different ones in different scenarios),
+as well as easy reuse.
+
+### Visibility into lower-level training
+
+Similar to [hooks](https://lightning.ai/docs/pytorch/stable//extensions/callbacks.html) in pytorch lightning,
+you can use Burr hooks to log metrics, visualize, etc... at each step. This allows you to have a more granular view of training that updates live
+(obviuosly depending on the UI/model-monitoring you're using.)
+
+## Human in the loop
+
+While some models are trained in a single-shot and shipped to production, many require human input.
+Burr can be used to express training, then checkpoint/pause the state while a human is evaluating it,
+and have their input (e.g. go/no-go) passed in as an [input parameter](https://burr.dagworks.io/concepts/actions/#runtime-inputs).
+
+Note that this still requires a scheduling tool (say a task executor that runs until the next human input is needed),
+but that task executor does not need to be complicated (all it needs to do is run a job when prompted, and possibly on a chron schedule).
+
+## Hyperparameter training
+
+Similar to epoch training, hyperparameter training can be modeled as a state machine. The system makes a decision about what
+to look for next based on the prior results. Furthermore, one can model a step that handles multiple jobs, keeping them alive/
+launching new ones as they complete (based on state).
+
+.
+
+
+## Code example
+
+This uses the class-based API for defining an `action`.
+
+```python
+"""
+This is a sketch of an example of a machine learning training pipeline using Burr where you
+want to do something dynamic like train a model for a variable number of iterations and then
+pick the best model from each of the iterations based on some validation metric. E.g. hyperparameter search.
+
+You could adjust this example to continue training a model until you reach a certain validation metric,
+or hit a max number of iterations (or epochs), etc.
+
+Note: this example uses the class based API to define the actions. You could also use the function+decorator API.
+"""
+import burr.core.application
+from burr.core import Action, Condition, State, default
+
+
+class ProcessDataAction(Action):
+ @property
+ def reads(self) -> list[str]:
+ return ["data_path"]
+
+ def run(self, state: State, **run_kwargs) -> dict:
+ """This is where you would load and process your data.
+ You can take in state and reference object fields.
+
+ You need to return a dictionary of the results that `update()` will then use to update the state.
+ """
+ # load data, and run some feature engineering -- create the data sets
+ # TODO: fill in
+ return {"training_data": "SOME_OBJECT/PATH", "evaluation_data": "SOME_OBJECT/PATH"}
+
+ @property
+ def writes(self) -> list[str]:
+ return ["training_data", "evaluation_data"]
+
+ def update(self, result: dict, state: State) -> State:
+ return state.update(
+ training_data=result["training_data"], evaluation_data=result["evaluation_data"]
+ )
+
+
+class TrainModel(Action):
+ @property
+ def reads(self) -> list[str]:
+ return ["training_data", "iterations"]
+
+ def run(self, state: State, **run_kwargs) -> dict:
+ """This is where you'd build the model and on each iteration do something different.
+ E.g. hyperparameter search.
+ """
+ # train the model, i.e. run one "iteration" whatever that means for you.
+ # TODO: fill in
+ return {
+ "model": "SOME_MODEL",
+ "iterations": state["iterations"] + 1,
+ "metrics": "SOME_METRICS",
+ }
+
+ @property
+ def writes(self) -> list[str]:
+ return ["models", "training_metrics", "iterations"]
+
+ def update(self, result: dict, state: State) -> State:
+ return state.update(
+ iterations=result["iterations"], # set current iteration value
+ ).append(
+ models=result["model"], # append -- note this can get big if your model is big
+ # so you'll want to overwrite but store the conditions, or log somewhere
+ metrics=result["metrics"], # append the metrics
+ )
+
+
+class ValidateModel(Action):
+ @property
+ def reads(self) -> list[str]:
+ return ["models", "evaluation_data"]
+
+ def run(self, state: State, **run_kwargs) -> dict:
+ """Compute validation metrics using the model and evaluation data."""
+ # TODO: fill in
+ return {"validation_metrics": "SOME_METRICS"}
+
+ @property
+ def writes(self) -> list[str]:
+ return ["validation_metrics"]
+
+ def update(self, result: dict, state: State) -> State:
+ return state.append(validation_metrics=result["validation_metrics"])
+
+
+class BestModel(Action):
+ @property
+ def reads(self) -> list[str]:
+ return ["validation_metrics", "models"]
+
+ def run(self, state: State, **run_kwargs) -> dict:
+ """Select the best model based on the validation metrics."""
+ # TODO: fill in
+ return {"best_model": "SOME_MODEL"}
+
+ @property
+ def writes(self) -> list[str]:
+ return ["best_model"]
+
+ def update(self, result: dict, state: State) -> State:
+ return state.update(best_model=result["best_model"])
+
+
+def application(iterations: int) -> burr.core.application.Application:
+ return (
+ burr.core.ApplicationBuilder()
+ .with_state(
+ data_path="data.csv",
+ iterations=10,
+ training_data=None,
+ evaluation_data=None,
+ models=[],
+ training_metrics=[],
+ validation_metrics=[],
+ best_model=None,
+ )
+ .with_actions(
+ process_data=ProcessDataAction(),
+ train_model=TrainModel(),
+ validate_model=ValidateModel(),
+ best_model=BestModel(),
+ )
+ .with_transitions(
+ ("process_data", "train_model", default),
+ ("train_model", "validate_model", default),
+ ("validate_model", "best_model", Condition.expr(f"iterations>{iterations}")),
+ ("validate_model", "train_model", default),
+ )
+ .with_entrypoint("process_data")
+ .build()
+ )
+
+
+if __name__ == "__main__":
+ app = application(100) # doing good data science is up to you...
+ app.visualize(output_file_path="statemachine", include_conditions=True, view=True, format="png")
+ # you could run things like this:
+ # last_action, result, state = app.run(halt_after=["best_model"])
+```
diff --git a/content/_sources/examples/data-science/simulation.md.txt b/content/_sources/examples/data-science/simulation.md.txt
new file mode 100644
index 00000000..e9c789eb
--- /dev/null
+++ b/content/_sources/examples/data-science/simulation.md.txt
@@ -0,0 +1,151 @@
+# Simulations
+
+At a high level, simulations generally run over a set of time steps and maintain state.
+The user then manages the state, which becomes the input to the next time step, as well
+as output data to analyze. Burr provides a simple way for you to construct, manage, and
+introspect the state of your simulation.
+
+This example is a WIP -- see the placeholder/example sketch in the [repository](https://github.com/DAGWorks-Inc/burr/tree/main/examples/simulation).
+We're actively looking for contributors + ideas via [this issue](https://github.com/DAGWorks-Inc/burr/issues/136) to track.
+
+
+For instance:
+
+## Time-series Forecast
+A time-series forecast will execute a computation (say, feature-engineering + a model) at each step for which the user is predicting. The
+user will then store a history of data (predictions, raw data, etc...), and use that to pass into the next step in the simulation.
+
+The merge into state + query from state capabilities can be complicated -- windowed operations, rolling averages, etc.. may all be useful,
+and they may want to visualize simulations live (E.G. by tracking some metrics as it goes along). Burr is a natural way to persist this -- even
+if it is just a few actions (`query_data`, `feature_engineer`, `forecast`), the persistence capabilities + hooks can allow
+for logging to whatever live framework one wants to visualize, and centralizing the logic of reading from/writing to state.
+
+.
+
+
+## Portfolio Construction
+
+This is a special case of time-series forecasting, in which one wants to simulate a financial portfolio. Actions might be:
+- `query_data` - get data from state/load externally
+- `prepare_data` - format to something you can make predictions on
+- `forecast` - runs a model to do stock-price fore asting
+- `construct_portfolio` - uses the forecast to construct a portfolio
+- `evaluate_portfolio` - evaluates the portfolio
+
+Each one of these could be a DAG using [Hamilton](https://github.com/dagworks-inc/hamilton), or running any custom code.
+
+## Multi-agent simulation
+
+See [Stanford Smallville](https://hai.stanford.edu/news/computational-agents-exhibit-believable-humanlike-behavior) for an example.
+For multiple independent "agents", Burr could help model the way they interact. This could be multiplke Burr applications, applications called within
+actions, or an action that loops over all "users". We are still figuring out the best way to model this, so reach out if you have ideas!
+
+## Code example
+
+```python
+"""
+This module contains a stubbed out structure for one way of doing a simulation
+ for forecasting purposes.
+
+For more sophisticated forecasts where prediction of one timestep
+depends on the previous timestep, you need to forecast on a per-step basis. So
+you're effectively simulating the future at each step. This is a simple example of
+how you might structure such a simulation.
+"""
+
+from typing import Tuple
+
+from burr.core import Application, ApplicationBuilder, State, expr
+from burr.core.action import action
+
+
+@action(reads=["data_path"], writes=["data"])
+def prepare_data(state: State) -> Tuple[dict, State]:
+ """This would pull the data, prepare it, and return it."""
+ # pull data, prepare as necessary
+ result = {"data": _process_data(state["data_path"])}
+ return result, state.update(**result)
+
+
+@action(reads=["data", "simulation_start"], writes=["model"])
+def build_model(state: State) -> Tuple[dict, State]:
+ """This would fit the model on data before the simulation start date."""
+ training_data = state["data"]
+ model = _fit_model(training_data, upto=state["simulation_start"])
+ result = {"model": model}
+ return result, state.update(**result)
+
+
+@action(
+ reads=["simulation_start", "simulation_end", "model", "data", "current_timestep"],
+ writes=["data"],
+)
+def forecast(state: State) -> Tuple[dict, State]:
+ """This action forecasts the next timestep in the simulation and appends it to data."""
+ model = state["model"]
+ data = state["data"]
+ start_time = state["simulation_start"]
+ current_time_step = state.get("current_timestep", start_time)
+ next_timestep = _forecast_timestep(model, data, current_time_step)
+ data = _update_data(data, next_timestep)
+ result = {"data": data, "current_timestep": next_timestep}
+ return result, state.update(**result)
+
+
+@action(reads=["data"], writes=[])
+def terminate(state: State) -> Tuple[dict, State]:
+ """This is a terminal step that would save the forecast or do something else."""
+ return {}, state
+
+
+def application() -> Application:
+ app = (
+ ApplicationBuilder()
+ .with_actions(prepare_data, build_model, forecast, terminate)
+ .with_transitions(
+ ("prepare_data", "build_model"),
+ ("build_model", "forecast"),
+ ("forecast", "forecast", expr("current_timestep<simulation_end")),
+ ("forecast", "terminate", expr("current_timestep>=simulation_end")),
+ )
+ .with_state(
+ data_path="SOME_PATH",
+ simulation_start="some-value",
+ simulation_end="some-end-value",
+ )
+ .with_entrypoint("prepare_data")
+ .build()
+ )
+ return app
+
+
+# --- stubbed out functions to fill out for the simulation
+def _process_data(data_path: str) -> object:
+ """This function would read and process data from a file."""
+ return {}
+
+
+def _fit_model(data: object, upto: str) -> object:
+ """This function would fit a model on data upto a certain date."""
+ return {}
+
+
+def _forecast_timestep(model: object, data: object, current_timestep: str) -> object:
+ """This function would forecast the next timestep in the simulation given the model and data."""
+ return {}
+
+
+def _update_data(data: object, next_timestep: object) -> object:
+ """This function would update the data with the forecasted timestep."""
+ return {}
+
+
+if __name__ == "__main__":
+ _app = application()
+ _app.visualize(
+ output_file_path="statemachine", include_conditions=True, view=True, format="png"
+ )
+ # you could run things like this:
+ # last_action, result, state = _app.run(halt_after=["terminate"])
+
+```
diff --git a/content/_sources/examples/deployment/index.rst.txt b/content/_sources/examples/deployment/index.rst.txt
new file mode 100644
index 00000000..4bed667a
--- /dev/null
+++ b/content/_sources/examples/deployment/index.rst.txt
@@ -0,0 +1,23 @@
+=============
+✈ Deployment
+=============
+
+Burr is specifically meant to make getting your product in production easier and faster.
+This section covers examples of getting Burr to production, as well as a brief overview of approaches/requirements.
+
+To deploy a Burr application in production, you need to do three things:
+
+1. Place your Burr application in some place you can trigger it. E.g. a web-service, a script, a library, etc.
+2. Provision infrastructure to run (1)
+3. Monitor your application in production (highly recommended, but not required)
+
+Due to the large number of methods people have for deploying applications, we will not cover all of them here. That said,
+we really appreciate contributions! Please `open an issue <https://github.com/DAGWorks-Inc/burr/issues/new?assignees=&labels=&projects=&template=feature_request.md&title=>`_ if there's an example you'd like, and :ref:`contribute back <contributing>` if you
+have an example that would add to this guide. We have created a variety of issues with placeholders and link to them in the docs.
+
+.. toctree::
+ :maxdepth: 2
+
+ web-server
+ infrastructure
+ monitoring
diff --git a/content/_sources/examples/deployment/infrastructure.rst.txt b/content/_sources/examples/deployment/infrastructure.rst.txt
new file mode 100644
index 00000000..34dd2147
--- /dev/null
+++ b/content/_sources/examples/deployment/infrastructure.rst.txt
@@ -0,0 +1,16 @@
+-------------------------------------
+Provisioning Infrastructure/Deploying
+-------------------------------------
+
+Burr is not opinionated about the method of deployment/cloud one uses. Any method that can run python code, or web-service will work
+(AWS, vercel, etc...). Note we aim to have more examples here -- see `this issue <https://github.com/DAGWorks-Inc/burr/issues/390>`_ to track!
+
+- `Deploying Burr in an AWS lambda function <https://github.com/DAGWorks-Inc/burr/tree/main/examples/deployment/aws/lambda>`_
+- `Deploying Burr using BentoML <https://github.com/DAGWorks-Inc/burr/tree/main/examples/deployment/aws/bentoml>`_
+
+
+Using BentoML
+-------------
+`BentoML <https://github.com/bentoml/BentoML>`_ is a specialized tool to package, deploy, and manage AI services.
+For example, it allows you to create a REST API for your Burr application with minimal effort.
+See the `Burr + BentoML example <https://github.com/DAGWorks-Inc/burr/tree/main/examples/deployment/aws/bentoml>`_ for more information.
diff --git a/content/_sources/examples/deployment/monitoring.rst.txt b/content/_sources/examples/deployment/monitoring.rst.txt
new file mode 100644
index 00000000..4138b6af
--- /dev/null
+++ b/content/_sources/examples/deployment/monitoring.rst.txt
@@ -0,0 +1,24 @@
+------------------------
+Monitoring in Production
+------------------------
+
+Burr's telemetry UI is meant both for debugging and running in production. It can consume `OpenTelemetry traces <https://burr.dagworks.io/reference/integrations/opentelemetry/>`_,
+and has a suite of useful capabilities for debugging Burr applications.
+
+It has two (current) implementations:
+
+1. `Local (filesystem) tracking <https://burr.dagworks.io/concepts/tracking/>`_ (default, for debugging or lower-scale production use-cases with a distributed file-system)
+2. `S3-based tracking <https://github.com/DAGWorks-Inc/burr/blob/main/burr/tracking/server/s3/README.md>`_ (meant for production use-cases)
+
+Which each come with an implementation of data storage on the server.
+
+To deploy these in production, you can follow the following examples:
+
+1. `Burr + FastAPI + docker <https://github.com/mdrideout/burr-fastapi-docker-compose>`_ by `Matthew Rideout <https://github.com/mdrideout>`_. This contains a sample API + UI + tracking server all bundled in one!
+2. `Docker compose + nginx proxy <https://github.com/DAGWorks-Inc/burr/tree/main/examples/email-assistant#running-the-ui-with-email-server-backend-in-a-docker-container>`_ by `Aditha Kaushik <https://github.com/97k>`_ for the email assistant example, demonstrates running the docker image with the tracking server.
+
+We also have a few issues to document deploying Burr's monitoring system in production:
+
+- `deploy on AWS <https://github.com/DAGWorks-Inc/burr/issues/391>`_
+- `deploy on GCP <https://github.com/DAGWorks-Inc/burr/issues/392>`_
+- `deploy on Azure <https://github.com/DAGWorks-Inc/burr/issues/393>`_
diff --git a/content/_sources/examples/deployment/web-server.rst.txt b/content/_sources/examples/deployment/web-server.rst.txt
new file mode 100644
index 00000000..dd07ec5f
--- /dev/null
+++ b/content/_sources/examples/deployment/web-server.rst.txt
@@ -0,0 +1,25 @@
+--------------------
+Burr in a web server
+--------------------
+
+We like `fastAPI <https://fastapi.tiangolo.com/>`_, but Burr can work with any python-friendly server framework
+(`django <https://www.djangoproject.com>`_, `flask <https://flask.palletsprojects.com/>`_, etc...).
+
+To run Burr in a FastAPI server, see the following examples:
+
+- `Human in the loop FastAPI server <https://github.com/DAGWorks-Inc/burr/tree/main/examples/web-server>`_ (`TDS blog post <https://towardsdatascience.com/building-an-email-assistant-application-with-burr-324bc34c547d>`__ )
+- `OpenAI-compatible agent with FastAPI <https://github.com/DAGWorks-Inc/burr/tree/main/examples/openai-compatible-agent>`_
+- `Streaming server using SSE + FastAPI <https://github.com/DAGWorks-Inc/burr/tree/main/examples/streaming-fastapi>`_ (`TDS blog post <https://towardsdatascience.com/how-to-build-a-streaming-agent-with-burr-fastapi-and-react-e2459ef527a8>`__ )
+- `Use typed state with Pydantic + FastAPI <https://github.com/DAGWorks-Inc/burr/tree/main/examples/typed-state>`_
+- `Burr + FastAPI + docker <https://github.com/mdrideout/burr-fastapi-docker-compose>`_ by `Matthew Rideout <https://github.com/mdrideout>`_. This contains a sample web server API + UI + tracking server all bundled in one!
+- `Docker compose + nginx proxy <https://github.com/DAGWorks-Inc/burr/tree/main/examples/email-assistant#running-the-ui-with-email-server-backend-in-a-docker-container>`_ by `Aditha Kaushik <https://github.com/97k>`_ for the email assistant example, demonstrates running the docker image with the tracking server.
+- `BentoML + Burr <https://github.com/DAGWorks-Inc/burr/tree/main/examples/deployment/aws/bentoml>`_ for deploying Burr with BentoML as a web-service.
+
+Connecting to a database
+------------------------
+
+To connect Burr to a database, you can use one of the provided persisters, or build your own:
+
+- :ref:`Documentation on persistence <state-persistence>`
+- :ref:`Set of available persisters <persistersref>`
+- `Simple chatbot intro with persistence to SQLLite <https://github.com/DAGWorks-Inc/burr/blob/main/examples/simple-chatbot-intro/notebook.ipynb>`_
diff --git a/content/_sources/examples/guardrails/creating_tests.rst.txt b/content/_sources/examples/guardrails/creating_tests.rst.txt
new file mode 100644
index 00000000..def1d89d
--- /dev/null
+++ b/content/_sources/examples/guardrails/creating_tests.rst.txt
@@ -0,0 +1,73 @@
+====================
+Creating Test Cases
+====================
+
+With Burr tracking state is part of the framework. This means creating a realistic test case
+for an action involves turning a persister or tracker on and then pulling that state out
+for a test case. The following example demonstrates how to create a test case
+using the `burr-test-case` command.
+
+Note: writing test cases for GenAI projects can be tricky. The same LLM API calls
+can result in different outputs. This means that 'exact' equality tests may not work,
+and you'll need to resort to more fuzzy tests like checking for the presence of certain
+words or phrases, or using LLMs to grade the output, etc. We aren't opinionated on how you
+do this, but in any case, you'll need to write a test case to exercise things, and this
+is what we're showing you how to do here.
+
+Need to know more about pytest?
+-------------------------------
+For a more pytest walkthrough and example, see the `pytest example <https://github.com/DAGWorks-Inc/burr/tree/main/examples/pytest>`_,
+that explains what pytest is, how to evaluate more than just a single assert statement, how to aggregate results, etc.
+
+
+Test Case Creation Example
+--------------------------
+Video walkthrough:
+
+.. raw:: html
+
+ <div>
+ <iframe width="800" height="455" src="https://www.youtube.com/embed/9U_CMsh0VBI?si=Z-powULn_RO2-2pB" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
+ </div>
+
+
+Steps:
+
+1. You have built some part of your Burr application.
+2. You have found some state you want to test / iterate on.
+3. Note the project name, partition key, app id, and sequence id for the state you want to test.
+ See orange lines indicating what to pull from the UI in the image below.
+
+ .. image:: _creating_tests.png
+ :alt: What you need to pull from the UI for running the command.
+ :align: center
+
+4. Run the following command & cut and paste the test to a test file. This will create a `pytest` test.
+5. Adjust the test as needed, e.g. what you're validating or asserting and how.
+
+.. code-block:: bash
+
+ burr-test-case create \
+ --project-name "SOME_NAME" \
+ --partition-key "SOME_KEY" \
+ --app-id "SOME_ID" \
+ --sequence-id 0 \
+ --target-file-name /tmp/test-case.json
+
+See `github repository example <https://github.com/DAGWorks-Inc/burr/tree/main/examples/test-case-creation>`_
+for an example.
+
+Note (1): if you have custom serialization/deserialization logic, you will want to pass in `--serde-module` to the
+test case with the module name of your serialization logic.
+
+Note (2): you can pass in `--action-name` to override the action name in the test case. This is useful if you want
+to use the output of one action as the input to another action; there are corner cases where this is useful.
+
+
+Future Work
+-----------
+We see many more improvements here:
+
+1. Automatically suggesting tests cases for you to add.
+2. Data export / integration with evaluation tools.
+3. etc. Please let us know what you need!
diff --git a/content/_sources/examples/guardrails/index.rst.txt b/content/_sources/examples/guardrails/index.rst.txt
new file mode 100644
index 00000000..c577fd0a
--- /dev/null
+++ b/content/_sources/examples/guardrails/index.rst.txt
@@ -0,0 +1,8 @@
+=======================
+🚧 Guardrails / Tests
+=======================
+
+.. toctree::
+ :maxdepth: 2
+
+ creating_tests
diff --git a/content/_sources/examples/index.rst.txt b/content/_sources/examples/index.rst.txt
new file mode 100644
index 00000000..ed9061bf
--- /dev/null
+++ b/content/_sources/examples/index.rst.txt
@@ -0,0 +1,20 @@
+========
+Cookbook
+========
+
+.. _examples:
+
+These are still under progress -- see the github `examples directory <https://github.com/dagworks-inc/burr/blob/main/examples>`_
+for the latest.
+
+Examples of more complex/powerful use-cases of Burr. Download/copy these to adapt to your use-cases.
+
+.. toctree::
+ :maxdepth: 2
+
+ simple/index
+ chatbots/index
+ agents/index
+ deployment/index
+ guardrails/index
+ data-science/index
diff --git a/content/_sources/examples/simple/choose-your-own-adventure.ipynb.txt b/content/_sources/examples/simple/choose-your-own-adventure.ipynb.txt
new file mode 100644
index 00000000..78aab4f8
--- /dev/null
+++ b/content/_sources/examples/simple/choose-your-own-adventure.ipynb.txt
@@ -0,0 +1,315 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Choose your own adventure\n",
+ "\n",
+ "A simple text-based game in the style of 80s adventure games in which you play a hungry corgi."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Setup"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-12-13T21:54:32.774619Z",
+ "start_time": "2024-12-13T21:54:31.058878Z"
+ }
+ },
+ "source": [
+ "import json\n",
+ "from typing import Optional, Tuple\n",
+ "\n",
+ "from burr.core import action, Application, ApplicationBuilder, State, default, when\n",
+ "from openai import OpenAI"
+ ],
+ "outputs": [],
+ "execution_count": 1
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# set your OpenAI API key to run the rest of the notebook\n",
+ "import os\n",
+ "os.environ[\"OPENAI_API_KEY\"] = ..."
+ ]
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-12-13T21:59:57.226911Z",
+ "start_time": "2024-12-13T21:59:57.223370Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "RESTRICTIONS = \"\"\"You're a small corgi with short legs. You can't jump high,\n",
+ "you can't run fast, you can't perform feats of athleticism in general\n",
+ "to achieve any of your goals. You can't open doors, you can't use tools,\n",
+ "you can't communicate with humans, you can't use your paws to manipulate\n",
+ "objects, you can't use your mouth to communicate with humans\"\"\""
+ ],
+ "outputs": [],
+ "execution_count": 11
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Define actions"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-12-13T21:59:58.536899Z",
+ "start_time": "2024-12-13T21:59:58.527089Z"
+ }
+ },
+ "source": [
+ "\n",
+ "@action(reads=[\"challenges\"], writes=[\"current_challenge\"])\n",
+ "def start(state: State) -> State:\n",
+ " \"\"\"Load the first challenge as the current challenge\"\"\"\n",
+ " current_challenge = state[\"challenges\"][0]\n",
+ " return state.update(current_challenge=current_challenge)\n",
+ "\n",
+ "\n",
+ "@action(reads=[\"current_challenge\"], writes=[\"attempts\"])\n",
+ "def prompt_for_challenge(state: State) -> State:\n",
+ " \"\"\"Request user input to solve the current challenge\"\"\"\n",
+ " current_challenge = state[\"current_challenge\"]\n",
+ " user_response = input(f'{current_challenge}. What do you do?\\n $ ')\n",
+ " return state.append(attempts=user_response)\n",
+ "\n",
+ "\n",
+ "@action(\n",
+ " reads=[\"attempts\", \"current_challenge\"],\n",
+ " writes=[\"challenge_solved\", \"what_happened\"],\n",
+ ")\n",
+ "def evaluate_attempt(state: State) -> State:\n",
+ " response = OpenAI().chat.completions.create(\n",
+ " model=\"gpt-4o-mini\",\n",
+ " messages=[\n",
+ " {\n",
+ " \"role\": \"system\",\n",
+ " \"content\": f\"\"\"You are evaluating responses for\n",
+ " whether they constitute solutions to the provided challenge in a text\n",
+ " based game, whose protagonist is a dog subject to the following limitations:\n",
+ " {RESTRICTIONS}. You respond ONLY with a json object containing two fields: \"solved\", which is a\n",
+ " boolean indicating whether the challenge was solved by the attempt, and \"what_happened\",\n",
+ " which is a string containing a brief narrative, written in the second person and addressed\n",
+ " to the player, of what happened during the protagonist's attempt\"\"\",\n",
+ " },\n",
+ " {\n",
+ " \"role\": \"user\",\n",
+ " \"content\": f\"The current challenge is: {state['current_challenge']} \"\n",
+ " f\"and the player's attempt is: {state['attempts'][-1]}\",\n",
+ " },\n",
+ " ],\n",
+ " )\n",
+ " content = response.choices[0].message.content\n",
+ " try:\n",
+ " json_result = json.loads(content)\n",
+ " except json.JSONDecodeError:\n",
+ " print(\"bad json: \", content)\n",
+ " json_result = {\n",
+ " \"solved\": False,\n",
+ " \"what_happened\": \"Not sure, really. I'm a dog. I can't read json. I can't read at all.\",\n",
+ " }\n",
+ "\n",
+ " return state.update(\n",
+ " challenge_solved=json_result[\"solved\"],\n",
+ " what_happened=json_result[\"what_happened\"],\n",
+ " )\n",
+ "\n",
+ "\n",
+ "@action(\n",
+ " reads=[\"challenge_solved\", \"current_challenge\", \"what_happened\"],\n",
+ " writes=[\"current_challenge\", \"did_win\"],\n",
+ ")\n",
+ "def maybe_progress(state: State) -> State:\n",
+ " print(\"What happened:\", state[\"what_happened\"])\n",
+ " if state[\"challenge_solved\"]:\n",
+ " if state[\"current_challenge\"] == challenges[-1]:\n",
+ " result = {\"did_win\": True}\n",
+ " else:\n",
+ " result = {\n",
+ " \"current_challenge\": challenges[challenges.index(state[\"current_challenge\"]) + 1]\n",
+ " }\n",
+ " else:\n",
+ " result = {\"current_challenge\": state[\"current_challenge\"]}\n",
+ " return state.update(**result)\n",
+ "\n",
+ "\n",
+ "@action(reads=[\"challenges\"], writes=[])\n",
+ "def win(state: State) -> State:\n",
+ " # get summary of actions taken from openai\n",
+ " print(\"you won\")\n",
+ " return state\n"
+ ],
+ "outputs": [],
+ "execution_count": 12
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-12-13T21:59:59.217332Z",
+ "start_time": "2024-12-13T21:59:59.214329Z"
+ }
+ },
+ "source": [
+ "challenges = [\n",
+ " \"There is a dish of dog food on the floor. You want to eat it\",\n",
+ " \"There is a dish of dog food on a table. You want to eat it\",\n",
+ " \"There is a dish of dog food in a locked car. You want to eat it\",\n",
+ "]"
+ ],
+ "outputs": [],
+ "execution_count": 13
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-12-13T22:00:01.429752Z",
+ "start_time": "2024-12-13T22:00:00.812349Z"
+ }
+ },
+ "source": [
+ "def build_application(\n",
+ " app_id: Optional[str] = None,\n",
+ " storage_dir: Optional[str] = \"~/.burr\",\n",
+ ") -> Application:\n",
+ " return (\n",
+ " ApplicationBuilder()\n",
+ " .with_state(did_win=False, challenges=challenges)\n",
+ " .with_actions(\n",
+ " start=start,\n",
+ " prompt_for_challenge=prompt_for_challenge,\n",
+ " evaluate_attempt=evaluate_attempt,\n",
+ " maybe_progress=maybe_progress,\n",
+ " win=win,\n",
+ " )\n",
+ " .with_transitions(\n",
+ " (\"start\", \"prompt_for_challenge\", default),\n",
+ " (\"prompt_for_challenge\", \"evaluate_attempt\", default),\n",
+ " (\"evaluate_attempt\", \"maybe_progress\", default),\n",
+ " (\"maybe_progress\", \"win\", when(did_win=True)),\n",
+ " (\"maybe_progress\", \"prompt_for_challenge\", default),\n",
+ " )\n",
+ " .with_entrypoint(\"start\")\n",
+ " .with_tracker(project=\"demo_corgi_adventure\", params={\"storage_dir\": storage_dir})\n",
+ " .with_identifiers(app_id=app_id)\n",
+ " .build()\n",
+ " )\n",
+ "\n",
+ "app = build_application()\n",
+ "app.visualize(output_file_path=\"digraph\", include_conditions=True, view=False, format=\"png\")"
+ ],
+ "outputs": [
+ {
+ "data": {
+ "image/svg+xml": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n<!-- Generated by graphviz version 12.0.0 (20240704.0754)\n -->\n<!-- Pages: 1 -->\n<svg width=\"194pt\" height=\"331pt\"\n viewBox=\"0.00 0.00 193.93 330.50\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) [...]
+ "text/plain": [
+ "<graphviz.graphs.Digraph at 0x1264f80a0>"
+ ]
+ },
+ "execution_count": 14,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 14
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-12-13T22:01:14.166318Z",
+ "start_time": "2024-12-13T22:00:01.585581Z"
+ }
+ },
+ "source": [
+ "action, state, result = app.run(halt_after=[\"win\"])"
+ ],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "What happened: You opened your mouth and attempted to eat the food, but without the ability to manipulate the dish, you couldn’t reach the food. It remained untouched on the floor, taunting you with its delicious aroma.\n",
+ "What happened: You tugged at the dish with your mouth, but your short legs can't provide enough strength to pull it any closer. The dish remains out of reach, and your belly rumbles in disappointment as you realize you can't manipulate it like you hoped.\n",
+ "What happened: You approach the dish of dog food with eager anticipation, but as you try to lean over and take a bite, your short legs make it difficult to reach the food comfortably. You can't quite get your mouth to the dish, and after a few attempts, you realize you'll need a different plan to enjoy your meal.\n",
+ "What happened: You try to bite the side of the dish to tip it over, but your short legs and limited strength make it impossible. The dish remains firmly on the floor, and the tantalizing smell of the dog food wafts up to you, teasing you further.\n"
+ ]
+ },
+ {
+ "ename": "KeyboardInterrupt",
+ "evalue": "Interrupted by user",
+ "output_type": "error",
+ "traceback": [
+ "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m",
+ "\u001B[0;31mKeyboardInterrupt\u001B[0m Traceback (most recent call last)",
+ "Cell \u001B[0;32mIn[15], line 1\u001B[0m\n\u001B[0;32m----> 1\u001B[0m action, state, result \u001B[38;5;241m=\u001B[39m \u001B[43mapp\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mrun\u001B[49m\u001B[43m(\u001B[49m\u001B[43mhalt_after\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43m[\u001B[49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[38;5;124;43mwin\u001B[39;49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[43m]\u001B[49m\u001B[43m)\u001B[49m\n",
+ "File \u001B[0;32m~/dagworks/burr/burr/telemetry.py:276\u001B[0m, in \u001B[0;36mcapture_function_usage.<locals>.wrapped_fn\u001B[0;34m(*args, **kwargs)\u001B[0m\n\u001B[1;32m 273\u001B[0m \u001B[38;5;129m@functools\u001B[39m\u001B[38;5;241m.\u001B[39mwraps(call_fn)\n\u001B[1;32m 274\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21mwrapped_fn\u001B[39m(\u001B[38;5;241m*\u001B[39margs, \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mkwargs):\n\u001B[1;32m 27 [...]
+ "File \u001B[0;32m~/dagworks/burr/burr/core/application.py:672\u001B[0m, in \u001B[0;36m_call_execute_method_pre_post.__call__.<locals>.wrapper_sync\u001B[0;34m(app_self, *args, **kwargs)\u001B[0m\n\u001B[1;32m 670\u001B[0m exc \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;01mNone\u001B[39;00m\n\u001B[1;32m 671\u001B[0m \u001B[38;5;28;01mtry\u001B[39;00m:\n\u001B[0;32m--> 672\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43mfn\u001B[49m\u001B[43m(\u001B[49m\u001B[43m [...]
+ "File \u001B[0;32m~/dagworks/burr/burr/core/application.py:1243\u001B[0m, in \u001B[0;36mApplication.run\u001B[0;34m(self, halt_before, halt_after, inputs)\u001B[0m\n\u001B[1;32m 1241\u001B[0m \u001B[38;5;28;01mwhile\u001B[39;00m \u001B[38;5;28;01mTrue\u001B[39;00m:\n\u001B[1;32m 1242\u001B[0m \u001B[38;5;28;01mtry\u001B[39;00m:\n\u001B[0;32m-> 1243\u001B[0m \u001B[38;5;28;43mnext\u001B[39;49m\u001B[43m(\u001B[49m\u001B[43mgen\u001B[49m\u001B[43m)\u001B[49m\n\u001B[ [...]
+ "File \u001B[0;32m~/dagworks/burr/burr/core/application.py:1186\u001B[0m, in \u001B[0;36mApplication.iterate\u001B[0;34m(self, halt_before, halt_after, inputs)\u001B[0m\n\u001B[1;32m 1183\u001B[0m prior_action: Optional[Action] \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;01mNone\u001B[39;00m\n\u001B[1;32m 1184\u001B[0m \u001B[38;5;28;01mwhile\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mhas_next_action():\n\u001B[1;32m 1185\u001B[0m \u001B[38;5;66;0 [...]
+ "File \u001B[0;32m~/dagworks/burr/burr/core/application.py:672\u001B[0m, in \u001B[0;36m_call_execute_method_pre_post.__call__.<locals>.wrapper_sync\u001B[0;34m(app_self, *args, **kwargs)\u001B[0m\n\u001B[1;32m 670\u001B[0m exc \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;01mNone\u001B[39;00m\n\u001B[1;32m 671\u001B[0m \u001B[38;5;28;01mtry\u001B[39;00m:\n\u001B[0;32m--> 672\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43mfn\u001B[49m\u001B[43m(\u001B[49m\u001B[43m [...]
+ "File \u001B[0;32m~/dagworks/burr/burr/core/application.py:843\u001B[0m, in \u001B[0;36mApplication.step\u001B[0;34m(self, inputs)\u001B[0m\n\u001B[1;32m 840\u001B[0m \u001B[38;5;66;03m# we need to increment the sequence before we start computing\u001B[39;00m\n\u001B[1;32m 841\u001B[0m \u001B[38;5;66;03m# that way if we're replaying from state, we don't get stuck\u001B[39;00m\n\u001B[1;32m 842\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_increment_sequ [...]
+ "File \u001B[0;32m~/dagworks/burr/burr/core/application.py:885\u001B[0m, in \u001B[0;36mApplication._step\u001B[0;34m(self, inputs, _run_hooks)\u001B[0m\n\u001B[1;32m 883\u001B[0m \u001B[38;5;28;01mtry\u001B[39;00m:\n\u001B[1;32m 884\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m next_action\u001B[38;5;241m.\u001B[39msingle_step:\n\u001B[0;32m--> 885\u001B[0m result, new_state \u001B[38;5;241m=\u001B[39m \u001B[43m_run_single_step_action\u001B[49m\u001B[43m(\u001B[49m [...]
+ "File \u001B[0;32m~/dagworks/burr/burr/core/application.py:287\u001B[0m, in \u001B[0;36m_run_single_step_action\u001B[0;34m(action, state, inputs)\u001B[0m\n\u001B[1;32m 284\u001B[0m \u001B[38;5;66;03m# TODO -- guard all reads/writes with a subset of the state\u001B[39;00m\n\u001B[1;32m 285\u001B[0m action\u001B[38;5;241m.\u001B[39mvalidate_inputs(inputs)\n\u001B[1;32m 286\u001B[0m result, new_state \u001B[38;5;241m=\u001B[39m _adjust_single_step_output(\n\u001B[0;32m--> 2 [...]
+ "File \u001B[0;32m~/dagworks/burr/burr/core/action.py:655\u001B[0m, in \u001B[0;36mFunctionBasedAction.run_and_update\u001B[0;34m(self, state, **run_kwargs)\u001B[0m\n\u001B[1;32m 654\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21mrun_and_update\u001B[39m(\u001B[38;5;28mself\u001B[39m, state: State, \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mrun_kwargs) \u001B[38;5;241m-\u001B[39m\u001B[38;5;241m>\u001B[39m \u001B[38;5;28mtuple\u001B[39m[\u001B[38;5;28mdic [...]
+ "Cell \u001B[0;32mIn[12], line 12\u001B[0m, in \u001B[0;36mprompt_for_challenge\u001B[0;34m(state)\u001B[0m\n\u001B[1;32m 10\u001B[0m \u001B[38;5;250m\u001B[39m\u001B[38;5;124;03m\"\"\"Request user input to solve the current challenge\"\"\"\u001B[39;00m\n\u001B[1;32m 11\u001B[0m current_challenge \u001B[38;5;241m=\u001B[39m state[\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mcurrent_challenge\u001B[39m\u001B[38;5;124m\"\u001B[39m]\n\u001B[0;32m---> 12\u001B[0m user_response \ [...]
+ "File \u001B[0;32m~/.pyenv/versions/burr-py310/lib/python3.10/site-packages/ipykernel/kernelbase.py:1270\u001B[0m, in \u001B[0;36mKernel.raw_input\u001B[0;34m(self, prompt)\u001B[0m\n\u001B[1;32m 1268\u001B[0m msg \u001B[38;5;241m=\u001B[39m \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mraw_input was called, but this frontend does not support input requests.\u001B[39m\u001B[38;5;124m\"\u001B[39m\n\u001B[1;32m 1269\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m StdinNotImpl [...]
+ "File \u001B[0;32m~/.pyenv/versions/burr-py310/lib/python3.10/site-packages/ipykernel/kernelbase.py:1313\u001B[0m, in \u001B[0;36mKernel._input_request\u001B[0;34m(self, prompt, ident, parent, password)\u001B[0m\n\u001B[1;32m 1310\u001B[0m \u001B[38;5;28;01mexcept\u001B[39;00m \u001B[38;5;167;01mKeyboardInterrupt\u001B[39;00m:\n\u001B[1;32m 1311\u001B[0m \u001B[38;5;66;03m# re-raise KeyboardInterrupt, to truncate traceback\u001B[39;00m\n\u001B[1;32m 1312\u001B[0m msg [...]
+ "\u001B[0;31mKeyboardInterrupt\u001B[0m: Interrupted by user"
+ ]
+ }
+ ],
+ "execution_count": 15
+ },
+ {
+ "metadata": {},
+ "cell_type": "code",
+ "outputs": [],
+ "execution_count": null,
+ "source": ""
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": ".venv",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.10.9"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/content/_sources/examples/simple/counter.ipynb.txt b/content/_sources/examples/simple/counter.ipynb.txt
new file mode 100644
index 00000000..838940dc
--- /dev/null
+++ b/content/_sources/examples/simple/counter.ipynb.txt
@@ -0,0 +1,231 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "49dab42f",
+ "metadata": {},
+ "source": [
+ "# Counter\n",
+ "\n",
+ "Let's build a simple counter using Burr. When the counter terminates, print the count and the current time."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "84328dc0",
+ "metadata": {},
+ "source": [
+ "## Imports"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "initial_id",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-03-27T00:11:36.093767Z",
+ "start_time": "2024-03-27T00:11:36.088301Z"
+ },
+ "collapsed": true,
+ "jupyter": {
+ "outputs_hidden": true
+ }
+ },
+ "outputs": [],
+ "source": [
+ "import datetime\n",
+ "\n",
+ "from burr.core import action, State, ApplicationBuilder, expr"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "396c5e0c",
+ "metadata": {},
+ "source": [
+ "## Define actions\n",
+ "We define the function `increment()` that has a `State` as input and outputs a new `State`. The decorator `@action` specifies it's a Burr action and it will need to read and write from the `counter` attribute in `State`. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "64c67b15363653e9",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-03-27T00:11:36.548223Z",
+ "start_time": "2024-03-27T00:11:36.539358Z"
+ },
+ "collapsed": false,
+ "jupyter": {
+ "outputs_hidden": false
+ }
+ },
+ "outputs": [],
+ "source": [
+ "@action(reads=[\"counter\"], writes=[\"counter\"])\n",
+ "def increment(state: State) -> State:\n",
+ " \"\"\"Increment the counter by 1\"\"\"\n",
+ " \n",
+ " # get the value from the `state`\n",
+ " current_count = state[\"counter\"]\n",
+ " current_count += 1\n",
+ "\n",
+ " print(\"Count: \", current_count)\n",
+ "\n",
+ " # use `.update()` to create a new `State`\n",
+ " return state.update(counter=current_count)\n",
+ "\n",
+ "\n",
+ "@action(reads=[\"counter\"], writes=[])\n",
+ "def exit_counter(state: State) -> State:\n",
+ " \"\"\"Print the current count and the current time\"\"\"\n",
+ " current_count = state[\"counter\"]\n",
+ "\n",
+ " print(f\"Finished counting to {current_count} at {datetime.datetime.now():%H:%M:%S %Y-%m-%d}\")\n",
+ "\n",
+ " return state"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "197106df",
+ "metadata": {},
+ "source": [
+ "## Build the `Application`\n",
+ "We pass the action to the `ApplicationBuilder` via `.with_actions()`. Then, we specify the valid transitions between actions: `increment -> increment` and `increment -> exit_counter`. The `expr`ession construct specifies to use the transition `increment -> increment` whenever the `counter` value in state is under ten. Then, default to `increment -> exit_counter` to terminate the application. We also use `.with_entrypoint()` to define where to start and `.with_state()` to set the sta [...]
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "72ca90a1",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/svg+xml": [
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
+ "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
+ " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
+ "<!-- Generated by graphviz version 2.43.0 (0)\n",
+ " -->\n",
+ "<!-- Title: %3 Pages: 1 -->\n",
+ "<svg width=\"220pt\" height=\"113pt\"\n",
+ " viewBox=\"0.00 0.00 220.00 113.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
+ "<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 109)\">\n",
+ "<title>%3</title>\n",
+ "<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-109 216,-109 216,4 -4,4\"/>\n",
+ "<!-- increment -->\n",
+ "<g id=\"node1\" class=\"node\">\n",
+ "<title>increment</title>\n",
+ "<path fill=\"#b4d8e4\" stroke=\"black\" d=\"M88,-105C88,-105 20,-105 20,-105 14,-105 8,-99 8,-93 8,-93 8,-80 8,-80 8,-74 14,-68 20,-68 20,-68 88,-68 88,-68 94,-68 100,-74 100,-80 100,-80 100,-93 100,-93 100,-99 94,-105 88,-105\"/>\n",
+ "<text text-anchor=\"middle\" x=\"54\" y=\"-82.8\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">increment</text>\n",
+ "</g>\n",
+ "<!-- increment->increment -->\n",
+ "<g id=\"edge1\" class=\"edge\">\n",
+ "<title>increment->increment</title>\n",
+ "<path fill=\"none\" stroke=\"black\" stroke-dasharray=\"5,2\" d=\"M100.02,-92.72C110.41,-92.16 118,-90.09 118,-86.5 118,-84.26 115.03,-82.61 110.27,-81.55\"/>\n",
+ "<polygon fill=\"black\" stroke=\"black\" points=\"110.38,-78.04 100.02,-80.28 109.52,-84.98 110.38,-78.04\"/>\n",
+ "<text text-anchor=\"middle\" x=\"165\" y=\"-82.8\" font-family=\"Times,serif\" font-size=\"14.00\">counter < 10</text>\n",
+ "</g>\n",
+ "<!-- exit_counter -->\n",
+ "<g id=\"node2\" class=\"node\">\n",
+ "<title>exit_counter</title>\n",
+ "<path fill=\"#b4d8e4\" stroke=\"black\" d=\"M96,-37C96,-37 12,-37 12,-37 6,-37 0,-31 0,-25 0,-25 0,-12 0,-12 0,-6 6,0 12,0 12,0 96,0 96,0 102,0 108,-6 108,-12 108,-12 108,-25 108,-25 108,-31 102,-37 96,-37\"/>\n",
+ "<text text-anchor=\"middle\" x=\"54\" y=\"-14.8\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">exit_counter</text>\n",
+ "</g>\n",
+ "<!-- increment->exit_counter -->\n",
+ "<g id=\"edge2\" class=\"edge\">\n",
+ "<title>increment->exit_counter</title>\n",
+ "<path fill=\"none\" stroke=\"black\" d=\"M54,-67.81C54,-61.6 54,-54.45 54,-47.6\"/>\n",
+ "<polygon fill=\"black\" stroke=\"black\" points=\"57.5,-47.24 54,-37.24 50.5,-47.24 57.5,-47.24\"/>\n",
+ "</g>\n",
+ "</g>\n",
+ "</svg>\n"
+ ],
+ "text/plain": [
+ "<graphviz.graphs.Digraph at 0x7f2886e3ead0>"
+ ]
+ },
+ "execution_count": 3,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "app = (\n",
+ " ApplicationBuilder()\n",
+ " .with_actions(increment, exit_counter)\n",
+ " .with_transitions(\n",
+ " (\"increment\", \"increment\", expr(\"counter < 10\")),\n",
+ " (\"increment\", \"exit_counter\"),\n",
+ " )\n",
+ " .with_state(counter=0)\n",
+ " .with_entrypoint(\"increment\")\n",
+ " .build()\n",
+ ")\n",
+ "app.visualize(include_conditions=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "a63802fa10f73ca8",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-03-27T00:11:38.483852Z",
+ "start_time": "2024-03-27T00:11:38.463109Z"
+ },
+ "collapsed": false,
+ "jupyter": {
+ "outputs_hidden": false
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Count: 1\n",
+ "Count: 2\n",
+ "Count: 3\n",
+ "Count: 4\n",
+ "Count: 5\n",
+ "Count: 6\n",
+ "Count: 7\n",
+ "Count: 8\n",
+ "Count: 9\n",
+ "Count: 10\n",
+ "Finished counting to 10 at 18:44:29 2024-08-26\n"
+ ]
+ }
+ ],
+ "source": [
+ "action_obj, result, state = app.run(halt_after=[\"exit_counter\"])"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.10.9"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/content/_sources/examples/simple/cowsay.ipynb.txt b/content/_sources/examples/simple/cowsay.ipynb.txt
new file mode 100644
index 00000000..a65cb543
--- /dev/null
+++ b/content/_sources/examples/simple/cowsay.ipynb.txt
@@ -0,0 +1,281 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "23493882",
+ "metadata": {},
+ "source": [
+ "# Cowsay"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "initial_id",
+ "metadata": {
+ "collapsed": true,
+ "jupyter": {
+ "outputs_hidden": true
+ },
+ "ExecuteTime": {
+ "end_time": "2024-12-13T21:01:40.623161Z",
+ "start_time": "2024-12-13T21:01:39.314706Z"
+ }
+ },
+ "source": [
+ "import random\n",
+ "import time\n",
+ "from typing import Tuple\n",
+ "\n",
+ "import cowsay\n",
+ "\n",
+ "from burr.core import Action, Application, ApplicationBuilder, State, default, expr\n",
+ "from burr.core.action import action\n",
+ "from burr.lifecycle import PostRunStepHook"
+ ],
+ "outputs": [],
+ "execution_count": 1
+ },
+ {
+ "cell_type": "code",
+ "id": "9f5b9bfc78851a59",
+ "metadata": {
+ "collapsed": false,
+ "jupyter": {
+ "outputs_hidden": false
+ },
+ "ExecuteTime": {
+ "end_time": "2024-12-13T21:01:40.637573Z",
+ "start_time": "2024-12-13T21:01:40.632290Z"
+ }
+ },
+ "source": [
+ "# create some hooks!\n",
+ "class PrintWhatTheCowSaid(PostRunStepHook):\n",
+ " def post_run_step(self, *, state: \"State\", action: \"Action\", **future_kwargs):\n",
+ " if action.name != \"cow_should_say\" and state[\"cow_said\"] is not None:\n",
+ " print(state[\"cow_said\"])\n",
+ "\n",
+ "\n",
+ "class CowCantSpeakFast(PostRunStepHook):\n",
+ " def __init__(self, sleep_time: float):\n",
+ " super(PostRunStepHook, self).__init__()\n",
+ " self.sleep_time = sleep_time\n",
+ "\n",
+ " def post_run_step(self, *, state: \"State\", action: \"Action\", **future_kwargs):\n",
+ " if action.name != \"cow_should_say\": # no need to print if we're not saying anything\n",
+ " time.sleep(self.sleep_time)"
+ ],
+ "outputs": [],
+ "execution_count": 2
+ },
+ {
+ "cell_type": "code",
+ "id": "5b29e9de2ca536b9",
+ "metadata": {
+ "collapsed": false,
+ "jupyter": {
+ "outputs_hidden": false
+ },
+ "ExecuteTime": {
+ "end_time": "2024-12-13T21:02:32.928090Z",
+ "start_time": "2024-12-13T21:02:32.923286Z"
+ }
+ },
+ "source": [
+ "# instantiate actions\n",
+ "@action(reads=[], writes=[\"cow_said\"])\n",
+ "def cow_said(state: State, say_what: list[str]) -> Tuple[dict, State]:\n",
+ " said = random.choice(say_what) if say_what is not None else None\n",
+ " result = {\"cow_said\": cowsay.get_output_string(\"cow\", said) if say_what is not None else None}\n",
+ " return result, state.update(**result)\n",
+ "\n",
+ "\n",
+ "@action(reads=[], writes=[\"cow_should_speak\"])\n",
+ "def cow_should_speak(state: State) -> Tuple[dict, State]:\n",
+ " result = {\"cow_should_speak\": random.randint(0, 3) == 0}\n",
+ " return result, state.update(**result)\n"
+ ],
+ "outputs": [],
+ "execution_count": 7
+ },
+ {
+ "cell_type": "code",
+ "id": "271b868317fbbdf4",
+ "metadata": {
+ "collapsed": false,
+ "jupyter": {
+ "outputs_hidden": false
+ },
+ "ExecuteTime": {
+ "end_time": "2024-12-13T21:02:33.629009Z",
+ "start_time": "2024-12-13T21:02:33.613724Z"
+ }
+ },
+ "source": [
+ "# build application\n",
+ "hooks = [\n",
+ " PrintWhatTheCowSaid(),\n",
+ " CowCantSpeakFast(sleep_time=2.0),\n",
+ "]\n",
+ " \n",
+ "app = (ApplicationBuilder()\n",
+ " .with_state(cow_said=None)\n",
+ " .with_actions(\n",
+ " say_nothing=cow_said.bind(say_what=None),\n",
+ " say_hello=cow_said.bind(\n",
+ " say_what=[\"Hello world!\", \"What's up?\", \"Are you Aaron Burr, sir?\"]\n",
+ " ),\n",
+ " cow_should_speak=cow_should_speak,\n",
+ " )\n",
+ " .with_transitions(\n",
+ " (\"cow_should_speak\", \"say_hello\", expr(\"cow_should_speak\")),\n",
+ " (\"say_hello\", \"cow_should_speak\", default),\n",
+ " (\"cow_should_speak\", \"say_nothing\", expr(\"not cow_should_speak\")),\n",
+ " (\"say_nothing\", \"cow_should_speak\", default),\n",
+ " )\n",
+ " .with_entrypoint(\"cow_should_speak\")\n",
+ " .with_hooks(*hooks)\n",
+ " .build()\n",
+ ")"
+ ],
+ "outputs": [],
+ "execution_count": 8
+ },
+ {
+ "cell_type": "code",
+ "id": "794bc0abda9e48ad",
+ "metadata": {
+ "collapsed": false,
+ "jupyter": {
+ "outputs_hidden": false
+ },
+ "ExecuteTime": {
+ "end_time": "2024-12-13T21:02:34.796777Z",
+ "start_time": "2024-12-13T21:02:34.484247Z"
+ }
+ },
+ "source": [
+ "app.visualize(include_conditions=True)"
+ ],
+ "outputs": [
+ {
+ "data": {
+ "image/svg+xml": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n<!-- Generated by graphviz version 12.0.0 (20240704.0754)\n -->\n<!-- Pages: 1 -->\n<svg width=\"200pt\" height=\"211pt\"\n viewBox=\"0.00 0.00 199.93 210.80\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) [...]
+ "text/plain": [
+ "<graphviz.graphs.Digraph at 0x128b199c0>"
+ ]
+ },
+ "execution_count": 9,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 9
+ },
+ {
+ "cell_type": "code",
+ "id": "2facfb753233cd7d",
+ "metadata": {
+ "collapsed": false,
+ "jupyter": {
+ "outputs_hidden": false
+ },
+ "ExecuteTime": {
+ "end_time": "2024-12-13T21:02:43.025443Z",
+ "start_time": "2024-12-13T21:02:35.007367Z"
+ }
+ },
+ "source": [
+ "# run things!\n",
+ "for i in range(0, 4):\n",
+ " # step through 4 times\n",
+ " a_obj, result, state = app.step()"
+ ],
+ "outputs": [],
+ "execution_count": 10
+ },
+ {
+ "cell_type": "code",
+ "id": "fed65721e7cc457a",
+ "metadata": {
+ "collapsed": false,
+ "jupyter": {
+ "outputs_hidden": false
+ },
+ "ExecuteTime": {
+ "end_time": "2024-12-13T21:02:51.055716Z",
+ "start_time": "2024-12-13T21:02:43.033412Z"
+ }
+ },
+ "source": [
+ "# Run some more\n",
+ "for i in range(0, 4):\n",
+ " # step through 4 times\n",
+ " a_obj, result, state = app.step()"
+ ],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " __________\n",
+ "| What's up? |\n",
+ " ==========\n",
+ " \\\n",
+ " \\\n",
+ " ^__^\n",
+ " (oo)\\_______\n",
+ " (__)\\ )\\/\\\n",
+ " ||----w |\n",
+ " || ||\n",
+ " __________\n",
+ "| What's up? |\n",
+ " ==========\n",
+ " \\\n",
+ " \\\n",
+ " ^__^\n",
+ " (oo)\\_______\n",
+ " (__)\\ )\\/\\\n",
+ " ||----w |\n",
+ " || ||\n"
+ ]
+ }
+ ],
+ "execution_count": 11
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "19095ae96e98fd83",
+ "metadata": {
+ "collapsed": false,
+ "jupyter": {
+ "outputs_hidden": false
+ }
+ },
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.10.4"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/content/_sources/examples/simple/index.rst.txt b/content/_sources/examples/simple/index.rst.txt
new file mode 100644
index 00000000..1c1584ee
--- /dev/null
+++ b/content/_sources/examples/simple/index.rst.txt
@@ -0,0 +1,12 @@
+================
+🧸 Toy examples
+================
+
+The following toy examples illustrate the basics of Burr. If you're interested in more real-life applications, skip ahead to chatbots.
+
+.. toctree::
+ :maxdepth: 2
+
+ counter
+ cowsay
+ choose-your-own-adventure
diff --git a/content/_sources/getting_started/index.rst.txt b/content/_sources/getting_started/index.rst.txt
new file mode 100644
index 00000000..06508710
--- /dev/null
+++ b/content/_sources/getting_started/index.rst.txt
@@ -0,0 +1,15 @@
+.. _gettingstarted:
+
+====================
+Get started
+====================
+
+The following section of the docs will walk you through Burr and how to integrate into your project:
+
+.. toctree::
+ :maxdepth: 1
+
+ why-burr
+ install
+ simple-example
+ up-next
diff --git a/content/_sources/getting_started/install.rst.txt b/content/_sources/getting_started/install.rst.txt
new file mode 100644
index 00000000..7458a2cd
--- /dev/null
+++ b/content/_sources/getting_started/install.rst.txt
@@ -0,0 +1,158 @@
+=============
+Installation
+=============
+
+Burr itself requires no dependencies. Every *extra*/*plugin* is an additional install target.
+
+We recommend you start with installing the ``start`` target -- this has dependencies necessary to track burr through the UI,
+along with a fully built server.
+
+.. code-block:: bash
+
+ pip install "burr[start]"
+
+This will give you tools to visualize, track, and interact with the UI. You can explore the UI (including some sample projects)
+simply by running the command ``burr``. Up next we'll write our own application and follow it in the UI.
+
+If you're using poetry, you can't install the ``start`` target directly, due to [this issue](https://github.com/python-poetry/poetry/issues/3369).
+Instead, please install manually using the following command:
+
+.. code-block:: bash
+
+ poetry add loguru "burr[tracking-client,tracking-server,streamlit,graphviz,hamilton]"
+
+This is just the kitchen sink for getting started -- remember, burr is dependency-free/pure python!
+
+------------------------------------
+Description of different targets
+------------------------------------
+
+.. code-block:: bash
+
+ pip install burr
+
+This only installs the framework. Zero other dependencies. All other installs below all install the
+framework + some other dependencies. Note you can combine installing multiple optional dependencies with commas, e.g.
+``pip install "burr[cli,tracking]"`` installs both the CLI and tracking + UI dependencies.
+
+.. code-block:: bash
+
+ pip install "burr[cli]"
+
+This installs the dependencies to run the burr CLI, i.e. `burr --help`.
+
+.. code-block:: bash
+
+ pip install "burr[developer]"
+
+This installs all the dependencies for developing locally.
+
+
+.. code-block:: bash
+
+ pip install "burr[documentation]"
+
+This installs the dependencies to build the documentation.
+
+.. code-block:: bash
+
+ pip install "burr[examples]"
+
+This installs the dependencies for the examples.
+
+
+.. code-block:: bash
+
+ pip install "burr[graphviz]"
+
+This installs the dependencies to visualize the graph.
+
+.. code-block:: bash
+
+ pip install "burr[hamilton]"
+
+This installs the dependencies for Hamilton.
+
+.. code-block:: bash
+
+ pip install "burr[haystack]"
+
+This installs the dependencies for Haystack.
+
+.. code-block:: bash
+
+ pip install "burr[learn]"
+
+This installs the dependencies for the UI, CLI, and running demos. It is equivalent to `start` below.
+
+.. code-block:: bash
+
+ pip install "burr[opentelemetry]"
+
+This installs the dependencies for using OpenTelemetry with Burr.
+
+.. code-block:: bash
+
+ pip install "burr[postgresql]"
+
+This installs the dependencies for PostgreSQL.
+
+.. code-block:: bash
+
+ pip install "burr[pydantic]"
+
+This installs the dependencies for Pydantic.
+
+.. code-block:: bash
+
+ pip install "burr[redis]"
+
+This installs the dependencies for Redis.
+
+.. code-block:: bash
+
+ pip install "burr[start]"
+
+This installs the dependencies for the UI, CLI, and running demos. It is equivalent to `learn` above.
+
+.. code-block:: bash
+
+ pip install "burr[streamlit]"
+
+This installs the dependencies for Streamlit.
+
+.. code-block:: bash
+
+ pip install "burr[tests]"
+
+This installs the dependencies for running unit tests.
+
+.. code-block:: bash
+
+ pip install "burr[tracking]"
+
+This installs the client and server dependencies for tracking and running the UI from tracking that is on a filesystem.
+
+.. code-block:: bash
+
+ pip install "burr[tracking-client]"
+
+This installs the client dependencies for tracking to a filesystem.
+
+.. code-block:: bash
+
+ pip install "burr[tracking-client-s3]"
+
+This installs the client dependencies for tracking to S3.
+
+.. code-block:: bash
+
+ pip install "burr[tracking-server-s3]"
+
+This installs the server dependencies to run the UI and load tracking that was sent to S3.
+
+.. code-block:: bash
+
+ pip install "burr[tracking-server]"
+
+This installs the server dependencies for running the UI off a filesystem.
diff --git a/content/_sources/getting_started/simple-example.rst.txt b/content/_sources/getting_started/simple-example.rst.txt
new file mode 100644
index 00000000..4757fe9e
--- /dev/null
+++ b/content/_sources/getting_started/simple-example.rst.txt
@@ -0,0 +1,206 @@
+.. _simpleexample:
+
+==============
+Quickstart
+==============
+
+Let's build a basic chatbot using Burr.
+
+While Burr has a host of features related to state management and inspection, this basic tutorial is going to demonstrate two that are particularly relevant to LLM apps.
+
+1. Specifying user inputs
+2. Persisting state across multiple interactions
+
+This tutorial will set you up to do more complex things, including:
+
+1. Building an application that makes decisions/handles conditional edges
+2. Persisting state in a database
+3. Tracking/monitoring your application
+4. Generating test cases from prior application runs
+
+So hold tight! This gets you started with the basics but there's a lot more you can do with little effort.
+
+
+.. note::
+
+ This should take about 10 minutes to complete, and give you a good sense of the library basics.
+ You'll need an OpenAI key set as the environment variable ``OPENAI_API_KEY``. If you don't have one you can get one at `OpenAI <https://platform.openai.com>`_.
+ If you don't want to get one, check out the simple example of a `counter application <https://github.com/DAGWorks-Inc/burr/tree/main/examples/hello-world-counter>`_.
+
+ If you want to skip ahead to the cool stuff (chatbots, ML training, simulations, etc...) feel free to jump into the deep end and start with the :ref:`examples <examples>`.
+
+🤔 If you prefer to learn by video, check out
+`this video walkthrough <https://www.youtube.com/watch?v=rEZ4oDN0GdU>`_
+using `this notebook <https://github.com/DAGWorks-Inc/burr/blob/main/examples/simple-chatbot-intro/notebook.ipynb>`_.
+
+----------------------
+Build a Simple Chatbot
+----------------------
+
+Let's build! Our chatbot will accept user input and pass it to an AI. The AI will then respond with a message.
+
+Let's start by defining some actions, the building-blocks of Burr. You can think of actions as a function that
+computes a result and modifies state. They declare what they read and write.
+
+
+We define two actions:
+
+1. ``human_input`` This accepts a prompt from the outside and adds it to the state
+2. ``ai_response`` This takes the prompt + chat history and queries OpenAI.
+
+
+.. code-block:: python
+
+ # Note: you can replace this with your favorite LLM Client
+ client = openai.Client()
+
+ @action(reads=[], writes=["prompt", "chat_history"])
+ def human_input(state: State, prompt: str) -> State:
+ chat_item = {
+ "content": prompt,
+ "role": "user"
+ }
+ return state.update(prompt=prompt).append(chat_history=chat_item)
+
+
+ @action(reads=["chat_history"], writes=["response", "chat_history"])
+ def ai_response(state: State) -> State:
+ content = client.chat.completions.create(
+ model="gpt-3.5-turbo",
+ messages=state["chat_history"],
+ ).choices[0].message.content
+ chat_item = {
+ "content": content,
+ "role": "assistant"
+ }
+ return state.update(response=content).append(chat_history=chat_item)
+
+
+Before we proceed, let's note the following about how we define these actions:
+
+1. State is a dictionary -- actions declare input fields (as strings) and write values to those fields
+2. Actions use a specific *immutable* state object and call operations on it (``.append(...)``, ``.update(...)``)
+3. Functions can do whatever you want -- they can use plain python, or delegate to `langchain <https://langchain.com>`_, `hamilton <https://github.com/dagworks-inc/hamilton>`_, etc... All they have to do is return the new state.
+4. We declare the parameter ``prompt``, meaning that we will expect the user to pass ``prompt`` every time they run the graph.
+
+.. note::
+
+ The action API has a few different forms. We've shown the simplest above, but it can also return a tuple of ``result, state`` (where ``result``
+ the intermediate result of the computation), to help with debugging. There are also multiple class-based APIs. See the :ref:`actions <actions>` section for more details.
+
+Next, let's piece together our application. To do this, we'll use an ``ApplicationBuilder``
+
+.. code-block:: python
+
+ app = (
+ ApplicationBuilder()
+ .with_actions(human_input, ai_response)
+ .with_transitions(
+ ("human_input", "ai_response"),
+ ("ai_response", "human_input")
+ ).with_state(chat_history=[])
+ .with_entrypoint("human_input")
+ .build()
+ )
+
+We can visualize the application (note you need ``burr[graphviz]`` installed):
+
+.. code-block:: python
+
+ app.visualize("./graph", format="png")
+
+.. image:: ../_static/chatbot.png
+ :align: center
+
+Let's note the following about how we define the application:
+
+1. It is an infinite loop! It is meant to pause for new prompt input.
+2. We're just using the function names as the action names. You can also name them if you want ``with_actions(human_input=human_input, ai_response=ai_response)``.
+3. We start it with an empty ``chat_history`` state field
+4. It utilizes a `builder pattern <https://en.wikipedia.org/wiki/Builder_pattern#:~:text=The%20builder%20pattern%20is%20a,Gang%20of%20Four%20design%20patterns>`_ -- this is a bit old-school (comes from the java days), but is an easy/modular way to express your application
+
+Finally, we can run the application -- it gives back multiple pieces of information but all we'll use is the state.
+
+.. code-block:: python
+
+ *_, state = app.run(halt_after=["ai_response"], inputs={"prompt": "Who was Aaron Burr?"})
+ print("answer:", app.state["response"])
+ print(len(state["chat_history"]), "items in chat")
+
+The result looks exactly as you'd expect!
+
+.. code-block:: text
+
+ answer: Aaron Burr was an American politician and lawyer who served as the third
+ Vice President of the United States from 1801 to 1805 under President Thomas Jefferson.
+ He is also known for killing Alexander Hamilton in a famous duel in 1804.
+ Burr was involved in various political intrigues and controversies throughout his career,
+ and his reputation was tarnished by suspicions of treason and conspiracy.
+ 2 items in chat
+
+If you want to copy/paste, you can open up the following code block and add to a file called ``run.py``:
+
+.. collapse:: <code>run.py</code>
+
+ .. code-block:: python
+
+ import uuid
+
+ import openai # replace with your LLM library
+
+ from burr.core import action, State, ApplicationBuilder
+
+ client = openai.Client() # replace this if not using OpenAI
+
+ @action(reads=[], writes=["prompt", "chat_history"])
+ def human_input(state: State, prompt: str) -> State:
+ chat_item = {
+ "content": prompt,
+ "role": "user"
+ }
+ return state.update(prompt=prompt).append(chat_history=chat_item)
+
+ @action(reads=["chat_history"], writes=["response", "chat_history"])
+ def ai_response(state: State) -> State:
+ content = client.chat.completions.create(
+ model="gpt-3.5-turbo",
+ messages=state["chat_history"],
+ ).choices[0].message.content
+ chat_item = {
+ "content": content,
+ "role": "assistant"
+ }
+ return state.update(response=content).append(chat_history=chat_item)
+ app = (
+ ApplicationBuilder()
+ .with_actions(human_input, ai_response)
+ .with_transitions(
+ ("human_input", "ai_response"),
+ ("ai_response", "human_input")
+ ).with_state(chat_history=[])
+ .with_entrypoint("human_input")
+ .build()
+ )
+
+ app.visualize("./graph", format="png")
+ *_, state = app.run(halt_after=["ai_response"], inputs={"prompt": "Who was Aaron Burr?"})
+ print("answer:", app.state["response"])
+ print(len(state["chat_history"]), "items in chat")
+
+
+Finally, let's open up the UI and see what it looks like (note, that if you have not installed ``burr[learn]`` now is a good time...).
+
+.. code-block:: bash
+
+ burr
+
+You'll see the UI pop up with projects. Navigate to `the UI <http://localhost:7241/project/my_first_app>`_ and explore!
+
+
+Now that we've built a basic application, we can do the following with only a few lines of code:
+
+1. :ref:`Add conditional edges <transitions>` -- add a condition as a third item in the tuple to the ``with_transitions`` method.
+2. :ref:`Persist state to a database + reload <state-persistence>` -- add a ``initialize_from`` line to the builder and select a pre-existing/implement a custom persistence method.
+3. :ref:`Add monitoring to track application data <tracking>` -- leverage ``with_tracker`` to track to the Burr UI and visualize your application live.
+4. :ref:`Stream results back <streaming>` -- minimize time to first token by streaming results back to the user.
+5. `Generate test cases from prior runs <https://github.com/DAGWorks-Inc/burr/tree/main/examples/test-case-creation>`_ -- use the ``burr-testburr-test-case create`` command to automatically generate test cases for your LLM app.
diff --git a/content/_sources/getting_started/up-next.rst.txt b/content/_sources/getting_started/up-next.rst.txt
new file mode 100644
index 00000000..77ce2718
--- /dev/null
+++ b/content/_sources/getting_started/up-next.rst.txt
@@ -0,0 +1,75 @@
+=================
+Next Steps
+=================
+
+You've written your own burr application in a few lines of code. Nice work! Let's look at some less trivial examples.
+All of the following are pre-populated in the UI under ``projects/``. You can start the UI by running:
+
+.. code-block:: bash
+
+ burr
+
+Telemetry UI chatbot demo ``demo_chatbot``
+------------------------------------------
+
+You'll need the env variable ``OPENAI_API_KEY`` set to your api key for this to work. If you don't
+have one, you'll still be able to run it and explore, you just won't be able to chat.
+
+If you haven't already:
+
+.. code-block:: bash
+
+ pip install burr[start]
+
+Then:
+
+.. code-block:: bash
+
+ burr-demo
+
+If you've run ``burr`` and have it open on port 7241, just navigate to `demos/chatbot <http://localhost:7241/demos/chatbot>`_.
+
+Repository Examples
+-------------------
+
+For the next examples you'll need the repository cloned:
+
+.. code-block:: bash
+
+ git clone https://github.com/dagworks-inc/burr && cd burr
+
+-------------------------------
+Simple Counter ``demo_counter``
+-------------------------------
+
+.. code-block:: bash
+
+ cd examples/hello-world-counter
+ python application.py
+
+-------------------------------------------
+Interactive RAG ``demo_conversational-rag``
+-------------------------------------------
+
+This is a toy interactive RAG example. You'll ask questions in the terminal about information it already has...
+
+.. code-block:: bash
+
+ cd examples/conversational-rag/simple_example
+ pip install -r requirements.txt
+ python application.py
+
+
+Understanding Concepts
+----------------------
+
+If you're more comfortable learning through concepts start here.
+
+Once you're comfortable with the UI, you may want to get a sense of a few of the capabilities
+of the Burr library and where you can go to learn more about them:
+
+- :ref:`Creating custom actions <actions>` and calling out to integrated frameworks
+- :ref:`Running applications <applications>`, managing their lifeycyle, and inspecting the results
+- :ref:`Managing state <state>` -- persisting, inspecting, and updating
+- :ref:`Handling transition between nodes <transitions>` and managing the flow of your application
+- :ref:`Adding hooks to customize execution <hooks>` and integrate with other systems
diff --git a/content/_sources/getting_started/why-burr.rst.txt b/content/_sources/getting_started/why-burr.rst.txt
new file mode 100644
index 00000000..530ecfc7
--- /dev/null
+++ b/content/_sources/getting_started/why-burr.rst.txt
@@ -0,0 +1,23 @@
+=========
+Why Burr?
+=========
+
+Why do you need a state machine for your applications? Won't the normal programming constructs suffice?
+
+**Yes, until a point.** Let's take a look at what you need to build a production-level LLM application:
+
+1. **Tracing/telemetry** -- LLMs can be chaotic, and you need visibility into what decisions it made and how long it took to make them.
+2. **State persistence** -- thinking about how to save/load your application is a whole other level of infrastructure you need to worry about.
+3. **Visualization/debugging** -- when developing you'll want to be able to view what it is doing/did + load up the data at any point
+4. **Manage interaction between users/LLM** -- pause for input in certain conditions
+5. **Data gathering for evaluation + test generation** -- storing data run in production to use for later analysis/fine-tuning
+
+You can always patch together various frameworks or build it all yourself, but at that point you're going to be spending a lot of time on tasks that
+are not related to the core value proposition of your software.
+
+**Burr was built to make these all easier.**
+
+By modeling your application as a state machine of simple python constructs you can have the best of both worlds.
+Bring in whatever infrastructure/tooling you want and get all of the above. Burr is meant to start off as an extremely lightweight tool to
+make building LLM (+ a wide swath of other) applications easier. The value compounds as you leverage more of the ecosystems, plugins, and additional
+features it provides.
diff --git a/content/_sources/index.rst.txt b/content/_sources/index.rst.txt
new file mode 100644
index 00000000..e36eeb55
--- /dev/null
+++ b/content/_sources/index.rst.txt
@@ -0,0 +1,28 @@
+.. include:: main.rst
+
+.. toctree::
+ :maxdepth: 2
+ :hidden:
+ :caption: Burr
+
+ getting_started/index
+ examples/index
+ concepts/index
+ reference/index
+ contributing/index
+
+.. toctree::
+ :hidden:
+ :maxdepth: 2
+ :caption: Burr Cloud
+
+ Waitist <https://forms.gle/w9u2QKcPrztApRedA>
+
+.. toctree::
+ :hidden:
+ :caption: RESOURCES
+
+ Blog <https://blog.dagworks.io/>
+ Discord community server <https://discord.gg/6Zy2DwP4f3>
+ GitHub <https://github.com/dagworks-inc/burr>
+ Twitter <https://twitter.com/dagworks>
diff --git a/content/_sources/main.rst.txt b/content/_sources/main.rst.txt
new file mode 100644
index 00000000..891506c1
--- /dev/null
+++ b/content/_sources/main.rst.txt
@@ -0,0 +1,92 @@
+==============
+Burr
+==============
+
+Welcome to Burr's documentation.
+
+For a quick overview of Burr, watch `this walkthrough <https://www.loom.com/share/a10f163428b942fea55db1a84b1140d8?sid=1512863b-f533-4a42-a2f3-95b13deb07c9>`_
+or read `our blog post <https://blog.dagworks.io/p/burr-develop-stateful-ai-applications?r=2cg5z1&utm_campaign=post&utm_medium=web>`_. The following video is
+a longer demo of building a simple chatbot application with Burr using `this notebook <https://github.com/DAGWorks-Inc/burr/blob/main/examples/conversational-rag/simple_example/notebook.ipynb>`_:
+
+.. raw:: html
+
+ <div style="position: relative; padding-bottom: 56.25%; height: 0;"><iframe width="800" height="455" src="https://www.youtube.com/embed/rEZ4oDN0GdU?si=6RvExhQVsAyNd8Ts" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe></div>
+
+For more details about how Burr works, what problems it solves, and how to use it, read on!
+
+You'll find this documentation separated into three sections.
+
+- If you don't know where to start, go to :ref:`getting started <gettingstarted>`.
+- If you're looking to build a mental model/read more, go to :ref:`concepts <concepts>`.
+- If you're looking for a specific piece of information, go to :ref:`reference <reference>`.
+
+We also ask that you:
+
+- Report any bugs, issues, or feature requests via `GitHub Issues <https://github.com/DAGWorks-Inc/burr/issues>`_ or \
+ `GitHub Discussions <https://github.com/DAGWorks-Inc/burr/discussions>`_.
+- Give us a star on `GitHub <https://github.com/dagworks-inc/burr>`_ if you like the project!
+
+
+Testimonials
+============
+
+.. raw:: html
+
+ <link rel="stylesheet" type="text/css" href="_static/testimonials.css">
+ <div class="testimonial-container">
+ <div class="testimonial-card">
+ <div class="testimonial-content">
+ <p>"After evaluating several other obfuscating LLM frame-works, their elegant yet comprehensive state management solution proved to be the powerful answer to rolling out robots driven by AI decision making."</p>
+ <h4>Ashish Ghosh</h4>
+ <span>CTO, Peanut Robotics</span>
+ </div>
+ </div>
+
+ <div class="testimonial-card">
+ <div class="testimonial-content">
+ <p>"Of course, you can use it [LangChain], but whether it's really production-ready and improves the time from 'code-to-prod' [...], we've been doing LLM apps for two years, and the answer is no [...] All these 'all-in-one' libs suffer from this [...]. Honestly, take a look at Burr. Thank me later."</p>
+ <h4>Reddit User</h4>
+ <span>LocalLlama, Subreddit</span>
+ </div>
+ </div>
+
+ <div class="testimonial-card">
+ <div class="testimonial-content">
+ <p>"Using Burr is a no-brainer if you want to build a modular AI application. It is so easy to build with and I especially love their UI which makes debugging, a piece of cake. And the always ready to help team, is the cherry on top."</p>
+ <h4>Ishita</h4>
+ <span>Founder, Watto.ai</span>
+ </div>
+ </div>
+
+ <div class="testimonial-card">
+ <div class="testimonial-content">
+ <p>"I just came across Burr and I'm like WOW, this seems like you guys predicted this exact need when building this. No weird esoteric concepts just because it's AI."</p>
+ <h4>Matthew Rideout</h4>
+ <span>Staff Software Engineer, Paxton AI</span>
+ </div>
+ </div>
+
+ <div class="testimonial-card">
+ <div class="testimonial-content">
+ <p>"Burr's state management part is really helpful for creating state snapshots and build debugging, replaying and even building evaluation cases around that"</p>
+ <h4>Rinat Gareev</h4>
+ <span>Senior Solutions Architect, Provectus</span>
+ </div>
+ </div>
+
+ <div class="testimonial-card">
+ <div class="testimonial-content">
+ <p>"I have been using Burr over the past few months, and compared to many agentic LLM platforms out there (e.g. LangChain, CrewAi, AutoGen, Agency Swarm, etc), Burr provides a more robust framework for designing complex behaviors."</p>
+ <h4>Hadi Nayebi</h4>
+ <span>Co-founder, CognitiveGraphs</span>
+ </div>
+ </div>
+
+ <div class="testimonial-card">
+ <div class="testimonial-content">
+ <p>"Moving from LangChain to Burr was a game-changer! <br/>Time-Saving: It took me just a few hours to get started with Burr, compared to the days and weeks I spent trying to navigate LangChain. <br/>Cleaner Implementation: With Burr, I could finally have a cleaner, more sophisticated, and stable implementation. No more wrestling with complex codebases. <br/>Team Adoption: I pitched Burr to my teammates, and we pivoted our entire codebase to it. It's been a smooth ride ev [...]
+ <h4>Aditya K.</h4>
+ <span>DS Architect, TaskHuman</span>
+ </div>
+ </div>
+ </div>
diff --git a/content/_sources/reference/actions.rst.txt b/content/_sources/reference/actions.rst.txt
new file mode 100644
index 00000000..72d52e1d
--- /dev/null
+++ b/content/_sources/reference/actions.rst.txt
@@ -0,0 +1,50 @@
+=================
+Actions
+=================
+
+.. _actionref:
+
+.. autoclass:: burr.core.action.Action
+ :members:
+ :inherited-members:
+ :show-inheritance:
+
+ .. automethod:: __init__
+
+.. autoclass:: burr.core.action.Result
+ :members:
+
+ .. automethod:: __init__
+
+
+.. autoclass:: burr.core.action.Input
+ :members:
+
+ .. automethod:: __init__
+
+.. autoclass:: burr.core.action.action
+ :members:
+
+ .. automethod:: __init__
+
+
+.. autofunction:: burr.core.action.bind
+
+
+.. autoclass:: burr.core.action.Function
+ :members:
+
+.. autoclass:: burr.core.action.Reducer
+ :members:
+
+.. autoclass:: burr.core.action.StreamingResultContainer
+ :members:
+
+.. autoclass:: burr.core.action.AsyncStreamingResultContainer
+ :members:
+
+.. autoclass:: burr.core.action.streaming_action
+ :members:
+
+.. autoclass:: burr.core.action.StreamingAction
+ :members:
diff --git a/content/_sources/reference/application.rst.txt b/content/_sources/reference/application.rst.txt
new file mode 100644
index 00000000..f6dfc3b9
--- /dev/null
+++ b/content/_sources/reference/application.rst.txt
@@ -0,0 +1,39 @@
+============
+Applications
+============
+
+Use this to build and manage a state Machine. You should only ever instantiate the ``ApplicationBuilder`` class,
+and not the ``Application`` class directly.
+
+
+.. autoclass:: burr.core.application.ApplicationBuilder
+ :members:
+
+.. _applicationref:
+
+.. autoclass:: burr.core.application.Application
+ :members:
+
+ .. automethod:: __init__
+
+.. autoclass:: burr.core.application.ApplicationGraph
+ :members:
+
+.. autoclass:: burr.core.application.ApplicationContext
+ :members:
+
+==========
+Graph APIs
+==========
+
+You can, optionally, use the graph API along with the :py:meth:`burr.core.application.ApplicationBuilder.with_graph`
+method. While this is a little more verbose, it helps decouple application logic from graph logic, and is useful in a host
+of situations.
+
+The ``GraphBuilder`` class is used to build a graph, and the ``Graph`` class can be passed to the application builder.
+
+.. autoclass:: burr.core.graph.GraphBuilder
+ :members:
+
+.. autoclass:: burr.core.graph.Graph
+ :members:
diff --git a/content/_sources/reference/conditions.rst.txt b/content/_sources/reference/conditions.rst.txt
new file mode 100644
index 00000000..6b7de8bf
--- /dev/null
+++ b/content/_sources/reference/conditions.rst.txt
@@ -0,0 +1,14 @@
+=======================
+Conditions/Transitions
+=======================
+
+.. _transitionref:
+
+Conditions represent choices to move between actions -- these are read by the application builder when executing the graph.
+Note that these will always be specified in order -- the first condition that evaluates to ``True`` will be the selected action.
+
+.. autoclass:: burr.core.action.Condition
+ :special-members: __and__, __or__, __invert__
+ :members:
+
+ .. automethod:: __init__
diff --git a/content/_sources/reference/index.rst.txt b/content/_sources/reference/index.rst.txt
new file mode 100644
index 00000000..1deeb28e
--- /dev/null
+++ b/content/_sources/reference/index.rst.txt
@@ -0,0 +1,27 @@
+
+.. _reference:
+
+========================
+API reference
+========================
+
+Reference documentation. Anything in here is part of the public (semantically versioned) API, unless marked otherwise.
+That means that if you find something not in here, it's not part of the public API and may change without notice. If you
+need functionality that is not publicly exposed, please open an issue and we can discuss adding it!
+
+.. toctree::
+ :maxdepth: 2
+
+ application
+ actions
+ state
+ serde
+ persister
+ conditions
+ tracking
+ visibility
+ lifecycle
+ parallelism
+ typing
+ integrations/index
+ telemetry
diff --git a/content/_sources/reference/integrations/hamilton.rst.txt b/content/_sources/reference/integrations/hamilton.rst.txt
new file mode 100644
index 00000000..5bf84a08
--- /dev/null
+++ b/content/_sources/reference/integrations/hamilton.rst.txt
@@ -0,0 +1,15 @@
+=======================
+Hamilton
+=======================
+
+Full Hamilton integration. Touch-points are custom Hamilton actions. This is installed by default.
+
+.. autofunction:: burr.integrations.hamilton.Hamilton
+
+.. autofunction:: burr.integrations.hamilton.from_state
+
+.. autofunction:: burr.integrations.hamilton.from_value
+
+.. autofunction:: burr.integrations.hamilton.update_state
+
+.. autofunction:: burr.integrations.hamilton.append_state
diff --git a/content/_sources/reference/integrations/haystack.rst.txt b/content/_sources/reference/integrations/haystack.rst.txt
new file mode 100644
index 00000000..045a099b
--- /dev/null
+++ b/content/_sources/reference/integrations/haystack.rst.txt
@@ -0,0 +1,9 @@
+========
+Haystack
+========
+
+The Haystack integration allows you to use ``Component`` as Burr ``Action`` using the ``HaystackAction`` construct. You can visit the examples in ``burr/examples/haystack-integration`` for a notebook tutorial.
+
+.. autoclass:: burr.integrations.haystack.HaystackAction
+
+.. autofunction:: burr.integrations.haystack.haystack_pipeline_to_burr_graph
diff --git a/content/_sources/reference/integrations/index.rst.txt b/content/_sources/reference/integrations/index.rst.txt
new file mode 100644
index 00000000..685da57a
--- /dev/null
+++ b/content/_sources/reference/integrations/index.rst.txt
@@ -0,0 +1,17 @@
+============
+Integrations
+============
+
+Integrations -- we will be adding more
+
+.. toctree::
+ :maxdepth: 2
+
+ hamilton
+ streamlit
+ opentelemetry
+ traceloop
+ langchain
+ pydantic
+ haystack
+ ray
diff --git a/content/_sources/reference/integrations/langchain.rst.txt b/content/_sources/reference/integrations/langchain.rst.txt
new file mode 100644
index 00000000..4b6436d0
--- /dev/null
+++ b/content/_sources/reference/integrations/langchain.rst.txt
@@ -0,0 +1,18 @@
+---------
+Langchain
+---------
+
+Burr works out of the box with langchain, as Burr delegates to any python code.
+
+There are multiple examples of Burr leveraging langchain, including:
+
+- `Multi agent collaboration <https://github.com/DAGWorks-Inc/burr/tree/main/examples/multi-agent-collaboration/lcel>`_
+- `LCEL + Hamilton together <https://github.com/DAGWorks-Inc/burr/tree/main/examples/multi-agent-collaboration/hamilton>`_
+
+Burr also provides custom ser/deserialization for langchain objects. See the following resources:
+1. `Example <https://github.com/DAGWorks-Inc/burr/tree/main/examples/custom-serde>`_
+2. :ref:`Custom serialization docs <serde>`
+3. `Langchain serialization plugin <https://github.com/DAGWorks-Inc/burr/blob/main/burr/integrations/serde/langchain.py>`_
+
+We are working on adding more builtin support for LCEL (LCELActions), and considering adding burr callbacks for tracing langgraph in the Burr
+UI. If you have any suggestions, please let us know.
diff --git a/content/_sources/reference/integrations/opentelemetry.rst.txt b/content/_sources/reference/integrations/opentelemetry.rst.txt
new file mode 100644
index 00000000..280cedf0
--- /dev/null
+++ b/content/_sources/reference/integrations/opentelemetry.rst.txt
@@ -0,0 +1,25 @@
+.. _opentelintegrationref:
+
+--------------
+OpenTelemetry
+--------------
+
+Burr has two integrations with OpenTelemetry:
+
+
+1. Burr can log traces to OpenTelemetry
+2. Burr can capture any traces logged within an action and log them to OpenTelemetry
+
+See the following resources for more information:
+
+- :ref:`Tracing/OpenTelemetry <opentelref>`
+- `Example in the repository <https://github.com/dagworks-inc/burr/tree/main/examples/opentelemetry>`_
+- `Blog post <https://blog.dagworks.io/p/9ef2488a-ff8a-4feb-b37f-1d9a781068ac/>`_
+- `OpenTelemetry <https://opentelemetry.io/>`_
+
+Reference for the various useful methods:
+
+.. autoclass:: burr.integrations.opentelemetry.OpenTelemetryBridge
+ :members:
+
+.. autofunction:: burr.integrations.opentelemetry.init_instruments
diff --git a/content/_sources/reference/integrations/pydantic.rst.txt b/content/_sources/reference/integrations/pydantic.rst.txt
new file mode 100644
index 00000000..6f24de8e
--- /dev/null
+++ b/content/_sources/reference/integrations/pydantic.rst.txt
@@ -0,0 +1,8 @@
+========
+Pydantic
+========
+
+`Pydantic <https://www.pydantic.io>`_ integrations come in two forms:
+
+- Serialization/deserialization of state (see :ref:`serderef` for more information)
+- Type checking of state (see :ref:`statetypingref` for more information)
diff --git a/content/_sources/reference/integrations/ray.rst.txt b/content/_sources/reference/integrations/ray.rst.txt
new file mode 100644
index 00000000..c781442b
--- /dev/null
+++ b/content/_sources/reference/integrations/ray.rst.txt
@@ -0,0 +1,8 @@
+===
+Ray
+===
+
+The Burr Ray integration allows you to run :ref:`parallel sub-applications <parallelism>` on `Ray <ray.io>`_.
+
+.. autoclass:: burr.integrations.ray.RayExecutor
+ :members:
diff --git a/content/_sources/reference/integrations/streamlit.rst.txt b/content/_sources/reference/integrations/streamlit.rst.txt
new file mode 100644
index 00000000..d24e6417
--- /dev/null
+++ b/content/_sources/reference/integrations/streamlit.rst.txt
@@ -0,0 +1,31 @@
+=======================
+Streamlit
+=======================
+
+Full Streamlit integration. Tough-points are utility functions.
+It is likely this will adapt/change over time, so it is only recommended to use this for debugging/developing.
+
+Install with pypi:
+
+.. code-block:: bash
+
+ pip install burr[streamlit]
+
+.. autoclass:: burr.integrations.streamlit.AppState
+ :members:
+
+.. autofunction:: burr.integrations.streamlit.load_state_from_log_file
+
+.. autofunction:: burr.integrations.streamlit.get_state
+
+.. autofunction:: burr.integrations.streamlit.update_state
+
+.. autofunction:: burr.integrations.streamlit.render_state_machine
+
+.. autofunction:: burr.integrations.streamlit.render_action
+
+.. autofunction:: burr.integrations.streamlit.render_state_results
+
+.. autofunction:: burr.integrations.streamlit.set_slider_to_current
+
+.. autofunction:: burr.integrations.streamlit.render_explorer
diff --git a/content/_sources/reference/integrations/traceloop.rst.txt b/content/_sources/reference/integrations/traceloop.rst.txt
new file mode 100644
index 00000000..c432def5
--- /dev/null
+++ b/content/_sources/reference/integrations/traceloop.rst.txt
@@ -0,0 +1,15 @@
+---------
+Traceloop
+---------
+
+`Traceloop <https://traceloop.io>`_ is an `OpenTelemetry <https://opentelemetry.io/>`_ vendor
+that has a special focus on AI observability.
+
+Integration with Burr is done through the :ref:`opentelemetry integration <opentelintegrationref>`.
+
+See the following resources for more information about how to leverage opentelemetry and traceloop's
+`openllmetry <https://github.com/traceloop/openllmetry/tree/main>`_ library to instrument your
+application:
+
+- `Example in the repository <https://github.com/dagworks-inc/burr/tree/main/examples/opentelemetry>`_
+- `Blog post <https://blog.dagworks.io/p/9ef2488a-ff8a-4feb-b37f-1d9a781068ac/>`_
diff --git a/content/_sources/reference/lifecycle.rst.txt b/content/_sources/reference/lifecycle.rst.txt
new file mode 100644
index 00000000..74f4d5cc
--- /dev/null
+++ b/content/_sources/reference/lifecycle.rst.txt
@@ -0,0 +1,60 @@
+=================
+Lifecycle API
+=================
+
+
+These are the available lifecycle hooks for Burr. Implement these classes
+and add instances to the application builder to customize your state machines's execution.
+
+.. _hooksref:
+.. autoclass:: burr.lifecycle.base.PreRunStepHook
+ :members:
+
+.. autoclass:: burr.lifecycle.base.PreRunStepHookAsync
+ :members:
+
+.. autoclass:: burr.lifecycle.base.PostRunStepHook
+ :members:
+
+.. autoclass:: burr.lifecycle.base.PostRunStepHookAsync
+ :members:
+
+.. autoclass:: burr.lifecycle.base.PostApplicationCreateHook
+ :members:
+
+.. autoclass:: burr.lifecycle.base.PreStartSpanHook
+ :members:
+
+.. autoclass:: burr.lifecycle.base.PreStartSpanHookAsync
+ :members:
+
+.. autoclass:: burr.lifecycle.base.PostEndSpanHook
+ :members:
+
+.. autoclass:: burr.lifecycle.base.PostEndSpanHookAsync
+ :members:
+
+.. autoclass:: burr.lifecycle.base.ExecuteMethod
+ :members:
+
+.. autoclass:: burr.lifecycle.base.PreApplicationExecuteCallHook
+ :members:
+
+.. autoclass:: burr.lifecycle.base.PreApplicationExecuteCallHookAsync
+ :members:
+
+.. autoclass:: burr.lifecycle.base.PostApplicationExecuteCallHook
+ :members:
+
+.. autoclass:: burr.lifecycle.base.PostApplicationExecuteCallHookAsync
+ :members:
+
+These hooks are available for you to use:
+
+.. autoclass:: burr.lifecycle.default.StateAndResultsFullLogger
+
+ .. automethod:: __init__
+
+.. autoclass:: burr.lifecycle.default.SlowDownHook
+
+ .. automethod:: __init__
diff --git a/content/_sources/reference/parallelism.rst.txt b/content/_sources/reference/parallelism.rst.txt
new file mode 100644
index 00000000..a21ec51e
--- /dev/null
+++ b/content/_sources/reference/parallelism.rst.txt
@@ -0,0 +1,27 @@
+.. _parallelismref:
+
+===========
+Parallelism
+===========
+
+Tools to make sub-actions/sub-graphs easier to work with. Read the docs on :ref:`parallelism` for more information.
+
+.. autoclass:: burr.core.parallelism.RunnableGraph
+ :members:
+
+.. autoclass:: burr.core.parallelism.SubGraphTask
+ :members:
+
+.. autoclass:: burr.core.parallelism.TaskBasedParallelAction
+ :members:
+
+.. autoclass:: burr.core.parallelism.MapActionsAndStates
+ :members:
+
+.. autoclass:: burr.core.parallelism.MapActions
+ :members:
+
+.. autoclass:: burr.core.parallelism.MapStates
+ :members:
+
+.. automethod:: burr.core.parallelism.map_reduce_action
diff --git a/content/_sources/reference/persister.rst.txt b/content/_sources/reference/persister.rst.txt
new file mode 100644
index 00000000..e8bd0002
--- /dev/null
+++ b/content/_sources/reference/persister.rst.txt
@@ -0,0 +1,144 @@
+=================
+State Persistence
+=================
+
+.. _persistersref:
+
+Burr provides a set of tools to make loading and saving state easy. These are functions
+that will be used by lifecycle hooks to save and load state.
+
+We currently support the following database integrations:
+
+.. table:: Burr Implemented Persisters
+ :widths: auto
+
+ +-------------+-----------------------------------------------------------------+---------------------------------------------------------------------+
+ | Database | Sync | Async |
+ +=============+===========+=====================================================+===============+=====================================================+
+ | SQLite | sqlite3 | :ref:`SQLitePersister <syncsqliteref>` | aiosqlite | :ref:`AsyncSQLitePersister <asyncsqliteref>` |
+ +-------------+-----------+-----------------------------------------------------+---------------+-----------------------------------------------------+
+ | PostgreSQL | psycopg2 | :ref:`PostgreSQLPersister <syncpostgresref>` | asyncpg | :ref:`AsyncPostgreSQLPersister <asyncpostgresref>` |
+ +-------------+-----------+-----------------------------------------------------+---------------+-----------------------------------------------------+
+ | Redis | redis | :ref:`RedisBasePersister <syncredisref>` | redis.asyncio | :ref:`AsyncRedisBasePersister <asyncredisref>` |
+ +-------------+-----------+-----------------------------------------------------+---------------+-----------------------------------------------------+
+ | MongoDB | pymongo | :ref:`MongoDBBasePersister <syncmongoref>` | ❌ | ❌ |
+ +-------------+-----------+-----------------------------------------------------+---------------+-----------------------------------------------------+
+
+We follow the naming convention ``b_dependency-library``, where the ``b_`` is used to avoid name
+clashing with the underlying library. We chose the library name in case we implement the same database
+persister with different dependency libraries to keep the class naming convention.
+
+If you want to implement your own state persister (to bridge it with a database), you should implement
+the ``BaseStatePersister`` or the ``AsyncBaseStatePersister`` interface.
+
+.. autoclass:: burr.core.persistence.BaseStatePersister
+ :members:
+ :show-inheritance:
+ :inherited-members:
+
+ .. automethod:: __init__
+
+Internally, this interface combines the following two interfaces:
+
+.. autoclass:: burr.core.persistence.BaseStateLoader
+ :members:
+
+ .. automethod:: __init__
+
+.. autoclass:: burr.core.persistence.BaseStateSaver
+ :members:
+ :show-inheritance:
+ :inherited-members:
+
+ .. automethod:: __init__
+
+Note we also have the corresponding async implementations:
+
+.. autoclass:: burr.core.persistence.AsyncBaseStatePersister
+ :members:
+ :show-inheritance:
+ :inherited-members:
+
+ .. automethod:: __init__
+
+Internally, this interface combines the following two interfaces:
+
+.. autoclass:: burr.core.persistence.AsyncBaseStateLoader
+ :members:
+
+ .. automethod:: __init__
+
+.. autoclass:: burr.core.persistence.AsyncBaseStateSaver
+ :members:
+ :show-inheritance:
+ :inherited-members:
+
+ .. automethod:: __init__
+
+
+
+Supported Sync Implementations
+================================
+
+Currently we support the following, although we highly recommend you contribute your own! We will be adding more shortly.
+
+.. _syncsqliteref:
+
+.. autoclass:: burr.core.persistence.SQLitePersister
+ :members:
+
+ .. automethod:: __init__
+
+.. _syncpostgresref:
+
+.. autoclass:: burr.integrations.persisters.b_psycopg2.PostgreSQLPersister
+ :members:
+
+ .. automethod:: __init__
+
+.. _syncredisref:
+
+.. autoclass:: burr.integrations.persisters.b_redis.RedisBasePersister
+ :members:
+
+ .. automethod:: __init__
+
+.. _syncmongoref:
+
+.. autoclass:: burr.integrations.persisters.b_pymongo.MongoDBBasePersister
+ :members:
+
+ .. automethod:: __init__
+
+
+Note that the :py:class:`LocalTrackingClient <burr.tracking.client.LocalTrackingClient>` leverages the :py:class:`BaseStateLoader <burr.core.persistence.BaseStateLoader>` to allow loading state,
+although it uses different mechanisms to save state (as it tracks more than just state).
+
+
+Supported Async Implementations
+================================
+
+.. _asyncpersistersref:
+
+Currently we support the following, although we highly recommend you contribute your own! We will be adding more shortly.
+
+.. _asyncsqliteref:
+
+.. autoclass:: burr.integrations.persisters.b_aiosqlite.AsyncSQLitePersister
+ :members:
+
+ .. automethod:: __init__
+
+.. _asyncpostgresref:
+
+.. autoclass:: burr.integrations.persisters.b_asyncpg.AsyncPostgreSQLPersister
+ :members:
+
+ .. automethod:: __init__
+
+.. _asyncredisref:
+
+.. autoclass:: burr.integrations.persisters.b_redis.AsyncRedisBasePersister
+ :members:
+
+ .. automethod:: __init__
diff --git a/content/_sources/reference/serde.rst.txt b/content/_sources/reference/serde.rst.txt
new file mode 100644
index 00000000..c331c975
--- /dev/null
+++ b/content/_sources/reference/serde.rst.txt
@@ -0,0 +1,50 @@
+.. _serderef:
+
+==================================
+Serialization & Deserialization
+==================================
+
+Burr provides a set of tools to make loading and saving state easy. These are functions
+that will be used by lifecycle hooks to save and load state.
+
+If you want to implement your own serialization and deserialization, you should implement
+the ``serialize`` & ``deserialize`` functions, which act as an interface. ``serialize`` uses
+singledispatch and operates on a Type level. While ``deserialize`` registers a string value
+to a function.
+
+.. automodule:: burr.core.serde
+ :members:
+
+Each serialize function needs to a mirror deserialize function. To know which
+deserialize function to use, the serialize function needs to return a dictionary and have
+``burr.core.serde.KEY`` as one of the keys mapping to the appropriate value.
+This is used to identify the deserialization function to use.
+
+Out of the box Implementations
+==============================
+
+The following implementations are available assuming you have the right dependencies in your environment:
+
+LangChain Objects
+^^^^^^^^^^^^^^^^^
+
+.. automodule:: burr.integrations.serde.langchain
+ :members:
+
+Pandas Objects
+^^^^^^^^^^^^^^^^^
+
+.. automodule:: burr.integrations.serde.pandas
+ :members:
+
+Pickle-able Objects
+^^^^^^^^^^^^^^^^^^^
+
+.. automodule:: burr.integrations.serde.pickle
+ :members:
+
+Pydantic Objects
+^^^^^^^^^^^^^^^^^
+
+.. automodule:: burr.integrations.serde.pydantic
+ :members:
diff --git a/content/_sources/reference/state.rst.txt b/content/_sources/reference/state.rst.txt
new file mode 100644
index 00000000..4e5c3e06
--- /dev/null
+++ b/content/_sources/reference/state.rst.txt
@@ -0,0 +1,18 @@
+=================
+State
+=================
+
+Use the state API to manipulate the state of the application.
+
+.. autoclass:: burr.core.state.State
+ :members:
+
+ .. automethod:: __init__
+
+
+Custom field level serialization and deserialization
+----------------------------------------------------
+Use the following to register custom field level serialization and deserialization functions.
+Note: this registration is global for any state field with the same name.
+
+.. autofunction:: burr.core.state.register_field_serde
diff --git a/content/_sources/reference/telemetry.rst.txt b/content/_sources/reference/telemetry.rst.txt
new file mode 100644
index 00000000..7d7ed800
--- /dev/null
+++ b/content/_sources/reference/telemetry.rst.txt
@@ -0,0 +1,47 @@
+==============================
+Usage analytics + data privacy
+==============================
+
+By default, when using Burr, it collects anonymous usage data to help improve Burr and know where to apply development efforts.
+
+We capture events on the following occasions:
+
+1. When an application is built
+2. When one of the ``execution`` functions is run in ``Application``
+3. When a CLI command is run
+
+The captured data is limited to:
+
+- Operating System and Python version
+- A persistent UUID to indentify the session, stored in ~/.burr.conf.
+- The name of the function/CLI command that was run
+
+If you're worried, see ``telemetry.py`` for details.
+
+If you do not wish to participate, one can opt-out with one of the following methods:
+
+1. Set it to false programmatically in your code before creating a Burr application builder:
+
+.. code-block:: python
+
+ from burr import telemetry
+ telemetry.disable_telemetry()
+
+2. Set the key telemetry_enabled to false in ``~/.burr.conf`` under the DEFAULT section:
+
+.. code-block:: ini
+
+ [DEFAULT]
+ telemetry_enabled = False
+
+3. Set BURR_TELEMETRY_ENABLED=false as an environment variable. Either setting it for your shell session:
+
+.. code-block:: bash
+
+ export BURR_TELEMETRY_ENABLED=false
+
+or passing it as part of the run command:
+
+.. code-block:: bash
+
+ BURR_TELEMETRY_ENABLED=false python NAME_OF_MY_DRIVER.py
diff --git a/content/_sources/reference/tracking.rst.txt b/content/_sources/reference/tracking.rst.txt
new file mode 100644
index 00000000..ea317156
--- /dev/null
+++ b/content/_sources/reference/tracking.rst.txt
@@ -0,0 +1,12 @@
+========
+Tracking
+========
+
+Reference on the Tracking/Telemetry API.
+Rather, you should use this through/in conjunction with :py:meth:`burr.core.application.ApplicationBuilder.with_tracker`.
+
+
+.. autoclass:: burr.tracking.LocalTrackingClient
+ :members:
+
+ .. automethod:: __init__
diff --git a/content/_sources/reference/typing.rst.txt b/content/_sources/reference/typing.rst.txt
new file mode 100644
index 00000000..c8e9017f
--- /dev/null
+++ b/content/_sources/reference/typing.rst.txt
@@ -0,0 +1,19 @@
+.. _statetypingref:
+
+======
+Typing
+======
+
+Reference on the State typing API. Note that docs here are for both core burr and the pydantic plugin
+(which does the interesting typing). For an overview, read :ref:`statetyping`.
+
+
+.. autoclass:: burr.core.typing.TypingSystem
+ :members:
+
+ .. automethod:: __init__
+
+.. autoclass:: burr.integrations.pydantic.PydanticTypingSystem
+ :members:
+
+ .. automethod:: __init__
diff --git a/content/_sources/reference/visibility.rst.txt b/content/_sources/reference/visibility.rst.txt
new file mode 100644
index 00000000..1e9db05c
--- /dev/null
+++ b/content/_sources/reference/visibility.rst.txt
@@ -0,0 +1,22 @@
+.. _visibility:
+
+
+======================
+Tracing Inside Actions
+======================
+
+Tooling for gaining visibility inside actions. You will never instantiate these directly,
+they are all injected by the framework. This is purely for reference.
+
+.. autoclass:: burr.visibility.tracing.TracerFactory
+ :members:
+
+.. autoclass:: burr.visibility.tracing.ActionSpanTracer
+ :members:
+
+
+.. autoclass:: burr.visibility.tracing.ActionSpan
+ :members:
+
+.. autoclass:: burr.visibility.tracing.trace
+ :special-members: __init__
diff --git a/content/_static/basic.css b/content/_static/basic.css
new file mode 100644
index 00000000..4738b2ed
--- /dev/null
+++ b/content/_static/basic.css
@@ -0,0 +1,906 @@
+/*
+ * Sphinx stylesheet -- basic theme.
+ */
+
+/* -- main layout ----------------------------------------------------------- */
+
+div.clearer {
+ clear: both;
+}
+
+div.section::after {
+ display: block;
+ content: '';
+ clear: left;
+}
+
+/* -- relbar ---------------------------------------------------------------- */
+
+div.related {
+ width: 100%;
+ font-size: 90%;
+}
+
+div.related h3 {
+ display: none;
+}
+
+div.related ul {
+ margin: 0;
+ padding: 0 0 0 10px;
+ list-style: none;
+}
+
+div.related li {
+ display: inline;
+}
+
+div.related li.right {
+ float: right;
+ margin-right: 5px;
+}
+
+/* -- sidebar --------------------------------------------------------------- */
+
+div.sphinxsidebarwrapper {
+ padding: 10px 5px 0 10px;
+}
+
+div.sphinxsidebar {
+ float: left;
+ width: 230px;
+ margin-left: -100%;
+ font-size: 90%;
+ word-wrap: break-word;
+ overflow-wrap : break-word;
+}
+
+div.sphinxsidebar ul {
+ list-style: none;
+}
+
+div.sphinxsidebar ul ul,
+div.sphinxsidebar ul.want-points {
+ margin-left: 20px;
+ list-style: square;
+}
+
+div.sphinxsidebar ul ul {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+div.sphinxsidebar form {
+ margin-top: 10px;
+}
+
+div.sphinxsidebar input {
+ border: 1px solid #98dbcc;
+ font-family: sans-serif;
+ font-size: 1em;
+}
+
+div.sphinxsidebar #searchbox form.search {
+ overflow: hidden;
+}
+
+div.sphinxsidebar #searchbox input[type="text"] {
+ float: left;
+ width: 80%;
+ padding: 0.25em;
+ box-sizing: border-box;
+}
+
+div.sphinxsidebar #searchbox input[type="submit"] {
+ float: left;
+ width: 20%;
+ border-left: none;
+ padding: 0.25em;
+ box-sizing: border-box;
+}
+
+
+img {
+ border: 0;
+ max-width: 100%;
+}
+
+/* -- search page ----------------------------------------------------------- */
+
+ul.search {
+ margin-top: 10px;
+}
+
+ul.search li {
+ padding: 5px 0;
+}
+
+ul.search li a {
+ font-weight: bold;
+}
+
+ul.search li p.context {
+ color: #888;
+ margin: 2px 0 0 30px;
+ text-align: left;
+}
+
+ul.keywordmatches li.goodmatch a {
+ font-weight: bold;
+}
+
+/* -- index page ------------------------------------------------------------ */
+
+table.contentstable {
+ width: 90%;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+table.contentstable p.biglink {
+ line-height: 150%;
+}
+
+a.biglink {
+ font-size: 1.3em;
+}
+
+span.linkdescr {
+ font-style: italic;
+ padding-top: 5px;
+ font-size: 90%;
+}
+
+/* -- general index --------------------------------------------------------- */
+
+table.indextable {
+ width: 100%;
+}
+
+table.indextable td {
+ text-align: left;
+ vertical-align: top;
+}
+
+table.indextable ul {
+ margin-top: 0;
+ margin-bottom: 0;
+ list-style-type: none;
+}
+
+table.indextable > tbody > tr > td > ul {
+ padding-left: 0em;
+}
+
+table.indextable tr.pcap {
+ height: 10px;
+}
+
+table.indextable tr.cap {
+ margin-top: 10px;
+ background-color: #f2f2f2;
+}
+
+img.toggler {
+ margin-right: 3px;
+ margin-top: 3px;
+ cursor: pointer;
+}
+
+div.modindex-jumpbox {
+ border-top: 1px solid #ddd;
+ border-bottom: 1px solid #ddd;
+ margin: 1em 0 1em 0;
+ padding: 0.4em;
+}
+
+div.genindex-jumpbox {
+ border-top: 1px solid #ddd;
+ border-bottom: 1px solid #ddd;
+ margin: 1em 0 1em 0;
+ padding: 0.4em;
+}
+
+/* -- domain module index --------------------------------------------------- */
+
+table.modindextable td {
+ padding: 2px;
+ border-collapse: collapse;
+}
+
+/* -- general body styles --------------------------------------------------- */
+
+div.body {
+ min-width: 360px;
+ max-width: 800px;
+}
+
+div.body p, div.body dd, div.body li, div.body blockquote {
+ -moz-hyphens: auto;
+ -ms-hyphens: auto;
+ -webkit-hyphens: auto;
+ hyphens: auto;
+}
+
+a.headerlink {
+ visibility: hidden;
+}
+
+a:visited {
+ color: #551A8B;
+}
+
+h1:hover > a.headerlink,
+h2:hover > a.headerlink,
+h3:hover > a.headerlink,
+h4:hover > a.headerlink,
+h5:hover > a.headerlink,
+h6:hover > a.headerlink,
+dt:hover > a.headerlink,
+caption:hover > a.headerlink,
+p.caption:hover > a.headerlink,
+div.code-block-caption:hover > a.headerlink {
+ visibility: visible;
+}
+
+div.body p.caption {
+ text-align: inherit;
+}
+
+div.body td {
+ text-align: left;
+}
+
+.first {
+ margin-top: 0 !important;
+}
+
+p.rubric {
+ margin-top: 30px;
+ font-weight: bold;
+}
+
+img.align-left, figure.align-left, .figure.align-left, object.align-left {
+ clear: left;
+ float: left;
+ margin-right: 1em;
+}
+
+img.align-right, figure.align-right, .figure.align-right, object.align-right {
+ clear: right;
+ float: right;
+ margin-left: 1em;
+}
+
+img.align-center, figure.align-center, .figure.align-center, object.align-center {
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+img.align-default, figure.align-default, .figure.align-default {
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.align-left {
+ text-align: left;
+}
+
+.align-center {
+ text-align: center;
+}
+
+.align-default {
+ text-align: center;
+}
+
+.align-right {
+ text-align: right;
+}
+
+/* -- sidebars -------------------------------------------------------------- */
+
+div.sidebar,
+aside.sidebar {
+ margin: 0 0 0.5em 1em;
+ border: 1px solid #ddb;
+ padding: 7px;
+ background-color: #ffe;
+ width: 40%;
+ float: right;
+ clear: right;
+ overflow-x: auto;
+}
+
+p.sidebar-title {
+ font-weight: bold;
+}
+
+nav.contents,
+aside.topic,
+div.admonition, div.topic, blockquote {
+ clear: left;
+}
+
+/* -- topics ---------------------------------------------------------------- */
+
+nav.contents,
+aside.topic,
+div.topic {
+ border: 1px solid #ccc;
+ padding: 7px;
+ margin: 10px 0 10px 0;
+}
+
+p.topic-title {
+ font-size: 1.1em;
+ font-weight: bold;
+ margin-top: 10px;
+}
+
+/* -- admonitions ----------------------------------------------------------- */
+
+div.admonition {
+ margin-top: 10px;
+ margin-bottom: 10px;
+ padding: 7px;
+}
+
+div.admonition dt {
+ font-weight: bold;
+}
+
+p.admonition-title {
+ margin: 0px 10px 5px 0px;
+ font-weight: bold;
+}
+
+div.body p.centered {
+ text-align: center;
+ margin-top: 25px;
+}
+
+/* -- content of sidebars/topics/admonitions -------------------------------- */
+
+div.sidebar > :last-child,
+aside.sidebar > :last-child,
+nav.contents > :last-child,
+aside.topic > :last-child,
+div.topic > :last-child,
+div.admonition > :last-child {
+ margin-bottom: 0;
+}
+
+div.sidebar::after,
+aside.sidebar::after,
+nav.contents::after,
+aside.topic::after,
+div.topic::after,
+div.admonition::after,
+blockquote::after {
+ display: block;
+ content: '';
+ clear: both;
+}
+
+/* -- tables ---------------------------------------------------------------- */
+
+table.docutils {
+ margin-top: 10px;
+ margin-bottom: 10px;
+ border: 0;
+ border-collapse: collapse;
+}
+
+table.align-center {
+ margin-left: auto;
+ margin-right: auto;
+}
+
+table.align-default {
+ margin-left: auto;
... 53733 lines suppressed ...