mirror of
https://github.com/humanlayer/12-factor-agents.git
synced 2025-08-20 18:59:53 +03:00
chapter 3
This commit is contained in:
1
workshops/2025-07-16/.gitignore
vendored
1
workshops/2025-07-16/.gitignore
vendored
@@ -4,3 +4,4 @@ package.json
|
||||
package-lock.json
|
||||
tsconfig.json
|
||||
build/
|
||||
tmp/
|
||||
|
||||
352
workshops/2025-07-16/hack/chapter0-1_final.ipynb
Normal file
352
workshops/2025-07-16/hack/chapter0-1_final.ipynb
Normal file
@@ -0,0 +1,352 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "dd43ecb0",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Building the 12-factor agent template from scratch in Python"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "51248df6",
|
||||
"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": "2b3f7aa8",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Chapter 0 - Hello World"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6cfe848d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's start with a basic Python setup and a hello world program."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "55dc9f35",
|
||||
"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": "488986b7",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Here's our simple hello world program:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "401582ef",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ./walkthrough/00-main.py\n",
|
||||
"def hello():\n",
|
||||
" print('hello, world!')\n",
|
||||
"\n",
|
||||
"def main():\n",
|
||||
" hello()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8971bfd3",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's run it to verify it works:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "afec6e79",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"main()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c4eb5aa5",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Chapter 1 - CLI and Agent Loop"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3856fb4c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now let's add BAML and create our first agent with a CLI interface."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3aa2700d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"In this chapter, we'll integrate BAML to create an AI agent that can respond to user input.\n",
|
||||
"\n",
|
||||
"## What is BAML?\n",
|
||||
"\n",
|
||||
"BAML (Boundary Markup Language) is a domain-specific language designed to help developers build reliable AI workflows and agents. Created by [BoundaryML](https://www.boundaryml.com/) (a Y Combinator W23 company), BAML adds the engineering to prompt engineering.\n",
|
||||
"\n",
|
||||
"### Why BAML?\n",
|
||||
"\n",
|
||||
"- **Type-safe outputs**: Get fully type-safe outputs from LLMs, even when streaming\n",
|
||||
"- **Language agnostic**: Works with Python, TypeScript, Ruby, Go, and more\n",
|
||||
"- **LLM agnostic**: Works with any LLM provider (OpenAI, Anthropic, etc.)\n",
|
||||
"- **Better performance**: State-of-the-art structured outputs that outperform even OpenAI's native function calling\n",
|
||||
"- **Developer-friendly**: Native VSCode extension with syntax highlighting, autocomplete, and interactive playground\n",
|
||||
"\n",
|
||||
"### Learn More\n",
|
||||
"\n",
|
||||
"- 📚 [Official Documentation](https://docs.boundaryml.com/home)\n",
|
||||
"- 💻 [GitHub Repository](https://github.com/BoundaryML/baml)\n",
|
||||
"- 🎯 [What is BAML?](https://docs.boundaryml.com/guide/introduction/what-is-baml)\n",
|
||||
"- 📖 [BAML Examples](https://github.com/BoundaryML/baml-examples)\n",
|
||||
"- 🏢 [Company Website](https://www.boundaryml.com/)\n",
|
||||
"- 📰 [Blog: AI Agents Need a New Syntax](https://www.boundaryml.com/blog/ai-agents-need-new-syntax)\n",
|
||||
"\n",
|
||||
"BAML turns prompt engineering into schema engineering, where you focus on defining the structure of your data rather than wrestling with prompts. This approach leads to more reliable and maintainable AI applications.\n",
|
||||
"\n",
|
||||
"### Note on Developer Experience\n",
|
||||
"\n",
|
||||
"BAML works much better in VS Code with their official extension, which provides syntax highlighting, autocomplete, inline testing, and an interactive playground. However, for this notebook tutorial, we'll work with BAML files directly without the enhanced IDE features.\n",
|
||||
"\n",
|
||||
"First, let's set up BAML support in our notebook.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "44c77bbd",
|
||||
"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": "9e719c4a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!pip install baml-py"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "9b8fb003",
|
||||
"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": "2de498d9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!baml-cli init"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "41cba04b",
|
||||
"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": "71ca1e48",
|
||||
"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": "774aaa2c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Next, we need to define the BAML function that our agent will use.\n",
|
||||
"\n",
|
||||
"### Understanding BAML Syntax\n",
|
||||
"\n",
|
||||
"BAML files define:\n",
|
||||
"- **Classes**: Structured output schemas (like `DoneForNow` below)\n",
|
||||
"- **Functions**: AI-powered functions that take inputs and return structured outputs\n",
|
||||
"- **Tests**: Example inputs/outputs to validate your prompts\n",
|
||||
"\n",
|
||||
"This BAML file defines what our agent can do:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "e29d0763",
|
||||
"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": "9ffae736",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now let's create our main function that accepts a message parameter:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "5cd3057c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ./walkthrough/01-main.py\n",
|
||||
"def main(message=\"hello from the notebook!\"):\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": "4ac99573",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's test our agent! Try calling main() with different messages:\n",
|
||||
"- `main(\"What's the weather like?\")`\n",
|
||||
"- `main(\"Tell me a joke\")`\n",
|
||||
"- `main(\"How are you doing today?\")`\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "967e398c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"baml_generate()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "5b128932",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"main(\"Hello from the Python notebook!\")"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
318
workshops/2025-07-16/hack/chapter0-1_test_v2.ipynb
Normal file
318
workshops/2025-07-16/hack/chapter0-1_test_v2.ipynb
Normal file
@@ -0,0 +1,318 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "88f860b5",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Building the 12-factor agent template from scratch in Python"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7ee779c8",
|
||||
"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": "ee1814f3",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Chapter 0 - Hello World"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "bf8c2eed",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's start with a basic Python setup and a hello world program."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "bd3f0e2d",
|
||||
"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": "6596bb81",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Here's our simple hello world program:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "e4f0678b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ./walkthrough/00-main.py\n",
|
||||
"def hello():\n",
|
||||
" print('hello, world!')\n",
|
||||
"\n",
|
||||
"def main():\n",
|
||||
" hello()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ca56296d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's run it to verify it works:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "b94b2080",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"main()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2e89c0c8",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Chapter 1 - CLI and Agent Loop"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ced8aec5",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now let's add BAML and create our first agent with a CLI interface."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "eb350b00",
|
||||
"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": "cc8413c7",
|
||||
"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": "d3daef39",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!pip install baml-py"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "0f28859f",
|
||||
"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": "bacf7469",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!baml-cli init"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "10d6c7b0",
|
||||
"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": "acbfe988",
|
||||
"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": "3e0876e7",
|
||||
"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": "bc8da38e",
|
||||
"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": "0740f7aa",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now let's create our main function that accepts a message parameter:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "93fc3916",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ./walkthrough/01-main.py\n",
|
||||
"def main(message=\"hello from the notebook!\"):\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": "f0d4bf23",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's test our agent! Try calling main() with different messages:\n",
|
||||
"- `main(\"What's the weather like?\")`\n",
|
||||
"- `main(\"Tell me a joke\")`\n",
|
||||
"- `main(\"How are you doing today?\")`\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "5a6685c6",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"baml_generate()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "0f28951c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"main(\"Hello from the Python notebook!\")"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
477
workshops/2025-07-16/hack/chapter0-2_fixed.ipynb
Normal file
477
workshops/2025-07-16/hack/chapter0-2_fixed.ipynb
Normal file
@@ -0,0 +1,477 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "acc41186",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Building the 12-factor agent template from scratch in Python"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8fe9763f",
|
||||
"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": "4123e288",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Chapter 0 - Hello World"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "94bee0c3",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's start with a basic Python setup and a hello world program."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "48330d87",
|
||||
"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": "8ed91b3c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Here's our simple hello world program:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "e407a39f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ./walkthrough/00-main.py\n",
|
||||
"def hello():\n",
|
||||
" print('hello, world!')\n",
|
||||
"\n",
|
||||
"def main():\n",
|
||||
" hello()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "1308eecd",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's run it to verify it works:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "e4f8da08",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"main()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "e3d31d30",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Chapter 1 - CLI and Agent Loop"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "bed806b8",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now let's add BAML and create our first agent with a CLI interface."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d4609f1f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"In this chapter, we'll integrate BAML to create an AI agent that can respond to user input.\n",
|
||||
"\n",
|
||||
"## What is BAML?\n",
|
||||
"\n",
|
||||
"BAML (Boundary Markup Language) is a domain-specific language designed to help developers build reliable AI workflows and agents. Created by [BoundaryML](https://www.boundaryml.com/) (a Y Combinator W23 company), BAML adds the engineering to prompt engineering.\n",
|
||||
"\n",
|
||||
"### Why BAML?\n",
|
||||
"\n",
|
||||
"- **Type-safe outputs**: Get fully type-safe outputs from LLMs, even when streaming\n",
|
||||
"- **Language agnostic**: Works with Python, TypeScript, Ruby, Go, and more\n",
|
||||
"- **LLM agnostic**: Works with any LLM provider (OpenAI, Anthropic, etc.)\n",
|
||||
"- **Better performance**: State-of-the-art structured outputs that outperform even OpenAI's native function calling\n",
|
||||
"- **Developer-friendly**: Native VSCode extension with syntax highlighting, autocomplete, and interactive playground\n",
|
||||
"\n",
|
||||
"### Learn More\n",
|
||||
"\n",
|
||||
"- 📚 [Official Documentation](https://docs.boundaryml.com/home)\n",
|
||||
"- 💻 [GitHub Repository](https://github.com/BoundaryML/baml)\n",
|
||||
"- 🎯 [What is BAML?](https://docs.boundaryml.com/guide/introduction/what-is-baml)\n",
|
||||
"- 📖 [BAML Examples](https://github.com/BoundaryML/baml-examples)\n",
|
||||
"- 🏢 [Company Website](https://www.boundaryml.com/)\n",
|
||||
"- 📰 [Blog: AI Agents Need a New Syntax](https://www.boundaryml.com/blog/ai-agents-need-new-syntax)\n",
|
||||
"\n",
|
||||
"BAML turns prompt engineering into schema engineering, where you focus on defining the structure of your data rather than wrestling with prompts. This approach leads to more reliable and maintainable AI applications.\n",
|
||||
"\n",
|
||||
"### Note on Developer Experience\n",
|
||||
"\n",
|
||||
"BAML works much better in VS Code with their official extension, which provides syntax highlighting, autocomplete, inline testing, and an interactive playground. However, for this notebook tutorial, we'll work with BAML files directly without the enhanced IDE features.\n",
|
||||
"\n",
|
||||
"First, let's set up BAML support in our notebook.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a1667330",
|
||||
"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": "f4bc29c7",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!pip install baml-py==0.202.0 pydantic"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "8c2328cd",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import subprocess\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"# Try to import Google Colab userdata, but don't fail if not in Colab\n",
|
||||
"try:\n",
|
||||
" from google.colab import userdata\n",
|
||||
" IN_COLAB = True\n",
|
||||
"except ImportError:\n",
|
||||
" IN_COLAB = False\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",
|
||||
" # Set API key from Colab secrets or environment\n",
|
||||
" if IN_COLAB:\n",
|
||||
" os.environ['OPENAI_API_KEY'] = userdata.get('OPENAI_API_KEY')\n",
|
||||
" elif 'OPENAI_API_KEY' not in os.environ:\n",
|
||||
" print(\"Warning: OPENAI_API_KEY not set. Please set it in your environment.\")\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": "236e47e5",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!baml-cli init"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "283a46ca",
|
||||
"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": "537ac878",
|
||||
"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": "1682e8b7",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Next, we need to define the BAML function that our agent will use.\n",
|
||||
"\n",
|
||||
"### Understanding BAML Syntax\n",
|
||||
"\n",
|
||||
"BAML files define:\n",
|
||||
"- **Classes**: Structured output schemas (like `DoneForNow` below)\n",
|
||||
"- **Functions**: AI-powered functions that take inputs and return structured outputs\n",
|
||||
"- **Tests**: Example inputs/outputs to validate your prompts\n",
|
||||
"\n",
|
||||
"This BAML file defines what our agent can do:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "c5c5f245",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!curl -fsSL -o baml_src/agent.baml https://raw.githubusercontent.com/humanlayer/12-factor-agents/refs/heads/main/workshops/2025-07-16/./walkthrough/01-agent.baml && cat baml_src/agent.baml"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "4bc7b6e8",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now let's create our main function that accepts a message parameter:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "8d6092ec",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ./walkthrough/01-main.py\n",
|
||||
"def main(message=\"hello from the notebook!\"):\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": "9cbb5999",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's test our agent! Try calling main() with different messages:\n",
|
||||
"- `main(\"What's the weather like?\")`\n",
|
||||
"- `main(\"Tell me a joke\")`\n",
|
||||
"- `main(\"How are you doing today?\")`\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "1943e86f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"baml_generate()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3813fcea",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"main(\"Hello from the Python notebook!\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6efa881e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Chapter 2 - Add Calculator Tools"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8ee92c1a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's add some calculator tools to our agent."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b20ebd98",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's start by adding a tool definition for the calculator.\n",
|
||||
"\n",
|
||||
"These are simple structured outputs that we'll ask the model to\n",
|
||||
"return as a \"next step\" in the agentic loop.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "c3e2f2bb",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!curl -fsSL -o baml_src/tool_calculator.baml https://raw.githubusercontent.com/humanlayer/12-factor-agents/refs/heads/main/workshops/2025-07-16/./walkthrough/02-tool_calculator.baml && cat baml_src/tool_calculator.baml"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "9bdddec7",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now, let's update the agent's DetermineNextStep method to\n",
|
||||
"expose the calculator tools as potential next steps.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "8f027eae",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!curl -fsSL -o baml_src/agent.baml https://raw.githubusercontent.com/humanlayer/12-factor-agents/refs/heads/main/workshops/2025-07-16/./walkthrough/02-agent.baml && cat baml_src/agent.baml"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "485b6900",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now let's update our main function to show the tool call:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "10fc6d7e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ./walkthrough/02-main.py\n",
|
||||
"def main(message=\"hello from the notebook!\"):\n",
|
||||
" # Create a new thread with the user's message\n",
|
||||
" thread = Thread([{\"type\": \"user_input\", \"data\": message}])\n",
|
||||
" \n",
|
||||
" # Get BAML client\n",
|
||||
" b = get_baml_client()\n",
|
||||
" \n",
|
||||
" # Get the next step from the agent - just show the tool call\n",
|
||||
" next_step = b.DetermineNextStep(thread.serialize_for_llm())\n",
|
||||
" \n",
|
||||
" # Print the raw response to show the tool call\n",
|
||||
" print(next_step)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "130656ac",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's try out the calculator! The agent should recognize that you want to perform a calculation\n",
|
||||
"and return the appropriate tool call instead of just a message.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "5e3e0e86",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"baml_generate()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "30bc78cc",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"main(\"can you add 3 and 4\")"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
488
workshops/2025-07-16/hack/chapter0-2_fixed_v2.ipynb
Normal file
488
workshops/2025-07-16/hack/chapter0-2_fixed_v2.ipynb
Normal file
@@ -0,0 +1,488 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b89b4f8e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Building the 12-factor agent template from scratch in Python"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "37c97708",
|
||||
"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": "759d9b2e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Chapter 0 - Hello World"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2c4d3b42",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's start with a basic Python setup and a hello world program."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "e72ea142",
|
||||
"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": "48b8dece",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Here's our simple hello world program:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "2abb7ddd",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ./walkthrough/00-main.py\n",
|
||||
"def hello():\n",
|
||||
" print('hello, world!')\n",
|
||||
"\n",
|
||||
"def main():\n",
|
||||
" hello()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "24e048e9",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's run it to verify it works:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "f15d231e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"main()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6907babb",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Chapter 1 - CLI and Agent Loop"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7499056d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now let's add BAML and create our first agent with a CLI interface."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c3bc3c6f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"In this chapter, we'll integrate BAML to create an AI agent that can respond to user input.\n",
|
||||
"\n",
|
||||
"## What is BAML?\n",
|
||||
"\n",
|
||||
"BAML (Boundary Markup Language) is a domain-specific language designed to help developers build reliable AI workflows and agents. Created by [BoundaryML](https://www.boundaryml.com/) (a Y Combinator W23 company), BAML adds the engineering to prompt engineering.\n",
|
||||
"\n",
|
||||
"### Why BAML?\n",
|
||||
"\n",
|
||||
"- **Type-safe outputs**: Get fully type-safe outputs from LLMs, even when streaming\n",
|
||||
"- **Language agnostic**: Works with Python, TypeScript, Ruby, Go, and more\n",
|
||||
"- **LLM agnostic**: Works with any LLM provider (OpenAI, Anthropic, etc.)\n",
|
||||
"- **Better performance**: State-of-the-art structured outputs that outperform even OpenAI's native function calling\n",
|
||||
"- **Developer-friendly**: Native VSCode extension with syntax highlighting, autocomplete, and interactive playground\n",
|
||||
"\n",
|
||||
"### Learn More\n",
|
||||
"\n",
|
||||
"- 📚 [Official Documentation](https://docs.boundaryml.com/home)\n",
|
||||
"- 💻 [GitHub Repository](https://github.com/BoundaryML/baml)\n",
|
||||
"- 🎯 [What is BAML?](https://docs.boundaryml.com/guide/introduction/what-is-baml)\n",
|
||||
"- 📖 [BAML Examples](https://github.com/BoundaryML/baml-examples)\n",
|
||||
"- 🏢 [Company Website](https://www.boundaryml.com/)\n",
|
||||
"- 📰 [Blog: AI Agents Need a New Syntax](https://www.boundaryml.com/blog/ai-agents-need-new-syntax)\n",
|
||||
"\n",
|
||||
"BAML turns prompt engineering into schema engineering, where you focus on defining the structure of your data rather than wrestling with prompts. This approach leads to more reliable and maintainable AI applications.\n",
|
||||
"\n",
|
||||
"### Note on Developer Experience\n",
|
||||
"\n",
|
||||
"BAML works much better in VS Code with their official extension, which provides syntax highlighting, autocomplete, inline testing, and an interactive playground. However, for this notebook tutorial, we'll work with BAML files directly without the enhanced IDE features.\n",
|
||||
"\n",
|
||||
"First, let's set up BAML support in our notebook.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c7cb4ae3",
|
||||
"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": "7a70ca2c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!pip install baml-py==0.202.0 pydantic"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "9cda7ecb",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import subprocess\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"# Try to import Google Colab userdata, but don't fail if not in Colab\n",
|
||||
"try:\n",
|
||||
" from google.colab import userdata\n",
|
||||
" IN_COLAB = True\n",
|
||||
"except ImportError:\n",
|
||||
" IN_COLAB = False\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",
|
||||
" # Set API key from Colab secrets or environment\n",
|
||||
" if IN_COLAB:\n",
|
||||
" os.environ['OPENAI_API_KEY'] = userdata.get('OPENAI_API_KEY')\n",
|
||||
" elif 'OPENAI_API_KEY' not in os.environ:\n",
|
||||
" print(\"Warning: OPENAI_API_KEY not set. Please set it in your environment.\")\n",
|
||||
" \n",
|
||||
" baml_generate()\n",
|
||||
" \n",
|
||||
" # Force delete all baml_client modules from sys.modules\n",
|
||||
" import sys\n",
|
||||
" modules_to_delete = [key for key in sys.modules.keys() if key.startswith('baml_client')]\n",
|
||||
" for module in modules_to_delete:\n",
|
||||
" del sys.modules[module]\n",
|
||||
" \n",
|
||||
" # Now import fresh\n",
|
||||
" import baml_client\n",
|
||||
" return baml_client.sync_client.b\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d7c6cf8b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!baml-cli init"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "de84a5ef",
|
||||
"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": "67a8acf5",
|
||||
"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": "094f2b2a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Next, we need to define the BAML function that our agent will use.\n",
|
||||
"\n",
|
||||
"### Understanding BAML Syntax\n",
|
||||
"\n",
|
||||
"BAML files define:\n",
|
||||
"- **Classes**: Structured output schemas (like `DoneForNow` below)\n",
|
||||
"- **Functions**: AI-powered functions that take inputs and return structured outputs\n",
|
||||
"- **Tests**: Example inputs/outputs to validate your prompts\n",
|
||||
"\n",
|
||||
"This BAML file defines what our agent can do:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d4aa5b7e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!curl -fsSL -o baml_src/agent.baml https://raw.githubusercontent.com/humanlayer/12-factor-agents/refs/heads/main/workshops/2025-07-16/./walkthrough/01-agent.baml && cat baml_src/agent.baml"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "10b15666",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now let's create our main function that accepts a message parameter:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "74f5c039",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ./walkthrough/01-main.py\n",
|
||||
"def main(message=\"hello from the notebook!\"):\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": "39192733",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's test our agent! Try calling main() with different messages:\n",
|
||||
"- `main(\"What's the weather like?\")`\n",
|
||||
"- `main(\"Tell me a joke\")`\n",
|
||||
"- `main(\"How are you doing today?\")`\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "c64f5b4c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"baml_generate()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "dbe20354",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"main(\"Hello from the Python notebook!\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "4715c874",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Chapter 2 - Add Calculator Tools"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "91aefaf2",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's add some calculator tools to our agent."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "faabf4e9",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's start by adding a tool definition for the calculator.\n",
|
||||
"\n",
|
||||
"These are simple structured outputs that we'll ask the model to\n",
|
||||
"return as a \"next step\" in the agentic loop.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "c51257cb",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!curl -fsSL -o baml_src/tool_calculator.baml https://raw.githubusercontent.com/humanlayer/12-factor-agents/refs/heads/main/workshops/2025-07-16/./walkthrough/02-tool_calculator.baml && cat baml_src/tool_calculator.baml"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6149ffa8",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now, let's update the agent's DetermineNextStep method to\n",
|
||||
"expose the calculator tools as potential next steps.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d3257406",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!curl -fsSL -o baml_src/agent.baml https://raw.githubusercontent.com/humanlayer/12-factor-agents/refs/heads/main/workshops/2025-07-16/./walkthrough/02-agent.baml && cat baml_src/agent.baml"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3f643b40",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now let's update our main function to show the tool call:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "efdab914",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ./walkthrough/02-main.py\n",
|
||||
"def main(message=\"hello from the notebook!\"):\n",
|
||||
" # Create a new thread with the user's message\n",
|
||||
" thread = Thread([{\"type\": \"user_input\", \"data\": message}])\n",
|
||||
" \n",
|
||||
" # Get BAML client\n",
|
||||
" b = get_baml_client()\n",
|
||||
" \n",
|
||||
" # Get the next step from the agent - just show the tool call\n",
|
||||
" next_step = b.DetermineNextStep(thread.serialize_for_llm())\n",
|
||||
" \n",
|
||||
" # Print the raw response to show the tool call\n",
|
||||
" print(next_step)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "1ff754f0",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's try out the calculator! The agent should recognize that you want to perform a calculation\n",
|
||||
"and return the appropriate tool call instead of just a message.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "5af0f57b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"baml_generate()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "067cfbac",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"main(\"can you add 3 and 4\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "z37cg7y9j4",
|
||||
"source": "# Building the 12-factor agent template from scratch in Python",
|
||||
"metadata": {}
|
||||
}
|
||||
],
|
||||
"metadata": {},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
477
workshops/2025-07-16/hack/chapter0-2_test.ipynb
Normal file
477
workshops/2025-07-16/hack/chapter0-2_test.ipynb
Normal file
@@ -0,0 +1,477 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b102542f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Building the 12-factor agent template from scratch in Python"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "102afe9c",
|
||||
"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": "22eeba5a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Chapter 0 - Hello World"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f1cdd18a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's start with a basic Python setup and a hello world program."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "dddbde46",
|
||||
"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": "eec93380",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Here's our simple hello world program:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "915a5235",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ./walkthrough/00-main.py\n",
|
||||
"def hello():\n",
|
||||
" print('hello, world!')\n",
|
||||
"\n",
|
||||
"def main():\n",
|
||||
" hello()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "cc7fa89a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's run it to verify it works:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "737420ce",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"main()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "0e2b54ff",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Chapter 1 - CLI and Agent Loop"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "65b83189",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now let's add BAML and create our first agent with a CLI interface."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "e1ad9ff1",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"In this chapter, we'll integrate BAML to create an AI agent that can respond to user input.\n",
|
||||
"\n",
|
||||
"## What is BAML?\n",
|
||||
"\n",
|
||||
"BAML (Boundary Markup Language) is a domain-specific language designed to help developers build reliable AI workflows and agents. Created by [BoundaryML](https://www.boundaryml.com/) (a Y Combinator W23 company), BAML adds the engineering to prompt engineering.\n",
|
||||
"\n",
|
||||
"### Why BAML?\n",
|
||||
"\n",
|
||||
"- **Type-safe outputs**: Get fully type-safe outputs from LLMs, even when streaming\n",
|
||||
"- **Language agnostic**: Works with Python, TypeScript, Ruby, Go, and more\n",
|
||||
"- **LLM agnostic**: Works with any LLM provider (OpenAI, Anthropic, etc.)\n",
|
||||
"- **Better performance**: State-of-the-art structured outputs that outperform even OpenAI's native function calling\n",
|
||||
"- **Developer-friendly**: Native VSCode extension with syntax highlighting, autocomplete, and interactive playground\n",
|
||||
"\n",
|
||||
"### Learn More\n",
|
||||
"\n",
|
||||
"- 📚 [Official Documentation](https://docs.boundaryml.com/home)\n",
|
||||
"- 💻 [GitHub Repository](https://github.com/BoundaryML/baml)\n",
|
||||
"- 🎯 [What is BAML?](https://docs.boundaryml.com/guide/introduction/what-is-baml)\n",
|
||||
"- 📖 [BAML Examples](https://github.com/BoundaryML/baml-examples)\n",
|
||||
"- 🏢 [Company Website](https://www.boundaryml.com/)\n",
|
||||
"- 📰 [Blog: AI Agents Need a New Syntax](https://www.boundaryml.com/blog/ai-agents-need-new-syntax)\n",
|
||||
"\n",
|
||||
"BAML turns prompt engineering into schema engineering, where you focus on defining the structure of your data rather than wrestling with prompts. This approach leads to more reliable and maintainable AI applications.\n",
|
||||
"\n",
|
||||
"### Note on Developer Experience\n",
|
||||
"\n",
|
||||
"BAML works much better in VS Code with their official extension, which provides syntax highlighting, autocomplete, inline testing, and an interactive playground. However, for this notebook tutorial, we'll work with BAML files directly without the enhanced IDE features.\n",
|
||||
"\n",
|
||||
"First, let's set up BAML support in our notebook.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "5686c992",
|
||||
"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": "325b7b32",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!pip install baml-py"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a0517855",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import subprocess\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"# Try to import Google Colab userdata, but don't fail if not in Colab\n",
|
||||
"try:\n",
|
||||
" from google.colab import userdata\n",
|
||||
" IN_COLAB = True\n",
|
||||
"except ImportError:\n",
|
||||
" IN_COLAB = False\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",
|
||||
" # Set API key from Colab secrets or environment\n",
|
||||
" if IN_COLAB:\n",
|
||||
" os.environ['OPENAI_API_KEY'] = userdata.get('OPENAI_API_KEY')\n",
|
||||
" elif 'OPENAI_API_KEY' not in os.environ:\n",
|
||||
" print(\"Warning: OPENAI_API_KEY not set. Please set it in your environment.\")\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": "2a104bd2",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!baml-cli init"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c2e54d6f",
|
||||
"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": "44538d6c",
|
||||
"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": "36a153a9",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Next, we need to define the BAML function that our agent will use.\n",
|
||||
"\n",
|
||||
"### Understanding BAML Syntax\n",
|
||||
"\n",
|
||||
"BAML files define:\n",
|
||||
"- **Classes**: Structured output schemas (like `DoneForNow` below)\n",
|
||||
"- **Functions**: AI-powered functions that take inputs and return structured outputs\n",
|
||||
"- **Tests**: Example inputs/outputs to validate your prompts\n",
|
||||
"\n",
|
||||
"This BAML file defines what our agent can do:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "4d4969f9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!curl -fsSL -o baml_src/agent.baml https://raw.githubusercontent.com/humanlayer/12-factor-agents/refs/heads/main/workshops/2025-07-16/./walkthrough/01-agent.baml && cat baml_src/agent.baml"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3922544b",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now let's create our main function that accepts a message parameter:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3f9cdec5",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ./walkthrough/01-main.py\n",
|
||||
"def main(message=\"hello from the notebook!\"):\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": "66cecd2f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's test our agent! Try calling main() with different messages:\n",
|
||||
"- `main(\"What's the weather like?\")`\n",
|
||||
"- `main(\"Tell me a joke\")`\n",
|
||||
"- `main(\"How are you doing today?\")`\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "b8cf2af4",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"baml_generate()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "5c75216a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"main(\"Hello from the Python notebook!\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f3c2722b",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Chapter 2 - Add Calculator Tools"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "4a192efe",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's add some calculator tools to our agent."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "4f39630b",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's start by adding a tool definition for the calculator.\n",
|
||||
"\n",
|
||||
"These are simple structured outputs that we'll ask the model to\n",
|
||||
"return as a \"next step\" in the agentic loop.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "683816a3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!curl -fsSL -o baml_src/tool_calculator.baml https://raw.githubusercontent.com/humanlayer/12-factor-agents/refs/heads/main/workshops/2025-07-16/./walkthrough/02-tool_calculator.baml && cat baml_src/tool_calculator.baml"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "36417465",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now, let's update the agent's DetermineNextStep method to\n",
|
||||
"expose the calculator tools as potential next steps.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "5a7db686",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!curl -fsSL -o baml_src/agent.baml https://raw.githubusercontent.com/humanlayer/12-factor-agents/refs/heads/main/workshops/2025-07-16/./walkthrough/02-agent.baml && cat baml_src/agent.baml"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "9dc26e7d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now let's update our main function to show the tool call:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "abc2341f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ./walkthrough/02-main.py\n",
|
||||
"def main(message=\"hello from the notebook!\"):\n",
|
||||
" # Create a new thread with the user's message\n",
|
||||
" thread = Thread([{\"type\": \"user_input\", \"data\": message}])\n",
|
||||
" \n",
|
||||
" # Get BAML client\n",
|
||||
" b = get_baml_client()\n",
|
||||
" \n",
|
||||
" # Get the next step from the agent - just show the tool call\n",
|
||||
" next_step = b.DetermineNextStep(thread.serialize_for_llm())\n",
|
||||
" \n",
|
||||
" # Print the raw response to show the tool call\n",
|
||||
" print(next_step)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "78a6f953",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's try out the calculator! The agent should recognize that you want to perform a calculation\n",
|
||||
"and return the appropriate tool call instead of just a message.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "9fdd6c71",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"baml_generate()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "06373364",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"main(\"can you add 3 and 4\")"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -5,6 +5,7 @@ description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
"baml>=0.19.1",
|
||||
"jupyter>=1.1.1",
|
||||
"nbformat>=5.10.4",
|
||||
"pyyaml>=6.0.2",
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
#!/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>")
|
||||
113
workshops/2025-07-16/hack/test_notebook_colab_sim.sh
Executable file
113
workshops/2025-07-16/hack/test_notebook_colab_sim.sh
Executable file
@@ -0,0 +1,113 @@
|
||||
#!/bin/bash
|
||||
# Simulate Google Colab environment for testing notebooks
|
||||
|
||||
set -e
|
||||
|
||||
NOTEBOOK_PATH="$1"
|
||||
|
||||
if [ -z "$NOTEBOOK_PATH" ]; then
|
||||
echo "Usage: $0 <notebook_path>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get absolute path of notebook
|
||||
NOTEBOOK_PATH=$(realpath "$NOTEBOOK_PATH")
|
||||
|
||||
# Create test directory in current folder
|
||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
TEMP_DIR="./tmp/test_${TIMESTAMP}"
|
||||
mkdir -p "$TEMP_DIR"
|
||||
echo "🧪 Creating clean test environment in: $TEMP_DIR"
|
||||
|
||||
# Don't auto-cleanup so we can inspect it
|
||||
echo "📁 Test directory will be preserved for inspection"
|
||||
|
||||
# Change to temp directory
|
||||
cd "$TEMP_DIR"
|
||||
|
||||
# Create fresh Python virtual environment
|
||||
echo "🐍 Creating fresh Python virtual environment..."
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
|
||||
# Install jupyter dependencies
|
||||
echo "📦 Installing Jupyter dependencies..."
|
||||
pip install --quiet notebook nbconvert ipykernel
|
||||
|
||||
# Copy notebook to temp directory
|
||||
cp "$NOTEBOOK_PATH" test_notebook.ipynb
|
||||
|
||||
# Create a Python script to execute the notebook
|
||||
cat > run_notebook.py << 'EOF'
|
||||
import nbformat
|
||||
from nbconvert.preprocessors import ExecutePreprocessor
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Ensure OPENAI_API_KEY is passed through
|
||||
if 'OPENAI_API_KEY' in os.environ:
|
||||
print(f"✅ OPENAI_API_KEY is set")
|
||||
else:
|
||||
print("⚠️ Warning: OPENAI_API_KEY not set")
|
||||
|
||||
# Read notebook
|
||||
with open('test_notebook.ipynb', 'r') as f:
|
||||
nb = nbformat.read(f, as_version=4)
|
||||
|
||||
# Execute ALL cells (just like Colab)
|
||||
ep = ExecutePreprocessor(timeout=120, kernel_name='python3')
|
||||
|
||||
print("🚀 Executing notebook (this simulates Google Colab)...")
|
||||
print("=" * 60)
|
||||
|
||||
try:
|
||||
ep.preprocess(nb, {'metadata': {'path': '.'}})
|
||||
print("\n✅ Notebook executed successfully!")
|
||||
|
||||
# Show final directory structure
|
||||
print("\n📁 Final directory structure:")
|
||||
for root, dirs, files in os.walk('.'):
|
||||
level = root.replace('.', '').count(os.sep)
|
||||
indent = ' ' * 2 * level
|
||||
print(f"{indent}{os.path.basename(root)}/")
|
||||
subindent = ' ' * 2 * (level + 1)
|
||||
for file in files[:10]: # Limit output
|
||||
if not file.startswith('.'):
|
||||
print(f"{subindent}{file}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ Error executing notebook: {e}")
|
||||
if hasattr(e, 'traceback'):
|
||||
print("\nTraceback:")
|
||||
print(e.traceback)
|
||||
sys.exit(1)
|
||||
EOF
|
||||
|
||||
# Run the notebook
|
||||
echo "🏃 Running notebook in clean environment..."
|
||||
python run_notebook.py
|
||||
|
||||
# Check what BAML files were created
|
||||
echo -e "\n📄 BAML files created:"
|
||||
if [ -d "baml_src" ]; then
|
||||
ls -la baml_src/
|
||||
else
|
||||
echo "No baml_src directory found"
|
||||
fi
|
||||
|
||||
# Check if Python BAML client was generated
|
||||
echo -e "\n🐍 Python BAML client:"
|
||||
if [ -d "baml_client" ]; then
|
||||
# Check if it's Python or TypeScript
|
||||
if [ -f "baml_client/__init__.py" ]; then
|
||||
echo "✅ Python client generated"
|
||||
ls baml_client/*.py 2>/dev/null | head -5
|
||||
else
|
||||
echo "❌ TypeScript client generated (not Python)"
|
||||
ls baml_client/*.ts 2>/dev/null | head -5
|
||||
fi
|
||||
else
|
||||
echo "No baml_client directory found"
|
||||
fi
|
||||
|
||||
echo -e "\n✨ Test complete!"
|
||||
1004
workshops/2025-07-16/hack/uv.lock
generated
1004
workshops/2025-07-16/hack/uv.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -28,6 +28,33 @@ sections:
|
||||
- text: |
|
||||
In this chapter, we'll integrate BAML to create an AI agent that can respond to user input.
|
||||
|
||||
## What is BAML?
|
||||
|
||||
BAML (Boundary Markup Language) is a domain-specific language designed to help developers build reliable AI workflows and agents. Created by [BoundaryML](https://www.boundaryml.com/) (a Y Combinator W23 company), BAML adds the engineering to prompt engineering.
|
||||
|
||||
### Why BAML?
|
||||
|
||||
- **Type-safe outputs**: Get fully type-safe outputs from LLMs, even when streaming
|
||||
- **Language agnostic**: Works with Python, TypeScript, Ruby, Go, and more
|
||||
- **LLM agnostic**: Works with any LLM provider (OpenAI, Anthropic, etc.)
|
||||
- **Better performance**: State-of-the-art structured outputs that outperform even OpenAI's native function calling
|
||||
- **Developer-friendly**: Native VSCode extension with syntax highlighting, autocomplete, and interactive playground
|
||||
|
||||
### Learn More
|
||||
|
||||
- 📚 [Official Documentation](https://docs.boundaryml.com/home)
|
||||
- 💻 [GitHub Repository](https://github.com/BoundaryML/baml)
|
||||
- 🎯 [What is BAML?](https://docs.boundaryml.com/guide/introduction/what-is-baml)
|
||||
- 📖 [BAML Examples](https://github.com/BoundaryML/baml-examples)
|
||||
- 🏢 [Company Website](https://www.boundaryml.com/)
|
||||
- 📰 [Blog: AI Agents Need a New Syntax](https://www.boundaryml.com/blog/ai-agents-need-new-syntax)
|
||||
|
||||
BAML turns prompt engineering into schema engineering, where you focus on defining the structure of your data rather than wrestling with prompts. This approach leads to more reliable and maintainable AI applications.
|
||||
|
||||
### Note on Developer Experience
|
||||
|
||||
BAML works much better in VS Code with their official extension, which provides syntax highlighting, autocomplete, inline testing, and an interactive playground. However, for this notebook tutorial, we'll work with BAML files directly without the enhanced IDE features.
|
||||
|
||||
First, let's set up BAML support in our notebook.
|
||||
- baml_setup: true
|
||||
- text: |
|
||||
@@ -38,11 +65,91 @@ sections:
|
||||
- text: |
|
||||
Next, we need to define the BAML function that our agent will use.
|
||||
|
||||
### Understanding BAML Syntax
|
||||
|
||||
BAML files define:
|
||||
- **Classes**: Structured output schemas (like `DoneForNow` below)
|
||||
- **Functions**: AI-powered functions that take inputs and return structured outputs
|
||||
- **Tests**: Example inputs/outputs to validate your prompts
|
||||
|
||||
This BAML file defines what our agent can do:
|
||||
- fetch_file: {src: ./walkthrough/01-agent.baml, dest: baml_src/01-agent.baml}
|
||||
- fetch_file: {src: ./walkthrough/01-agent.baml, dest: baml_src/agent.baml}
|
||||
- text: |
|
||||
Now let's create our main function that simulates command-line arguments:
|
||||
Now let's create our main function that accepts a message parameter:
|
||||
- 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}
|
||||
Let's test our agent! Try calling main() with different messages:
|
||||
- `main("What's the weather like?")`
|
||||
- `main("Tell me a joke")`
|
||||
- `main("How are you doing today?")`
|
||||
- run_main: {regenerate_baml: true, args: "Hello from the Python notebook!"}
|
||||
|
||||
- name: calculator-tools
|
||||
title: "Chapter 2 - Add Calculator Tools"
|
||||
text: "Let's add some calculator tools to our agent."
|
||||
steps:
|
||||
- text: |
|
||||
Let's start by adding a tool definition for the calculator.
|
||||
|
||||
These are simple structured outputs that we'll ask the model to
|
||||
return as a "next step" in the agentic loop.
|
||||
|
||||
- fetch_file: {src: ./walkthrough/02-tool_calculator.baml, dest: baml_src/tool_calculator.baml}
|
||||
- text: |
|
||||
Now, let's update the agent's DetermineNextStep method to
|
||||
expose the calculator tools as potential next steps.
|
||||
|
||||
- fetch_file: {src: ./walkthrough/02-agent.baml, dest: baml_src/agent.baml}
|
||||
- text: |
|
||||
Now let's update our main function to show the tool call:
|
||||
- file: {src: ./walkthrough/02-main.py}
|
||||
- text: |
|
||||
Let's try out the calculator! The agent should recognize that you want to perform a calculation
|
||||
and return the appropriate tool call instead of just a message.
|
||||
- run_main: {regenerate_baml: true, args: "can you add 3 and 4"}
|
||||
|
||||
- name: tool-loop
|
||||
title: "Chapter 3 - Process Tool Calls in a Loop"
|
||||
text: "Now let's add a real agentic loop that can run the tools and get a final answer from the LLM."
|
||||
steps:
|
||||
- text: |
|
||||
In this chapter, we'll enhance our agent to process tool calls in a loop. This means:
|
||||
- The agent can call multiple tools in sequence
|
||||
- Each tool result is fed back to the agent
|
||||
- The agent continues until it has a final answer
|
||||
|
||||
Let's update our agent to handle tool calls properly:
|
||||
- file: {src: ./walkthrough/03-agent.py}
|
||||
- text: |
|
||||
Now let's update our main function to use the new agent loop:
|
||||
- file: {src: ./walkthrough/03-main.py}
|
||||
- text: |
|
||||
Let's try it out! The agent should now call the tool and return the calculated result:
|
||||
- run_main: {regenerate_baml: true, args: "can you add 3 and 4"}
|
||||
- text: |
|
||||
You should see the agent:
|
||||
1. Recognize it needs to use the add tool
|
||||
2. Call the tool with the correct parameters
|
||||
3. Get the result (7)
|
||||
4. Generate a final response incorporating the result
|
||||
|
||||
For more complex calculations, we need to handle all calculator operations. Let's add support for subtract, multiply, and divide:
|
||||
- file: {src: ./walkthrough/03b-agent.py}
|
||||
- text: |
|
||||
Now let's test subtraction:
|
||||
- run_main: {regenerate_baml: false, args: "can you subtract 3 from 4"}
|
||||
- text: |
|
||||
Test multiplication:
|
||||
- run_main: {regenerate_baml: false, args: "can you multiply 3 and 4"}
|
||||
- text: |
|
||||
Finally, let's test a complex multi-step calculation:
|
||||
- run_main: {regenerate_baml: false, args: "can you multiply 3 and 4, then divide the result by 2 and then add 12 to that result"}
|
||||
- text: |
|
||||
Congratulations! You've taken your first step into hand-rolling an agent loop.
|
||||
|
||||
Key concepts you've learned:
|
||||
- **Thread Management**: Tracking conversation history and tool calls
|
||||
- **Tool Execution**: Processing different tool types and returning results
|
||||
- **Agent Loop**: Continuing until the agent has a final answer
|
||||
|
||||
From here, we'll start incorporating more intermediate and advanced concepts for 12-factor agents.
|
||||
@@ -20,15 +20,21 @@ Don't worry too much about this setup code - it will make sense later! For now,
|
||||
- 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"
|
||||
# First cell: Install baml-py and pydantic
|
||||
install_code = "!pip install baml-py==0.202.0 pydantic"
|
||||
nb.cells.append(new_code_cell(install_code))
|
||||
|
||||
# Second cell: Helper functions
|
||||
setup_code = '''import subprocess
|
||||
from google.colab import userdata
|
||||
import os
|
||||
|
||||
# Try to import Google Colab userdata, but don't fail if not in Colab
|
||||
try:
|
||||
from google.colab import userdata
|
||||
IN_COLAB = True
|
||||
except ImportError:
|
||||
IN_COLAB = False
|
||||
|
||||
def baml_generate():
|
||||
try:
|
||||
result = subprocess.run(
|
||||
@@ -53,13 +59,22 @@ 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')
|
||||
# Set API key from Colab secrets or environment
|
||||
if IN_COLAB:
|
||||
os.environ['OPENAI_API_KEY'] = userdata.get('OPENAI_API_KEY')
|
||||
elif 'OPENAI_API_KEY' not in os.environ:
|
||||
print("Warning: OPENAI_API_KEY not set. Please set it in your environment.")
|
||||
|
||||
baml_generate()
|
||||
|
||||
import importlib
|
||||
# Force delete all baml_client modules from sys.modules
|
||||
import sys
|
||||
modules_to_delete = [key for key in sys.modules.keys() if key.startswith('baml_client')]
|
||||
for module in modules_to_delete:
|
||||
del sys.modules[module]
|
||||
|
||||
# Now import fresh
|
||||
import baml_client
|
||||
importlib.reload(baml_client)
|
||||
return baml_client.sync_client.b
|
||||
'''
|
||||
nb.cells.append(new_code_cell(setup_code))
|
||||
@@ -125,7 +140,14 @@ def process_step(nb, step, base_path, current_functions):
|
||||
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()"))
|
||||
|
||||
# Check if args are provided
|
||||
args = step['run_main'].get('args', '')
|
||||
if args:
|
||||
# Pass the args as a string to main()
|
||||
nb.cells.append(new_code_cell(f'main("{args}")'))
|
||||
else:
|
||||
nb.cells.append(new_code_cell("main()"))
|
||||
|
||||
def convert_walkthrough_to_notebook(yaml_path, output_path):
|
||||
"""Convert walkthrough.yaml to Jupyter notebook."""
|
||||
|
||||
771
workshops/2025-07-16/hack/workshop_chapter3.ipynb
Normal file
771
workshops/2025-07-16/hack/workshop_chapter3.ipynb
Normal file
@@ -0,0 +1,771 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "9a5274e1",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Building the 12-factor agent template from scratch in Python"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6a6efe20",
|
||||
"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": "f8c0592e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Chapter 0 - Hello World"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d0e804de",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's start with a basic Python setup and a hello world program."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "083841c5",
|
||||
"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": "627ee046",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Here's our simple hello world program:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "eaeb2d23",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ./walkthrough/00-main.py\n",
|
||||
"def hello():\n",
|
||||
" print('hello, world!')\n",
|
||||
"\n",
|
||||
"def main():\n",
|
||||
" hello()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "fb5293e9",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's run it to verify it works:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "4fedee3c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"main()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "974d58b8",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Chapter 1 - CLI and Agent Loop"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6fdcf8bb",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now let's add BAML and create our first agent with a CLI interface."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7aad128d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"In this chapter, we'll integrate BAML to create an AI agent that can respond to user input.\n",
|
||||
"\n",
|
||||
"## What is BAML?\n",
|
||||
"\n",
|
||||
"BAML (Boundary Markup Language) is a domain-specific language designed to help developers build reliable AI workflows and agents. Created by [BoundaryML](https://www.boundaryml.com/) (a Y Combinator W23 company), BAML adds the engineering to prompt engineering.\n",
|
||||
"\n",
|
||||
"### Why BAML?\n",
|
||||
"\n",
|
||||
"- **Type-safe outputs**: Get fully type-safe outputs from LLMs, even when streaming\n",
|
||||
"- **Language agnostic**: Works with Python, TypeScript, Ruby, Go, and more\n",
|
||||
"- **LLM agnostic**: Works with any LLM provider (OpenAI, Anthropic, etc.)\n",
|
||||
"- **Better performance**: State-of-the-art structured outputs that outperform even OpenAI's native function calling\n",
|
||||
"- **Developer-friendly**: Native VSCode extension with syntax highlighting, autocomplete, and interactive playground\n",
|
||||
"\n",
|
||||
"### Learn More\n",
|
||||
"\n",
|
||||
"- 📚 [Official Documentation](https://docs.boundaryml.com/home)\n",
|
||||
"- 💻 [GitHub Repository](https://github.com/BoundaryML/baml)\n",
|
||||
"- 🎯 [What is BAML?](https://docs.boundaryml.com/guide/introduction/what-is-baml)\n",
|
||||
"- 📖 [BAML Examples](https://github.com/BoundaryML/baml-examples)\n",
|
||||
"- 🏢 [Company Website](https://www.boundaryml.com/)\n",
|
||||
"- 📰 [Blog: AI Agents Need a New Syntax](https://www.boundaryml.com/blog/ai-agents-need-new-syntax)\n",
|
||||
"\n",
|
||||
"BAML turns prompt engineering into schema engineering, where you focus on defining the structure of your data rather than wrestling with prompts. This approach leads to more reliable and maintainable AI applications.\n",
|
||||
"\n",
|
||||
"### Note on Developer Experience\n",
|
||||
"\n",
|
||||
"BAML works much better in VS Code with their official extension, which provides syntax highlighting, autocomplete, inline testing, and an interactive playground. However, for this notebook tutorial, we'll work with BAML files directly without the enhanced IDE features.\n",
|
||||
"\n",
|
||||
"First, let's set up BAML support in our notebook.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "9f17a460",
|
||||
"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": "aa2481ab",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!pip install baml-py==0.202.0 pydantic"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d6dbd418",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import subprocess\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"# Try to import Google Colab userdata, but don't fail if not in Colab\n",
|
||||
"try:\n",
|
||||
" from google.colab import userdata\n",
|
||||
" IN_COLAB = True\n",
|
||||
"except ImportError:\n",
|
||||
" IN_COLAB = False\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",
|
||||
" # Set API key from Colab secrets or environment\n",
|
||||
" if IN_COLAB:\n",
|
||||
" os.environ['OPENAI_API_KEY'] = userdata.get('OPENAI_API_KEY')\n",
|
||||
" elif 'OPENAI_API_KEY' not in os.environ:\n",
|
||||
" print(\"Warning: OPENAI_API_KEY not set. Please set it in your environment.\")\n",
|
||||
" \n",
|
||||
" baml_generate()\n",
|
||||
" \n",
|
||||
" # Force delete all baml_client modules from sys.modules\n",
|
||||
" import sys\n",
|
||||
" modules_to_delete = [key for key in sys.modules.keys() if key.startswith('baml_client')]\n",
|
||||
" for module in modules_to_delete:\n",
|
||||
" del sys.modules[module]\n",
|
||||
" \n",
|
||||
" # Now import fresh\n",
|
||||
" import baml_client\n",
|
||||
" return baml_client.sync_client.b\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "b25f5c0c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!baml-cli init"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "64eb1d9b",
|
||||
"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": "2488f695",
|
||||
"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": "6c0c0588",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Next, we need to define the BAML function that our agent will use.\n",
|
||||
"\n",
|
||||
"### Understanding BAML Syntax\n",
|
||||
"\n",
|
||||
"BAML files define:\n",
|
||||
"- **Classes**: Structured output schemas (like `DoneForNow` below)\n",
|
||||
"- **Functions**: AI-powered functions that take inputs and return structured outputs\n",
|
||||
"- **Tests**: Example inputs/outputs to validate your prompts\n",
|
||||
"\n",
|
||||
"This BAML file defines what our agent can do:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a5462c6b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!curl -fsSL -o baml_src/agent.baml https://raw.githubusercontent.com/humanlayer/12-factor-agents/refs/heads/main/workshops/2025-07-16/./walkthrough/01-agent.baml && cat baml_src/agent.baml"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "9ff08812",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now let's create our main function that accepts a message parameter:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a7c49c77",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ./walkthrough/01-main.py\n",
|
||||
"def main(message=\"hello from the notebook!\"):\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": "ad905bd3",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's test our agent! Try calling main() with different messages:\n",
|
||||
"- `main(\"What's the weather like?\")`\n",
|
||||
"- `main(\"Tell me a joke\")`\n",
|
||||
"- `main(\"How are you doing today?\")`\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "6211ff4e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"baml_generate()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "f41dba2f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"main(\"Hello from the Python notebook!\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "81a15b53",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Chapter 2 - Add Calculator Tools"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "414016d0",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's add some calculator tools to our agent."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "758fbf1b",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's start by adding a tool definition for the calculator.\n",
|
||||
"\n",
|
||||
"These are simple structured outputs that we'll ask the model to\n",
|
||||
"return as a \"next step\" in the agentic loop.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "21a94991",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!curl -fsSL -o baml_src/tool_calculator.baml https://raw.githubusercontent.com/humanlayer/12-factor-agents/refs/heads/main/workshops/2025-07-16/./walkthrough/02-tool_calculator.baml && cat baml_src/tool_calculator.baml"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "85436def",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now, let's update the agent's DetermineNextStep method to\n",
|
||||
"expose the calculator tools as potential next steps.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "75b9c2f0",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!curl -fsSL -o baml_src/agent.baml https://raw.githubusercontent.com/humanlayer/12-factor-agents/refs/heads/main/workshops/2025-07-16/./walkthrough/02-agent.baml && cat baml_src/agent.baml"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "78ba8da1",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now let's update our main function to show the tool call:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "26aa645f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ./walkthrough/02-main.py\n",
|
||||
"def main(message=\"hello from the notebook!\"):\n",
|
||||
" # Create a new thread with the user's message\n",
|
||||
" thread = Thread([{\"type\": \"user_input\", \"data\": message}])\n",
|
||||
" \n",
|
||||
" # Get BAML client\n",
|
||||
" b = get_baml_client()\n",
|
||||
" \n",
|
||||
" # Get the next step from the agent - just show the tool call\n",
|
||||
" next_step = b.DetermineNextStep(thread.serialize_for_llm())\n",
|
||||
" \n",
|
||||
" # Print the raw response to show the tool call\n",
|
||||
" print(next_step)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a0e9730a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's try out the calculator! The agent should recognize that you want to perform a calculation\n",
|
||||
"and return the appropriate tool call instead of just a message.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "19902e4d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"baml_generate()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "01bf191e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"main(\"can you add 3 and 4\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c2485c13",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Chapter 3 - Process Tool Calls in a Loop"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "dc99f949",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now let's add a real agentic loop that can run the tools and get a final answer from the LLM."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "fe9a3718",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"In this chapter, we'll enhance our agent to process tool calls in a loop. This means:\n",
|
||||
"- The agent can call multiple tools in sequence\n",
|
||||
"- Each tool result is fed back to the agent\n",
|
||||
"- The agent continues until it has a final answer\n",
|
||||
"\n",
|
||||
"Let's update our agent to handle tool calls properly:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "1f279921",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ./walkthrough/03-agent.py\n",
|
||||
"import json\n",
|
||||
"from typing import Dict, Any, List\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",
|
||||
"\n",
|
||||
"def agent_loop(thread: Thread) -> str:\n",
|
||||
" b = get_baml_client()\n",
|
||||
" \n",
|
||||
" while True:\n",
|
||||
" next_step = b.DetermineNextStep(thread.serialize_for_llm())\n",
|
||||
" print(\"nextStep\", next_step)\n",
|
||||
" \n",
|
||||
" if next_step.intent == \"done_for_now\":\n",
|
||||
" # response to human, return the next step object\n",
|
||||
" return next_step.message\n",
|
||||
" elif next_step.intent == \"add\":\n",
|
||||
" thread.events.append({\n",
|
||||
" \"type\": \"tool_call\",\n",
|
||||
" \"data\": next_step.__dict__\n",
|
||||
" })\n",
|
||||
" result = next_step.a + next_step.b\n",
|
||||
" print(\"tool_response\", result)\n",
|
||||
" thread.events.append({\n",
|
||||
" \"type\": \"tool_response\",\n",
|
||||
" \"data\": result\n",
|
||||
" })\n",
|
||||
" continue\n",
|
||||
" else:\n",
|
||||
" raise ValueError(f\"Unknown intent: {next_step.intent}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3457e09f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now let's update our main function to use the new agent loop:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "92cc1194",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ./walkthrough/03-main.py\n",
|
||||
"def main(message=\"hello from the notebook!\"):\n",
|
||||
" # Create a new thread with the user's message\n",
|
||||
" thread = Thread([{\"type\": \"user_input\", \"data\": message}])\n",
|
||||
" \n",
|
||||
" # Run the agent loop with full tool handling\n",
|
||||
" result = agent_loop(thread)\n",
|
||||
" \n",
|
||||
" # Print the final response\n",
|
||||
" print(f\"\\nFinal response: {result}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8f4f81e1",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's try it out! The agent should now call the tool and return the calculated result:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "71596095",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"baml_generate()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "192f038e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"main(\"can you add 3 and 4\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c26d30b2",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"You should see the agent:\n",
|
||||
"1. Recognize it needs to use the add tool\n",
|
||||
"2. Call the tool with the correct parameters\n",
|
||||
"3. Get the result (7)\n",
|
||||
"4. Generate a final response incorporating the result\n",
|
||||
"\n",
|
||||
"For more complex calculations, we need to handle all calculator operations. Let's add support for subtract, multiply, and divide:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "c612395e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ./walkthrough/03b-agent.py\n",
|
||||
"import json\n",
|
||||
"from typing import Dict, Any, List, Union\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",
|
||||
"def handle_next_step(next_step, thread: Thread) -> Thread:\n",
|
||||
" result: float\n",
|
||||
" \n",
|
||||
" if next_step.intent == \"add\":\n",
|
||||
" result = next_step.a + next_step.b\n",
|
||||
" print(\"tool_response\", result)\n",
|
||||
" thread.events.append({\n",
|
||||
" \"type\": \"tool_response\",\n",
|
||||
" \"data\": result\n",
|
||||
" })\n",
|
||||
" return thread\n",
|
||||
" elif next_step.intent == \"subtract\":\n",
|
||||
" result = next_step.a - next_step.b\n",
|
||||
" print(\"tool_response\", result)\n",
|
||||
" thread.events.append({\n",
|
||||
" \"type\": \"tool_response\",\n",
|
||||
" \"data\": result\n",
|
||||
" })\n",
|
||||
" return thread\n",
|
||||
" elif next_step.intent == \"multiply\":\n",
|
||||
" result = next_step.a * next_step.b\n",
|
||||
" print(\"tool_response\", result)\n",
|
||||
" thread.events.append({\n",
|
||||
" \"type\": \"tool_response\",\n",
|
||||
" \"data\": result\n",
|
||||
" })\n",
|
||||
" return thread\n",
|
||||
" elif next_step.intent == \"divide\":\n",
|
||||
" result = next_step.a / next_step.b\n",
|
||||
" print(\"tool_response\", result)\n",
|
||||
" thread.events.append({\n",
|
||||
" \"type\": \"tool_response\",\n",
|
||||
" \"data\": result\n",
|
||||
" })\n",
|
||||
" return thread\n",
|
||||
"\n",
|
||||
"def agent_loop(thread: Thread) -> str:\n",
|
||||
" b = get_baml_client()\n",
|
||||
" \n",
|
||||
" while True:\n",
|
||||
" next_step = b.DetermineNextStep(thread.serialize_for_llm())\n",
|
||||
" print(\"nextStep\", next_step)\n",
|
||||
" \n",
|
||||
" thread.events.append({\n",
|
||||
" \"type\": \"tool_call\",\n",
|
||||
" \"data\": next_step.__dict__\n",
|
||||
" })\n",
|
||||
" \n",
|
||||
" if next_step.intent == \"done_for_now\":\n",
|
||||
" # response to human, return the next step object\n",
|
||||
" return next_step.message\n",
|
||||
" elif next_step.intent in [\"add\", \"subtract\", \"multiply\", \"divide\"]:\n",
|
||||
" thread = handle_next_step(next_step, thread)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f2c066cb",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now let's test subtraction:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d21dad8c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"main(\"can you subtract 3 from 4\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "69fc9590",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Test multiplication:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "ae1bf622",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"main(\"can you multiply 3 and 4\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "dada4d98",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Finally, let's test a complex multi-step calculation:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "1f29ef37",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"main(\"can you multiply 3 and 4, then divide the result by 2 and then add 12 to that result\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "38a60e47",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Congratulations! You've taken your first step into hand-rolling an agent loop.\n",
|
||||
"\n",
|
||||
"Key concepts you've learned:\n",
|
||||
"- **Thread Management**: Tracking conversation history and tool calls\n",
|
||||
"- **Tool Execution**: Processing different tool types and returning results\n",
|
||||
"- **Agent Loop**: Continuing until the agent has a final answer\n",
|
||||
"\n",
|
||||
"From here, we'll start incorporating more intermediate and advanced concepts for 12-factor agents."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -1,20 +1,4 @@
|
||||
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)
|
||||
|
||||
def main(message="hello from the notebook!"):
|
||||
# Create a new thread with the user's message as the initial event
|
||||
thread = Thread([{"type": "user_input", "data": message}])
|
||||
|
||||
|
||||
12
workshops/2025-07-16/walkthrough/02-main.py
Normal file
12
workshops/2025-07-16/walkthrough/02-main.py
Normal file
@@ -0,0 +1,12 @@
|
||||
def main(message="hello from the notebook!"):
|
||||
# Create a new thread with the user's message
|
||||
thread = Thread([{"type": "user_input", "data": message}])
|
||||
|
||||
# Get BAML client
|
||||
b = get_baml_client()
|
||||
|
||||
# Get the next step from the agent - just show the tool call
|
||||
next_step = b.DetermineNextStep(thread.serialize_for_llm())
|
||||
|
||||
# Print the raw response to show the tool call
|
||||
print(next_step)
|
||||
37
workshops/2025-07-16/walkthrough/03-agent.py
Normal file
37
workshops/2025-07-16/walkthrough/03-agent.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import json
|
||||
from typing import Dict, Any, List
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def agent_loop(thread: Thread) -> str:
|
||||
b = get_baml_client()
|
||||
|
||||
while True:
|
||||
next_step = b.DetermineNextStep(thread.serialize_for_llm())
|
||||
print("nextStep", next_step)
|
||||
|
||||
if next_step.intent == "done_for_now":
|
||||
# response to human, return the next step object
|
||||
return next_step.message
|
||||
elif next_step.intent == "add":
|
||||
thread.events.append({
|
||||
"type": "tool_call",
|
||||
"data": next_step.__dict__
|
||||
})
|
||||
result = next_step.a + next_step.b
|
||||
print("tool_response", result)
|
||||
thread.events.append({
|
||||
"type": "tool_response",
|
||||
"data": result
|
||||
})
|
||||
continue
|
||||
else:
|
||||
raise ValueError(f"Unknown intent: {next_step.intent}")
|
||||
9
workshops/2025-07-16/walkthrough/03-main.py
Normal file
9
workshops/2025-07-16/walkthrough/03-main.py
Normal file
@@ -0,0 +1,9 @@
|
||||
def main(message="hello from the notebook!"):
|
||||
# Create a new thread with the user's message
|
||||
thread = Thread([{"type": "user_input", "data": message}])
|
||||
|
||||
# Run the agent loop with full tool handling
|
||||
result = agent_loop(thread)
|
||||
|
||||
# Print the final response
|
||||
print(f"\nFinal response: {result}")
|
||||
65
workshops/2025-07-16/walkthrough/03b-agent.py
Normal file
65
workshops/2025-07-16/walkthrough/03b-agent.py
Normal file
@@ -0,0 +1,65 @@
|
||||
import json
|
||||
from typing import Dict, Any, List, Union
|
||||
|
||||
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)
|
||||
|
||||
def handle_next_step(next_step, thread: Thread) -> Thread:
|
||||
result: float
|
||||
|
||||
if next_step.intent == "add":
|
||||
result = next_step.a + next_step.b
|
||||
print("tool_response", result)
|
||||
thread.events.append({
|
||||
"type": "tool_response",
|
||||
"data": result
|
||||
})
|
||||
return thread
|
||||
elif next_step.intent == "subtract":
|
||||
result = next_step.a - next_step.b
|
||||
print("tool_response", result)
|
||||
thread.events.append({
|
||||
"type": "tool_response",
|
||||
"data": result
|
||||
})
|
||||
return thread
|
||||
elif next_step.intent == "multiply":
|
||||
result = next_step.a * next_step.b
|
||||
print("tool_response", result)
|
||||
thread.events.append({
|
||||
"type": "tool_response",
|
||||
"data": result
|
||||
})
|
||||
return thread
|
||||
elif next_step.intent == "divide":
|
||||
result = next_step.a / next_step.b
|
||||
print("tool_response", result)
|
||||
thread.events.append({
|
||||
"type": "tool_response",
|
||||
"data": result
|
||||
})
|
||||
return thread
|
||||
|
||||
def agent_loop(thread: Thread) -> str:
|
||||
b = get_baml_client()
|
||||
|
||||
while True:
|
||||
next_step = b.DetermineNextStep(thread.serialize_for_llm())
|
||||
print("nextStep", next_step)
|
||||
|
||||
thread.events.append({
|
||||
"type": "tool_call",
|
||||
"data": next_step.__dict__
|
||||
})
|
||||
|
||||
if next_step.intent == "done_for_now":
|
||||
# response to human, return the next step object
|
||||
return next_step.message
|
||||
elif next_step.intent in ["add", "subtract", "multiply", "divide"]:
|
||||
thread = handle_next_step(next_step, thread)
|
||||
Reference in New Issue
Block a user