mirror of
https://github.com/humanlayer/12-factor-agents.git
synced 2025-08-20 18:59:53 +03:00
working on workshops
This commit is contained in:
331
workshops/2025-07-16/hack/chapter0-1_test.ipynb
Normal file
331
workshops/2025-07-16/hack/chapter0-1_test.ipynb
Normal file
@@ -0,0 +1,331 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "be04e2a1",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Building the 12-factor agent template from scratch in Python"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "bd951167",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Steps to start from a bare Python repo and build up a 12-factor agent. This walkthrough will guide you through creating a Python agent that follows the 12-factor methodology with BAML."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8c8bd795",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Chapter 0 - Hello World"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "65edc632",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's start with a basic Python setup and a hello world program."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "349851d1",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This guide will walk you through building agents in Python with BAML.\n",
|
||||
"\n",
|
||||
"We'll start simple with a hello world program and gradually build up to a full agent.\n",
|
||||
"\n",
|
||||
"For this notebook, you'll need to have your OpenAI API key saved in Google Colab secrets.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "81c54be5",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Here's our simple hello world program:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "0ce40eda",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ./walkthrough/00-main.py\n",
|
||||
"def hello():\n",
|
||||
" print('hello, world!')\n",
|
||||
"\n",
|
||||
"def main():\n",
|
||||
" hello()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "0a5adaf8",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's run it to verify it works:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "5068756a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"main()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f3f75943",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Chapter 1 - CLI and Agent Loop"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3a41e811",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now let's add BAML and create our first agent with a CLI interface."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f390af8c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"In this chapter, we'll integrate BAML to create an AI agent that can respond to user input.\n",
|
||||
"\n",
|
||||
"First, let's set up BAML support in our notebook.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3d3cf8ff",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### BAML Setup\n",
|
||||
"\n",
|
||||
"Don't worry too much about this setup code - it will make sense later! For now, just know that:\n",
|
||||
"- BAML is a tool for working with language models\n",
|
||||
"- We need some special setup code to make it work nicely in Google Colab\n",
|
||||
"- The `get_baml_client()` function will be used to interact with AI models"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d6787b0e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!pip install baml-py"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "0a87bf65",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import subprocess\n",
|
||||
"from google.colab import userdata\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"def baml_generate():\n",
|
||||
" try:\n",
|
||||
" result = subprocess.run(\n",
|
||||
" [\"baml-cli\", \"generate\"],\n",
|
||||
" check=True,\n",
|
||||
" capture_output=True,\n",
|
||||
" text=True\n",
|
||||
" )\n",
|
||||
" if result.stdout:\n",
|
||||
" print(\"[baml-cli generate]\\n\", result.stdout)\n",
|
||||
" if result.stderr:\n",
|
||||
" print(\"[baml-cli generate]\\n\", result.stderr)\n",
|
||||
" except subprocess.CalledProcessError as e:\n",
|
||||
" msg = (\n",
|
||||
" f\"`baml-cli generate` failed with exit code {e.returncode}\\n\"\n",
|
||||
" f\"--- STDOUT ---\\n{e.stdout}\\n\"\n",
|
||||
" f\"--- STDERR ---\\n{e.stderr}\"\n",
|
||||
" )\n",
|
||||
" raise RuntimeError(msg) from None\n",
|
||||
"\n",
|
||||
"def get_baml_client():\n",
|
||||
" \"\"\"\n",
|
||||
" a bunch of fun jank to work around the google colab import cache\n",
|
||||
" \"\"\"\n",
|
||||
" os.environ['OPENAI_API_KEY'] = userdata.get('OPENAI_API_KEY')\n",
|
||||
" \n",
|
||||
" baml_generate()\n",
|
||||
" \n",
|
||||
" import importlib\n",
|
||||
" import baml_client\n",
|
||||
" importlib.reload(baml_client)\n",
|
||||
" return baml_client.sync_client.b\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d59b175f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!baml-cli init"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "15a0e941",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now let's create our agent that will use BAML to process user input.\n",
|
||||
"\n",
|
||||
"First, we'll define the core agent logic:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "70570b76",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ./walkthrough/01-agent.py\n",
|
||||
"import json\n",
|
||||
"from typing import Dict, Any, List\n",
|
||||
"\n",
|
||||
"# tool call or a respond to human tool\n",
|
||||
"AgentResponse = Any # This will be the return type from b.DetermineNextStep\n",
|
||||
"\n",
|
||||
"class Event:\n",
|
||||
" def __init__(self, type: str, data: Any):\n",
|
||||
" self.type = type\n",
|
||||
" self.data = data\n",
|
||||
"\n",
|
||||
"class Thread:\n",
|
||||
" def __init__(self, events: List[Dict[str, Any]]):\n",
|
||||
" self.events = events\n",
|
||||
" \n",
|
||||
" def serialize_for_llm(self):\n",
|
||||
" # can change this to whatever custom serialization you want to do, XML, etc\n",
|
||||
" # e.g. https://github.com/got-agents/agents/blob/59ebbfa236fc376618f16ee08eb0f3bf7b698892/linear-assistant-ts/src/agent.ts#L66-L105\n",
|
||||
" return json.dumps(self.events)\n",
|
||||
"\n",
|
||||
"# right now this just runs one turn with the LLM, but\n",
|
||||
"# we'll update this function to handle all the agent logic\n",
|
||||
"def agent_loop(thread: Thread) -> AgentResponse:\n",
|
||||
" b = get_baml_client() # This will be defined by the BAML setup\n",
|
||||
" next_step = b.DetermineNextStep(thread.serialize_for_llm())\n",
|
||||
" return next_step"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ed9ef001",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Next, we need to define the BAML function that our agent will use.\n",
|
||||
"\n",
|
||||
"This BAML file defines what our agent can do:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "87d1ffc3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!curl -fsSL -o baml_src/01-agent.baml https://raw.githubusercontent.com/humanlayer/12-factor-agents/refs/heads/main/workshops/2025-07-16/./walkthrough/01-agent.baml && cat baml_src/01-agent.baml"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "cf84ac22",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now let's create our main function that simulates command-line arguments:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "430d840b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ./walkthrough/01-main.py\n",
|
||||
"import sys\n",
|
||||
"\n",
|
||||
"def main():\n",
|
||||
" # Set default args if none provided\n",
|
||||
" if len(sys.argv) < 2:\n",
|
||||
" sys.argv = [\"notebook\", \"hello from the notebook!\"]\n",
|
||||
" \n",
|
||||
" # Get command line arguments, skipping the first one (script name)\n",
|
||||
" args = sys.argv[1:]\n",
|
||||
" \n",
|
||||
" if len(args) == 0:\n",
|
||||
" print(\"Error: Please provide a message as a command line argument\", file=sys.stderr)\n",
|
||||
" return\n",
|
||||
" \n",
|
||||
" # Join all arguments into a single message\n",
|
||||
" message = \" \".join(args)\n",
|
||||
" \n",
|
||||
" # Create a new thread with the user's message as the initial event\n",
|
||||
" thread = Thread([{\"type\": \"user_input\", \"data\": message}])\n",
|
||||
" \n",
|
||||
" # Run the agent loop with the thread\n",
|
||||
" result = agent_loop(thread)\n",
|
||||
" print(result)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "938ca8b7",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's test our agent! You can modify the sys.argv line in the cell above to send different messages.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "8ea2980e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"baml_generate()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "6a10500c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"main()"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
92
workshops/2025-07-16/hack/chapter0_test.ipynb
Normal file
92
workshops/2025-07-16/hack/chapter0_test.ipynb
Normal file
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "889aab5a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Building the 12-factor agent template from scratch in Python"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "154f7699",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Steps to start from a bare Python repo and build up a 12-factor agent. This walkthrough will guide you through creating a Python agent that follows the 12-factor methodology with BAML."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "18d91ea3",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Chapter 0 - Hello World"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "0f435832",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's start with a basic Python setup and a hello world program."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a1a09ab1",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This guide will walk you through building agents in Python with BAML.\n",
|
||||
"\n",
|
||||
"We'll start simple with a hello world program and gradually build up to a full agent.\n",
|
||||
"\n",
|
||||
"For this notebook, you'll need to have your OpenAI API key saved in Google Colab secrets.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6311a11e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Here's our simple hello world program:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "41d5d158",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ./walkthrough/00-main.py\n",
|
||||
"def hello():\n",
|
||||
" print('hello, world!')\n",
|
||||
"\n",
|
||||
"def main():\n",
|
||||
" hello()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a925428d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's run it to verify it works:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "7e38f57f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"main()"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
3
workshops/2025-07-16/hack/claude.md
Normal file
3
workshops/2025-07-16/hack/claude.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Hack Directory Notes
|
||||
|
||||
This is a uv project - use `uv add` for dependencies and `uv run` to execute scripts.
|
||||
@@ -6,4 +6,6 @@ readme = "README.md"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
"jupyter>=1.1.1",
|
||||
"nbformat>=5.10.4",
|
||||
"pyyaml>=6.0.2",
|
||||
]
|
||||
|
||||
48
workshops/2025-07-16/hack/test_notebook.py
Normal file
48
workshops/2025-07-16/hack/test_notebook.py
Normal file
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Test notebook execution without requiring Google Colab."""
|
||||
|
||||
import nbformat
|
||||
from nbconvert.preprocessors import ExecutePreprocessor
|
||||
import sys
|
||||
|
||||
def test_notebook(notebook_path):
|
||||
"""Execute notebook and check for errors."""
|
||||
print(f"Testing notebook: {notebook_path}")
|
||||
|
||||
# Read notebook
|
||||
with open(notebook_path, 'r') as f:
|
||||
nb = nbformat.read(f, as_version=4)
|
||||
|
||||
# Skip cells that require Google Colab
|
||||
cells_to_execute = []
|
||||
for cell in nb.cells:
|
||||
if cell.cell_type == 'code':
|
||||
# Skip cells with Google Colab imports or baml-cli commands
|
||||
if 'google.colab' in cell.source or 'baml-cli' in cell.source:
|
||||
print(f"Skipping cell with Colab/BAML dependencies: {cell.source[:50]}...")
|
||||
continue
|
||||
# Skip pip install cells
|
||||
if cell.source.strip().startswith('!pip'):
|
||||
print(f"Skipping pip install cell")
|
||||
continue
|
||||
cells_to_execute.append(cell)
|
||||
|
||||
# Create a new notebook with only executable cells
|
||||
test_nb = nbformat.v4.new_notebook()
|
||||
test_nb.cells = cells_to_execute
|
||||
|
||||
# Execute
|
||||
ep = ExecutePreprocessor(timeout=10, kernel_name='python3')
|
||||
try:
|
||||
ep.preprocess(test_nb, {'metadata': {'path': '.'}})
|
||||
print("✅ Notebook executed successfully!")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ Error executing notebook: {e}")
|
||||
return False
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) > 1:
|
||||
test_notebook(sys.argv[1])
|
||||
else:
|
||||
print("Usage: python test_notebook.py <notebook_path>")
|
||||
122
workshops/2025-07-16/hack/test_output.ipynb
Normal file
122
workshops/2025-07-16/hack/test_output.ipynb
Normal file
@@ -0,0 +1,122 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "fedb829e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Test Walkthrough"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3c45031e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This is a test walkthrough to verify the script works."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "6ce41a36",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!pip install baml-py"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "ec52f527",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import subprocess\n",
|
||||
"from google.colab import userdata\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"def baml_generate():\n",
|
||||
" try:\n",
|
||||
" result = subprocess.run(\n",
|
||||
" [\"baml-cli\", \"generate\"],\n",
|
||||
" check=True,\n",
|
||||
" capture_output=True,\n",
|
||||
" text=True\n",
|
||||
" )\n",
|
||||
" if result.stdout:\n",
|
||||
" print(\"[baml-cli generate]\\n\", result.stdout)\n",
|
||||
" if result.stderr:\n",
|
||||
" print(\"[baml-cli generate]\\n\", result.stderr)\n",
|
||||
" except subprocess.CalledProcessError as e:\n",
|
||||
" msg = (\n",
|
||||
" f\"`baml-cli generate` failed with exit code {e.returncode}\\n\"\n",
|
||||
" f\"--- STDOUT ---\\n{e.stdout}\\n\"\n",
|
||||
" f\"--- STDERR ---\\n{e.stderr}\"\n",
|
||||
" )\n",
|
||||
" raise RuntimeError(msg) from None\n",
|
||||
"\n",
|
||||
"def get_baml_client():\n",
|
||||
" \"\"\"\n",
|
||||
" a bunch of fun jank to work around the google colab import cache\n",
|
||||
" \"\"\"\n",
|
||||
" os.environ['OPENAI_API_KEY'] = userdata.get('OPENAI_API_KEY')\n",
|
||||
" \n",
|
||||
" baml_generate()\n",
|
||||
" \n",
|
||||
" import importlib\n",
|
||||
" import baml_client\n",
|
||||
" importlib.reload(baml_client)\n",
|
||||
" return baml_client.sync_client.b\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "50969900",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!baml-cli init"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "296fdf4b",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Test Section"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "54ee6e14",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This is a test section."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "87503005",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This is a test markdown cell"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "ddb13aa9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!echo 'Hello from command!'"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
13
workshops/2025-07-16/hack/test_walkthrough.yaml
Normal file
13
workshops/2025-07-16/hack/test_walkthrough.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
title: "Test Walkthrough"
|
||||
text: "This is a test walkthrough to verify the script works."
|
||||
|
||||
targets:
|
||||
- ipynb: "./test_output.ipynb"
|
||||
|
||||
sections:
|
||||
- name: test-section
|
||||
title: "Test Section"
|
||||
text: "This is a test section."
|
||||
steps:
|
||||
- text: "This is a test markdown cell"
|
||||
- command: "echo 'Hello from command!'"
|
||||
8
workshops/2025-07-16/hack/uv.lock
generated
8
workshops/2025-07-16/hack/uv.lock
generated
@@ -1512,7 +1512,13 @@ version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "jupyter" },
|
||||
{ name = "nbformat" },
|
||||
{ name = "pyyaml" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "jupyter", specifier = ">=1.1.1" }]
|
||||
requires-dist = [
|
||||
{ name = "jupyter", specifier = ">=1.1.1" },
|
||||
{ name = "nbformat", specifier = ">=5.10.4" },
|
||||
{ name = "pyyaml", specifier = ">=6.0.2" },
|
||||
]
|
||||
|
||||
48
workshops/2025-07-16/hack/walkthrough_python.yaml
Normal file
48
workshops/2025-07-16/hack/walkthrough_python.yaml
Normal file
@@ -0,0 +1,48 @@
|
||||
title: "Building the 12-factor agent template from scratch in Python"
|
||||
text: "Steps to start from a bare Python repo and build up a 12-factor agent. This walkthrough will guide you through creating a Python agent that follows the 12-factor methodology with BAML."
|
||||
|
||||
targets:
|
||||
- ipynb: "./build/workshop-2025-07-16.ipynb"
|
||||
|
||||
sections:
|
||||
- name: hello-world
|
||||
title: "Chapter 0 - Hello World"
|
||||
text: "Let's start with a basic Python setup and a hello world program."
|
||||
steps:
|
||||
- text: |
|
||||
This guide will walk you through building agents in Python with BAML.
|
||||
|
||||
We'll start simple with a hello world program and gradually build up to a full agent.
|
||||
|
||||
For this notebook, you'll need to have your OpenAI API key saved in Google Colab secrets.
|
||||
|
||||
- text: "Here's our simple hello world program:"
|
||||
- file: {src: ./walkthrough/00-main.py}
|
||||
- text: "Let's run it to verify it works:"
|
||||
- run_main: {regenerate_baml: false}
|
||||
|
||||
- name: cli-and-agent
|
||||
title: "Chapter 1 - CLI and Agent Loop"
|
||||
text: "Now let's add BAML and create our first agent with a CLI interface."
|
||||
steps:
|
||||
- text: |
|
||||
In this chapter, we'll integrate BAML to create an AI agent that can respond to user input.
|
||||
|
||||
First, let's set up BAML support in our notebook.
|
||||
- baml_setup: true
|
||||
- text: |
|
||||
Now let's create our agent that will use BAML to process user input.
|
||||
|
||||
First, we'll define the core agent logic:
|
||||
- file: {src: ./walkthrough/01-agent.py}
|
||||
- text: |
|
||||
Next, we need to define the BAML function that our agent will use.
|
||||
|
||||
This BAML file defines what our agent can do:
|
||||
- fetch_file: {src: ./walkthrough/01-agent.baml, dest: baml_src/01-agent.baml}
|
||||
- text: |
|
||||
Now let's create our main function that simulates command-line arguments:
|
||||
- file: {src: ./walkthrough/01-main.py}
|
||||
- text: |
|
||||
Let's test our agent! You can modify the sys.argv line in the cell above to send different messages.
|
||||
- run_main: {regenerate_baml: true}
|
||||
180
workshops/2025-07-16/hack/walkthroughgen_py.py
Executable file
180
workshops/2025-07-16/hack/walkthroughgen_py.py
Executable file
@@ -0,0 +1,180 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Convert walkthrough.yaml to Jupyter notebook for BAML Python tutorials."""
|
||||
|
||||
import yaml
|
||||
import nbformat
|
||||
from nbformat.v4 import new_notebook, new_markdown_cell, new_code_cell
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
|
||||
def create_baml_setup_cells(nb):
|
||||
"""Add BAML setup cells with explanation."""
|
||||
# Add explanation markdown
|
||||
explanation = """### BAML Setup
|
||||
|
||||
Don't worry too much about this setup code - it will make sense later! For now, just know that:
|
||||
- BAML is a tool for working with language models
|
||||
- We need some special setup code to make it work nicely in Google Colab
|
||||
- The `get_baml_client()` function will be used to interact with AI models"""
|
||||
nb.cells.append(new_markdown_cell(explanation))
|
||||
|
||||
# First cell: Install baml-py
|
||||
install_code = "!pip install baml-py"
|
||||
nb.cells.append(new_code_cell(install_code))
|
||||
|
||||
# Second cell: Helper functions
|
||||
setup_code = '''import subprocess
|
||||
from google.colab import userdata
|
||||
import os
|
||||
|
||||
def baml_generate():
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["baml-cli", "generate"],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
if result.stdout:
|
||||
print("[baml-cli generate]\\n", result.stdout)
|
||||
if result.stderr:
|
||||
print("[baml-cli generate]\\n", result.stderr)
|
||||
except subprocess.CalledProcessError as e:
|
||||
msg = (
|
||||
f"`baml-cli generate` failed with exit code {e.returncode}\\n"
|
||||
f"--- STDOUT ---\\n{e.stdout}\\n"
|
||||
f"--- STDERR ---\\n{e.stderr}"
|
||||
)
|
||||
raise RuntimeError(msg) from None
|
||||
|
||||
def get_baml_client():
|
||||
"""
|
||||
a bunch of fun jank to work around the google colab import cache
|
||||
"""
|
||||
os.environ['OPENAI_API_KEY'] = userdata.get('OPENAI_API_KEY')
|
||||
|
||||
baml_generate()
|
||||
|
||||
import importlib
|
||||
import baml_client
|
||||
importlib.reload(baml_client)
|
||||
return baml_client.sync_client.b
|
||||
'''
|
||||
nb.cells.append(new_code_cell(setup_code))
|
||||
|
||||
# Third cell: Initialize BAML
|
||||
init_code = "!baml-cli init"
|
||||
nb.cells.append(new_code_cell(init_code))
|
||||
|
||||
def process_step(nb, step, base_path, current_functions):
|
||||
"""Process different step types."""
|
||||
if 'text' in step:
|
||||
# Add markdown cell
|
||||
nb.cells.append(new_markdown_cell(step['text']))
|
||||
|
||||
if 'baml_setup' in step:
|
||||
# Add BAML setup cells
|
||||
create_baml_setup_cells(nb)
|
||||
|
||||
if 'file' in step:
|
||||
src = step['file']['src']
|
||||
# For Python files, add the entire file content as a code cell
|
||||
if src.endswith('.py'):
|
||||
# Handle relative paths that start with ./
|
||||
if src.startswith('./'):
|
||||
file_path = base_path.parent / src[2:]
|
||||
else:
|
||||
file_path = base_path / src
|
||||
|
||||
if file_path.exists():
|
||||
with open(file_path, 'r') as f:
|
||||
content = f.read()
|
||||
# Add filename as comment at top
|
||||
code_with_header = f"# {src}\n{content}"
|
||||
nb.cells.append(new_code_cell(code_with_header))
|
||||
else:
|
||||
print(f"Warning: File not found: {file_path}")
|
||||
nb.cells.append(new_markdown_cell(f"**Error: File not found: {src}**"))
|
||||
|
||||
if 'fetch_file' in step:
|
||||
# Fetch BAML file from GitHub
|
||||
src = step['fetch_file']['src']
|
||||
dest = step['fetch_file']['dest']
|
||||
github_url = f"https://raw.githubusercontent.com/humanlayer/12-factor-agents/refs/heads/main/workshops/2025-07-16/{src}"
|
||||
command = f"!curl -fsSL -o {dest} {github_url} && cat {dest}"
|
||||
nb.cells.append(new_code_cell(command))
|
||||
|
||||
if 'dir' in step:
|
||||
# Create directory
|
||||
path = step['dir']['path']
|
||||
command = f"!mkdir -p {path}"
|
||||
nb.cells.append(new_code_cell(command))
|
||||
|
||||
if 'command' in step:
|
||||
# Add command as code cell
|
||||
command = step['command'].strip()
|
||||
# Convert to notebook-style command
|
||||
if not command.startswith('!'):
|
||||
command = f"!{command}"
|
||||
nb.cells.append(new_code_cell(command))
|
||||
|
||||
if 'run_main' in step:
|
||||
# Run main function
|
||||
regenerate = step['run_main'].get('regenerate_baml', False)
|
||||
if regenerate:
|
||||
nb.cells.append(new_code_cell("baml_generate()"))
|
||||
nb.cells.append(new_code_cell("main()"))
|
||||
|
||||
def convert_walkthrough_to_notebook(yaml_path, output_path):
|
||||
"""Convert walkthrough.yaml to Jupyter notebook."""
|
||||
# Load YAML
|
||||
with open(yaml_path, 'r') as f:
|
||||
walkthrough = yaml.safe_load(f)
|
||||
|
||||
# Create notebook
|
||||
nb = new_notebook()
|
||||
|
||||
# Add title
|
||||
title = walkthrough.get('title', 'Walkthrough')
|
||||
nb.cells.append(new_markdown_cell(f"# {title}"))
|
||||
|
||||
# Add description
|
||||
if 'text' in walkthrough:
|
||||
nb.cells.append(new_markdown_cell(walkthrough['text']))
|
||||
|
||||
# Process sections
|
||||
base_path = Path(yaml_path).parent
|
||||
current_functions = {}
|
||||
|
||||
for section in walkthrough.get('sections', []):
|
||||
# Add section title
|
||||
section_title = section.get('title', section.get('name', 'Section'))
|
||||
nb.cells.append(new_markdown_cell(f"## {section_title}"))
|
||||
|
||||
# Add section description
|
||||
if 'text' in section:
|
||||
nb.cells.append(new_markdown_cell(section['text']))
|
||||
|
||||
# Process steps
|
||||
for step in section.get('steps', []):
|
||||
process_step(nb, step, base_path, current_functions)
|
||||
|
||||
# Write notebook
|
||||
with open(output_path, 'w') as f:
|
||||
nbformat.write(nb, f)
|
||||
|
||||
print(f"Generated notebook: {output_path}")
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Convert walkthrough.yaml to Jupyter notebook')
|
||||
parser.add_argument('yaml_file', help='Path to walkthrough.yaml')
|
||||
parser.add_argument('-o', '--output', default='output.ipynb', help='Output notebook file')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
convert_walkthrough_to_notebook(args.yaml_file, args.output)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
5
workshops/2025-07-16/walkthrough/00-main.py
Normal file
5
workshops/2025-07-16/walkthrough/00-main.py
Normal file
@@ -0,0 +1,5 @@
|
||||
def hello():
|
||||
print('hello, world!')
|
||||
|
||||
def main():
|
||||
hello()
|
||||
26
workshops/2025-07-16/walkthrough/01-agent.py
Normal file
26
workshops/2025-07-16/walkthrough/01-agent.py
Normal file
@@ -0,0 +1,26 @@
|
||||
import json
|
||||
from typing import Dict, Any, List
|
||||
|
||||
# tool call or a respond to human tool
|
||||
AgentResponse = Any # This will be the return type from b.DetermineNextStep
|
||||
|
||||
class Event:
|
||||
def __init__(self, type: str, data: Any):
|
||||
self.type = type
|
||||
self.data = data
|
||||
|
||||
class Thread:
|
||||
def __init__(self, events: List[Dict[str, Any]]):
|
||||
self.events = events
|
||||
|
||||
def serialize_for_llm(self):
|
||||
# can change this to whatever custom serialization you want to do, XML, etc
|
||||
# e.g. https://github.com/got-agents/agents/blob/59ebbfa236fc376618f16ee08eb0f3bf7b698892/linear-assistant-ts/src/agent.ts#L66-L105
|
||||
return json.dumps(self.events)
|
||||
|
||||
# right now this just runs one turn with the LLM, but
|
||||
# we'll update this function to handle all the agent logic
|
||||
def agent_loop(thread: Thread) -> AgentResponse:
|
||||
b = get_baml_client() # This will be defined by the BAML setup
|
||||
next_step = b.DetermineNextStep(thread.serialize_for_llm())
|
||||
return next_step
|
||||
23
workshops/2025-07-16/walkthrough/01-main.py
Normal file
23
workshops/2025-07-16/walkthrough/01-main.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import sys
|
||||
|
||||
def main():
|
||||
# Set default args if none provided
|
||||
if len(sys.argv) < 2:
|
||||
sys.argv = ["notebook", "hello from the notebook!"]
|
||||
|
||||
# Get command line arguments, skipping the first one (script name)
|
||||
args = sys.argv[1:]
|
||||
|
||||
if len(args) == 0:
|
||||
print("Error: Please provide a message as a command line argument", file=sys.stderr)
|
||||
return
|
||||
|
||||
# Join all arguments into a single message
|
||||
message = " ".join(args)
|
||||
|
||||
# Create a new thread with the user's message as the initial event
|
||||
thread = Thread([{"type": "user_input", "data": message}])
|
||||
|
||||
# Run the agent loop with the thread
|
||||
result = agent_loop(thread)
|
||||
print(result)
|
||||
Reference in New Issue
Block a user