working on workshops

This commit is contained in:
dexhorthy
2025-07-16 09:54:00 -07:00
parent f8c5b1608a
commit f5412c10f4
13 changed files with 900 additions and 1 deletions

View 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
}

View 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
}

View File

@@ -0,0 +1,3 @@
# Hack Directory Notes
This is a uv project - use `uv add` for dependencies and `uv run` to execute scripts.

View File

@@ -6,4 +6,6 @@ readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"jupyter>=1.1.1",
"nbformat>=5.10.4",
"pyyaml>=6.0.2",
]

View 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>")

View 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
}

View 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!'"

View File

@@ -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" },
]

View 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}

View 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()

View File

@@ -0,0 +1,5 @@
def hello():
print('hello, world!')
def main():
hello()

View 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

View 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)