mirror of
https://github.com/humanlayer/12-factor-agents.git
synced 2025-08-20 18:59:53 +03:00
wip
This commit is contained in:
49
workshops/2025-05-17/baml_src/agent.baml
Normal file
49
workshops/2025-05-17/baml_src/agent.baml
Normal file
@@ -0,0 +1,49 @@
|
||||
class DoneForNow {
|
||||
intent "done_for_now"
|
||||
message string
|
||||
}
|
||||
|
||||
client<llm> Qwen3 {
|
||||
provider "openai-generic"
|
||||
options {
|
||||
base_url "https://model-4w7jrl6w.api.baseten.co/environments/production/sync/v1"
|
||||
api_key env.BASETEN_API_KEY
|
||||
}
|
||||
}
|
||||
|
||||
function DetermineNextStep(
|
||||
thread: string
|
||||
) -> DoneForNow {
|
||||
client Qwen3
|
||||
|
||||
// use /nothink for now because the thinking tokens (or streaming thereof) screw with baml (i think (no pun intended))
|
||||
prompt #"
|
||||
{{ _.role("system") }}
|
||||
|
||||
/nothink
|
||||
|
||||
You are a helpful assistant that can help with tasks.
|
||||
|
||||
{{ _.role("user") }}
|
||||
|
||||
You are working on the following thread:
|
||||
|
||||
{{ thread }}
|
||||
|
||||
What should the next step be?
|
||||
|
||||
{{ ctx.output_format }}
|
||||
"#
|
||||
}
|
||||
|
||||
test HelloWorld {
|
||||
functions [DetermineNextStep]
|
||||
args {
|
||||
thread #"
|
||||
{
|
||||
"type": "user_input",
|
||||
"data": "hello!"
|
||||
}
|
||||
"#
|
||||
}
|
||||
}
|
||||
680
workshops/2025-05-17/walkthrough.yaml
Normal file
680
workshops/2025-05-17/walkthrough.yaml
Normal file
@@ -0,0 +1,680 @@
|
||||
title: "Building the 12-factor agent template from scratch"
|
||||
text: "Steps to start from a bare TS repo and build up a 12-factor agent. This walkthrough will guide you through creating a TypeScript agent that follows the 12-factor methodology."
|
||||
|
||||
targets:
|
||||
- markdown: "./build/walkthrough.md"
|
||||
onChange:
|
||||
diff: true
|
||||
cp: true
|
||||
newFiles:
|
||||
cat: false
|
||||
cp: true
|
||||
- folders:
|
||||
path: "./build/sections"
|
||||
skip:
|
||||
- "cleanup"
|
||||
final:
|
||||
dirName: "final"
|
||||
|
||||
sections:
|
||||
- name: cleanup
|
||||
title: "Cleanup"
|
||||
text: "Make sure you're starting from a clean slate"
|
||||
steps:
|
||||
- text: "Clean up existing files"
|
||||
command: |
|
||||
rm -rf baml_src/ && rm -rf src/
|
||||
|
||||
- name: hello-world
|
||||
title: "Chapter 0 - Hello World"
|
||||
text: "Let's start with a basic TypeScript setup and a hello world program."
|
||||
steps:
|
||||
- text: |
|
||||
This guide is written in TypeScript (yes, a python version is coming soon)
|
||||
|
||||
There are many checkpoints between the every file edit in theworkshop steps,
|
||||
so even if you aren't super familiar with typescript,
|
||||
you should be able to keep up and run each example.
|
||||
|
||||
To run this guide, you'll need a relatively recent version of nodejs and npm installed
|
||||
|
||||
You can use whatever nodejs version manager you want, [homebrew](https://formulae.brew.sh/formula/node) is fine
|
||||
|
||||
command:
|
||||
brew install node@20
|
||||
results:
|
||||
- text: "You should see the node version"
|
||||
code: |
|
||||
node --version
|
||||
|
||||
- text: "Copy initial package.json"
|
||||
file: {src: ./walkthrough/00-package.json, dest: package.json}
|
||||
- text: "Install dependencies"
|
||||
command: |
|
||||
npm install
|
||||
incremental: true
|
||||
- text: "Copy tsconfig.json"
|
||||
file: {src: ./walkthrough/00-tsconfig.json, dest: tsconfig.json}
|
||||
- text: "add .gitignore"
|
||||
file: {src: ./walkthrough/00-.gitignore, dest: .gitignore}
|
||||
- text: "Create src folder"
|
||||
dir: {create: true, path: src}
|
||||
- text: "Add a simple hello world index.ts"
|
||||
file: {src: ./walkthrough/00-index.ts, dest: src/index.ts}
|
||||
- text: "Run it to verify"
|
||||
command: |
|
||||
npx tsx src/index.ts
|
||||
results:
|
||||
- text: "You should see:"
|
||||
code: |
|
||||
hello, world!
|
||||
|
||||
- name: cli-and-agent
|
||||
title: "Chapter 1 - CLI and Agent Loop"
|
||||
text: |
|
||||
Now let's add BAML and create our first agent with a CLI interface.
|
||||
|
||||
This will give us a very basic
|
||||
steps:
|
||||
- text: |
|
||||
First, we'll need to install [BAML](https://github.com/boundaryml/baml)
|
||||
which is a tool for prompting and structured outputs.
|
||||
command: |
|
||||
npm i baml
|
||||
incremental: true
|
||||
- text: "Initialize BAML"
|
||||
command: |
|
||||
npx baml-cli init
|
||||
incremental: true
|
||||
- text: "Remove default resume.baml"
|
||||
command: |
|
||||
rm baml_src/resume.baml
|
||||
incremental: true
|
||||
- text: "Add our starter agent, a single baml prompt that we'll build on"
|
||||
file: {src: ./walkthrough/01-agent.baml, dest: baml_src/agent.baml}
|
||||
- text: "Generate BAML client code"
|
||||
command: |
|
||||
npx baml-cli generate
|
||||
incremental: true
|
||||
- text: "Enable BAML logging for this section"
|
||||
command: |
|
||||
export BAML_LOG=debug
|
||||
- text: "Add the CLI interface"
|
||||
file: {src: ./walkthrough/01-cli.ts, dest: src/cli.ts}
|
||||
- text: "Update index.ts to use the CLI"
|
||||
file: {src: ./walkthrough/01-index.ts, dest: src/index.ts}
|
||||
- text: "Add the agent implementation"
|
||||
file: {src: ./walkthrough/01-agent.ts, dest: src/agent.ts}
|
||||
- text: |
|
||||
The the BAML code is configured to use BASETEN_API_KEY by default
|
||||
|
||||
As you're testing, you can change the model / provider to something else
|
||||
as you please
|
||||
|
||||
client Qwen3
|
||||
|
||||
[Docs on baml clients can be found here](https://docs.boundaryml.com/guide/baml-basics/switching-llms)
|
||||
|
||||
For example, you can configure [gemini](https://docs.boundaryml.com/ref/llm-client-providers/google-ai-gemini)
|
||||
or [anthropic](https://docs.boundaryml.com/ref/llm-client-providers/anthropic) as your model provider.
|
||||
|
||||
If you want to run the example with no changes, you can set the BASETEN_API_KEY env var to any valid baseten key.
|
||||
|
||||
command: |
|
||||
export BASETEN_API_KEY=...
|
||||
- text: "Try it out"
|
||||
command: |
|
||||
npx tsx src/index.ts hello
|
||||
results:
|
||||
- text: you should see a familiar response from the model
|
||||
code: |
|
||||
{
|
||||
intent: 'done_for_now',
|
||||
message: 'Hello! How can I assist you today?'
|
||||
}
|
||||
|
||||
- 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 simpile structured outputs that we'll ask the model to
|
||||
return as a "next step" in the agentic loop.
|
||||
|
||||
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
|
||||
|
||||
file: {src: ./walkthrough/02-agent.baml, dest: baml_src/agent.baml}
|
||||
- text: "Generate updated BAML client"
|
||||
command: |
|
||||
npx baml-cli generate
|
||||
incremental: true
|
||||
- text: "Try out the calculator"
|
||||
command: |
|
||||
npx tsx src/index.ts 'can you add 3 and 4'
|
||||
results:
|
||||
- text: "You should see a tool call to the calculator"
|
||||
code: |
|
||||
{
|
||||
intent: 'add',
|
||||
a: 3,
|
||||
b: 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.
|
||||
|
||||
This will set you up for
|
||||
- [factor 4 - tools are structured outputs](https://github.com/humanlayer/12-factor-agents/blob/main/content/factor-04-tools-are-structured-outputs.md)
|
||||
- [factor 8 - own your control flow](https://github.com/humanlayer/12-factor-agents/blob/main/content/factor-08-own-your-control-flow.md)
|
||||
|
||||
steps:
|
||||
- text: |
|
||||
First, lets update the agent to handle the tool call
|
||||
file: {src: ./walkthrough/03-agent.ts, dest: src/agent.ts}
|
||||
- text: |
|
||||
Now, lets try it out
|
||||
command: |
|
||||
npx tsx src/index.ts 'can you add 3 and 4'
|
||||
results:
|
||||
- text: you should see the agent call the tool and then return the result
|
||||
code: |
|
||||
{
|
||||
intent: 'done_for_now',
|
||||
message: 'The sum of 3 and 4 is 7.'
|
||||
}
|
||||
- text: "For the next step, we'll do a more complex calculation, let's turn off the baml logs for more concise output"
|
||||
command: |
|
||||
export BAML_LOG=off
|
||||
- text: "Try a multi-step calculation"
|
||||
command: |
|
||||
npx tsx src/index.ts 'can you add 3 and 4, then add 6 to that result'
|
||||
- text: "you'll notice that tools like multiply and divide are not available"
|
||||
command: |
|
||||
npx tsx src/index.ts 'can you multiply 3 and 4'
|
||||
- text: |
|
||||
next, let's add handlers for the rest of the calculator tools
|
||||
file: {src: ./walkthrough/03b-agent.ts, dest: src/agent.ts}
|
||||
- text: "Test subtraction"
|
||||
command: |
|
||||
npx tsx src/index.ts 'can you subtract 3 from 4'
|
||||
- text: |
|
||||
now, let's test the multiplication tool
|
||||
command: |
|
||||
npx tsx src/index.ts 'can you multiply 3 and 4'
|
||||
- text: |
|
||||
finally, let's test a more complex calculation with multiple operations
|
||||
command: |
|
||||
npx tsx src/index.ts 'can you multiply 3 and 4, then divide the result by 2 and then add 12 to that result'
|
||||
|
||||
- name: baml-tests
|
||||
title: "Chapter 4 - Add Tests to agent.baml"
|
||||
text: "Let's add some tests to our BAML agent."
|
||||
steps:
|
||||
- text: to start, leave the baml logs enabled
|
||||
command: |
|
||||
export BAML_LOG=debug
|
||||
- text: |
|
||||
next, let's add some tests to the agent
|
||||
|
||||
We'll start with a simple test that checks the agent's ability to handle
|
||||
a basic calculation.
|
||||
|
||||
file: {src: ./walkthrough/04-agent.baml, dest: baml_src/agent.baml}
|
||||
- text: "Run the tests"
|
||||
command: |
|
||||
npx baml-cli test
|
||||
- text: |
|
||||
now, let's improve the test with assertions!
|
||||
|
||||
Assertions are a great way to make sure the agent is working as expected,
|
||||
and can easily be extended to check for more complex behavior.
|
||||
|
||||
file: {src: ./walkthrough/04b-agent.baml, dest: baml_src/agent.baml}
|
||||
- text: "Run the tests"
|
||||
command: |
|
||||
npx baml-cli test
|
||||
- text: |
|
||||
as you add more tests, you can disable the logs to keep the output clean.
|
||||
You may want to turn them on as you iterate on specific tests.
|
||||
command: |
|
||||
export BAML_LOG=off
|
||||
- text: |
|
||||
now, let's add some more complex test cases,
|
||||
where we resume from in the middle of an in-progress
|
||||
agentic context window
|
||||
|
||||
|
||||
file: {src: ./walkthrough/04c-agent.baml, dest: baml_src/agent.baml}
|
||||
- text: |
|
||||
let's try to run it
|
||||
command: |
|
||||
npx baml-cli test
|
||||
|
||||
- name: human-tools
|
||||
title: "Chapter 5 - Multiple Human Tools"
|
||||
text: |
|
||||
In this section, we'll add support for multiple tools that serve to
|
||||
contact humans.
|
||||
|
||||
|
||||
|
||||
steps:
|
||||
- text: "for this section, we'll disable the baml logs. You can optionally enable them if you want to see more details."
|
||||
command: |
|
||||
export BAML_LOG=off
|
||||
- text: |
|
||||
first, let's add a tool that can request clarification from a human
|
||||
|
||||
this will be different from the "done_for_now" tool,
|
||||
and can be used to more flexibly handle different types of human interactions
|
||||
in your agent.
|
||||
|
||||
file: {src: ./walkthrough/05-agent.baml, dest: baml_src/agent.baml}
|
||||
- text: |
|
||||
next, let's re-generate the client code
|
||||
|
||||
NOTE - if you're using the VSCode extension for BAML,
|
||||
the client will be regenerated automatically when you save the file
|
||||
in your editor.
|
||||
|
||||
command: |
|
||||
npx baml-cli generate
|
||||
incremental: true
|
||||
- text: |
|
||||
now, let's update the agent to use the new tool
|
||||
|
||||
file: {src: ./walkthrough/05-agent.ts, dest: src/agent.ts}
|
||||
- text: |
|
||||
next, let's update the CLI to handle clarification requests
|
||||
by requesting input from the user on the CLI
|
||||
|
||||
file: {src: ./walkthrough/05-cli.ts, dest: src/cli.ts}
|
||||
- text: |
|
||||
let's try it out
|
||||
|
||||
command: |
|
||||
npx tsx src/index.ts 'can you multiply 3 and FD*(#F&& '
|
||||
- text: |
|
||||
next, let's add a test that checks the agent's ability to handle
|
||||
a clarification request
|
||||
|
||||
file: {src: ./walkthrough/05b-agent.baml, dest: baml_src/agent.baml}
|
||||
- text: |
|
||||
and now we can run the tests again
|
||||
command: |
|
||||
npx baml-cli test
|
||||
- text: |
|
||||
you'll notice the new test passes, but the hello world test fails
|
||||
|
||||
This is because the agent's default behavior is to return "done_for_now"
|
||||
|
||||
file: {src: ./walkthrough/05c-agent.baml, dest: baml_src/agent.baml}
|
||||
- text: "Verify tests pass"
|
||||
command: |
|
||||
npx baml-cli test
|
||||
|
||||
- name: customize-prompt
|
||||
title: "Chapter 6 - Customize Your Prompt with Reasoning"
|
||||
text: |
|
||||
In this section, we'll explore how to customize the prompt of the agent
|
||||
with reasoning steps.
|
||||
|
||||
this is core to [factor 2 - own your prompts](https://github.com/humanlayer/12-factor-agents/blob/main/content/factor-2-own-your-prompts.md)
|
||||
|
||||
there's a deep dive on reasoning on AI That Works [reasoning models versus reasoning steps](https://github.com/hellovai/ai-that-works/tree/main/2025-04-07-reasoning-models-vs-prompts)
|
||||
|
||||
steps:
|
||||
- text: "for this section, it will be helpful to leave the baml logs enabled"
|
||||
command: |
|
||||
export BAML_LOG=debug
|
||||
- text: |
|
||||
update the agent prompt to include a reasoning step
|
||||
file: {src: ./walkthrough/06-agent.baml, dest: baml_src/agent.baml}
|
||||
- text: generate the updated client
|
||||
command: |
|
||||
npx baml-cli generate
|
||||
incremental: true
|
||||
- text: |
|
||||
now, you can try it out with a simple prompt
|
||||
command: |
|
||||
npx tsx src/index.ts 'can you multiply 3 and 4'
|
||||
results:
|
||||
- text: you should see output from the baml logs showing the reasoning steps
|
||||
- text: |
|
||||
#### optional challenge
|
||||
|
||||
add a field to your tool output format that includes the reasoning steps in the output!
|
||||
|
||||
- name: context-window
|
||||
title: "Chapter 7 - Customize Your Context Window"
|
||||
text: |
|
||||
In this section, we'll explore how to customize the context window
|
||||
of the agent.
|
||||
|
||||
this is core to [factor 3 - own your context window](https://github.com/humanlayer/12-factor-agents/blob/main/content/factor-3-own-your-context-window.md)
|
||||
|
||||
steps:
|
||||
- text: |
|
||||
update the agent to pretty-print the Context window for the model
|
||||
file: {src: ./walkthrough/07-agent.ts, dest: src/agent.ts}
|
||||
- text: "Test the formatting"
|
||||
command: |
|
||||
BAML_LOG=info npx tsx src/index.ts 'can you multiply 3 and 4, then divide the result by 2 and then add 12 to that result'
|
||||
- text: |
|
||||
next, let's update the agent to use XML formatting instead
|
||||
|
||||
this is a very popular format for passing data to a model,
|
||||
|
||||
among other things, because of the token efficiency of XML.
|
||||
|
||||
file: {src: ./walkthrough/07b-agent.ts, dest: src/agent.ts}
|
||||
- text: |
|
||||
let's try it out
|
||||
command: |
|
||||
BAML_LOG=info npx tsx src/index.ts 'can you multiply 3 and 4, then divide the result by 2 and then add 12 to that result'
|
||||
- text: |
|
||||
lets update our tests to match the new output format
|
||||
file: {src: ./walkthrough/07c-agent.baml, dest: baml_src/agent.baml}
|
||||
- text: |
|
||||
check out the updated tests
|
||||
command: |
|
||||
npx baml-cli test
|
||||
|
||||
- name: api-endpoints
|
||||
title: "Chapter 8 - Adding API Endpoints"
|
||||
text: "Add an Express server to expose the agent via HTTP."
|
||||
steps:
|
||||
- text: "for this section, we'll disable the baml logs. You can optionally enable them if you want to see more details."
|
||||
command: |
|
||||
export BAML_LOG=off
|
||||
- text: "Install Express and types"
|
||||
command: |
|
||||
npm install express && npm install --save-dev @types/express supertest
|
||||
incremental: true
|
||||
- text: "Add the server implementation"
|
||||
file: {src: ./walkthrough/08-server.ts, dest: src/server.ts}
|
||||
- text: "Start the server"
|
||||
command: |
|
||||
npx tsx src/server.ts
|
||||
- text: "Test with curl (in another terminal)"
|
||||
command: |
|
||||
curl -X POST http://localhost:3000/thread \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"message":"can you add 3 and 4"}'
|
||||
results:
|
||||
- text: |
|
||||
You should get an answer from the agent which includes the
|
||||
agentic trace, ending in a message like:
|
||||
|
||||
code: |
|
||||
{"intent":"done_for_now","message":"The sum of 3 and 4 is 7."}
|
||||
|
||||
- name: state-management
|
||||
title: "Chapter 9 - In-Memory State and Async Clarification"
|
||||
text: "Add state management and async clarification support."
|
||||
steps:
|
||||
- text: "for this section, we'll disable the baml logs. You can optionally enable them if you want to see more details."
|
||||
command: |
|
||||
export BAML_LOG=off
|
||||
- text: "Add some simple in-memory state management for threads"
|
||||
file: {src: ./walkthrough/09-state.ts, dest: src/state.ts}
|
||||
- text: |
|
||||
update the server to use the state management
|
||||
|
||||
* Add thread state management using `ThreadStore`
|
||||
* return thread IDs and response URLs from the /thread endpoint
|
||||
* implement GET /thread/:id
|
||||
* implement POST /thread/:id/response
|
||||
file: {src: ./walkthrough/09-server.ts, dest: src/server.ts}
|
||||
- text: "Start the server"
|
||||
command: |
|
||||
npx tsx src/server.ts
|
||||
- text: "Test clarification flow"
|
||||
command: |
|
||||
curl -X POST http://localhost:3000/thread \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"message":"can you multiply 3 and xyz"}'
|
||||
|
||||
- name: human-approval
|
||||
title: "Chapter 10 - Adding Human Approval"
|
||||
text: "Add support for human approval of operations."
|
||||
steps:
|
||||
- text: "for this section, we'll disable the baml logs. You can optionally enable them if you want to see more details."
|
||||
command: |
|
||||
export BAML_LOG=off
|
||||
- text: |
|
||||
update the server to handle human approvals
|
||||
|
||||
* Import `handleNextStep` to execute approved actions
|
||||
* Add two payload types to distinguish approvals from responses
|
||||
* Handle responses and approvals differently in the endpoint
|
||||
* Show better error messages when things go wrongs
|
||||
|
||||
file: {src: ./walkthrough/10-server.ts, dest: src/server.ts}
|
||||
- text: "Add a few methods to the agent to handle approvals and responses"
|
||||
file: {src: ./walkthrough/10-agent.ts, dest: src/agent.ts}
|
||||
- text: "Start the server"
|
||||
command: |
|
||||
npx tsx src/server.ts
|
||||
- text: "Test division with approval"
|
||||
command: |
|
||||
curl -X POST http://localhost:3000/thread \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"message":"can you divide 3 by 4"}'
|
||||
results:
|
||||
- text: "You should see:"
|
||||
code: |
|
||||
{
|
||||
"thread_id": "2b243b66-215a-4f37-8bc6-9ace3849043b",
|
||||
"events": [
|
||||
{
|
||||
"type": "user_input",
|
||||
"data": "can you divide 3 by 4"
|
||||
},
|
||||
{
|
||||
"type": "tool_call",
|
||||
"data": {
|
||||
"intent": "divide",
|
||||
"a": 3,
|
||||
"b": 4,
|
||||
"response_url": "/thread/2b243b66-215a-4f37-8bc6-9ace3849043b/response"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
- text: "reject the request with another curl call, changing the thread ID"
|
||||
command: |
|
||||
curl -X POST 'http://localhost:3000/thread/{thread_id}/response' \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"type": "approval", "approved": false, "comment": "I dont think thats right, use 5 instead of 4"}'
|
||||
results:
|
||||
- text: 'You should see: the last tool call is now `"intent":"divide","a":3,"b":5`'
|
||||
code: |
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"type": "user_input",
|
||||
"data": "can you divide 3 by 4"
|
||||
},
|
||||
{
|
||||
"type": "tool_call",
|
||||
"data": {
|
||||
"intent": "divide",
|
||||
"a": 3,
|
||||
"b": 4,
|
||||
"response_url": "/thread/2b243b66-215a-4f37-8bc6-9ace3849043b/response"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "tool_response",
|
||||
"data": "user denied the operation with feedback: \"I dont think thats right, use 5 instead of 4\""
|
||||
},
|
||||
{
|
||||
"type": "tool_call",
|
||||
"data": {
|
||||
"intent": "divide",
|
||||
"a": 3,
|
||||
"b": 5,
|
||||
"response_url": "/thread/1f1f5ff5-20d7-4114-97b4-3fc52d5e0816/response"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
- text: "now you can approve the operation"
|
||||
command: |
|
||||
curl -X POST 'http://localhost:3000/thread/{thread_id}/response' \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"type": "approval", "approved": true}'
|
||||
results:
|
||||
- text: "you should see the final message includes the tool response and final result!"
|
||||
code: |
|
||||
...
|
||||
{
|
||||
"type": "tool_response",
|
||||
"data": 0.5
|
||||
},
|
||||
{
|
||||
"type": "done_for_now",
|
||||
"message": "I divided 3 by 6 and the result is 0.5. If you have any more operations or queries, feel free to ask!",
|
||||
"response_url": "/thread/2b469403-c497-4797-b253-043aae830209/response"
|
||||
}
|
||||
|
||||
- name: humanlayer-approval
|
||||
title: "Chapter 11 - Human Approvals over email"
|
||||
text: |
|
||||
in this section, we'll add support for human approvals over email.
|
||||
|
||||
This will start a little bit contrived, just to get the concepts down -
|
||||
|
||||
We'll start by invoking the workflow from the CLI but approvals for `divide`
|
||||
and `request_more_information` will be handled over email,
|
||||
then the final `done_for_now` answer will be printed back to the CLI
|
||||
|
||||
While contrived, this is a great example of the flexibility you get from
|
||||
[factor 7 - contact humans with tools](https://github.com/humanlayer/12-factor-agents/blob/main/content/factor-7-contact-humans-with-tools.md)
|
||||
|
||||
steps:
|
||||
- text: "for this section, we'll disable the baml logs. You can optionally enable them if you want to see more details."
|
||||
command: |
|
||||
export BAML_LOG=off
|
||||
- text: "Install HumanLayer"
|
||||
command: |
|
||||
npm install humanlayer
|
||||
incremental: true
|
||||
- text: "Update CLI to send `divide` and `request_more_information` to a human via email"
|
||||
file: {src: ./walkthrough/11-cli.ts, dest: src/cli.ts}
|
||||
- text: "Run the CLI"
|
||||
command: |
|
||||
npx tsx src/index.ts 'can you divide 4 by 5'
|
||||
results:
|
||||
- text: "The last line of your program should mention human review step"
|
||||
code: |
|
||||
nextStep { intent: 'divide', a: 4, b: 5 }
|
||||
HumanLayer: Requested human approval from HumanLayer cloud
|
||||
- text: |
|
||||
go ahead and respond to the email with some feedback:
|
||||
|
||||

|
||||
- text: |
|
||||
you should get another email with an updated attempt based on your feedback!
|
||||
|
||||
You can go ahead and approve this one:
|
||||
|
||||

|
||||
results:
|
||||
- text: and your final output will look like
|
||||
code: |
|
||||
nextStep {
|
||||
intent: 'done_for_now',
|
||||
message: 'The division of 4 by 5 is 0.8. If you have any other calculations or questions, feel free to ask!'
|
||||
}
|
||||
The division of 4 by 5 is 0.8. If you have any other calculations or questions, feel free to ask!
|
||||
- text: |
|
||||
lets implement the `request_more_information` flow as well
|
||||
file: {src: ./walkthrough/11b-cli.ts, dest: src/cli.ts}
|
||||
- text: |
|
||||
lets test the require_approval flow as by asking for a calculation
|
||||
with garbled input:
|
||||
command: |
|
||||
npx tsx src/index.ts 'can you multiply 4 and xyz'
|
||||
- text: "You should get an email with a request for clarification"
|
||||
command: |
|
||||
Can you clarify what 'xyz' represents in this context? Is it a specific number, variable, or something else?
|
||||
- text: you can response with something like
|
||||
command: |
|
||||
use 8 instead of xyz
|
||||
results:
|
||||
- text: you should see a final result on the CLI like
|
||||
code: |
|
||||
I have multiplied 4 and xyz, using the value 8 for xyz, resulting in 32.
|
||||
- text: |
|
||||
as a final step, lets explore using a custom html template for the email
|
||||
file: {src: ./walkthrough/11c-cli.ts, dest: src/cli.ts}
|
||||
- text: |
|
||||
first try with divide:
|
||||
command: |
|
||||
npx tsx src/index.ts 'can you divide 4 by 5'
|
||||
results:
|
||||
- text: |
|
||||
you should see a slightly different email with the custom template
|
||||
|
||||

|
||||
|
||||
feel free to run with the flow and then you can try updating the template to your liking
|
||||
|
||||
(if you're using cursor, something as simple as highlighting the template and asking to "make it better"
|
||||
should do the trick)
|
||||
|
||||
try triggering "request_more_information" as well!
|
||||
- text: |
|
||||
thats it - in the next chapter, we'll build a fully email-driven
|
||||
workflow agent that uses webhooks for human approval
|
||||
|
||||
- name: humanlayer-webhook
|
||||
title: "Chapter XX - HumanLayer Webhook Integration"
|
||||
text: |
|
||||
the previous sections used the humanlayer SDK in "synchronous mode" - that
|
||||
means every time we wait for human approval, we sit in a loop
|
||||
polling until the human response if received.
|
||||
|
||||
That's obviously not ideal, especially for production workloads,
|
||||
so in this section we'll implement [factor 6 - launch / pause / resume with simple APIs](https://github.com/humanlayer/12-factor-agents/blob/main/content/factor-6-launch-pause-resume.md)
|
||||
by updating the server to end processing after contacting a human, and use webhooks to receive the results.
|
||||
|
||||
steps:
|
||||
- text: |
|
||||
add code to initialize humanlayer in the server
|
||||
file: {src: ./walkthrough/12-1-server-init.ts, dest: src/server.ts}
|
||||
- text: |
|
||||
next, lets update the /thread endpoint to
|
||||
|
||||
1. handle requests asynchronously, returning immediately
|
||||
2. create a human contact on request_more_information and done_for_now calls
|
||||
|
||||
# file: {src: }
|
||||
- text: |
|
||||
Update the server to be able to handle request_clarification responses
|
||||
|
||||
- remove the old /response endpoint and types
|
||||
- update the /thread endpoint to run processing asynchronously, return immediately
|
||||
- send a state.threadId when requesting human responses
|
||||
- add a handleHumanResponse function to process the human response
|
||||
- add a /webhook endpoint to handle the webhook response
|
||||
|
||||
file: {src: ./walkthrough/12a-server.ts, dest: src/server.ts}
|
||||
- text: "Start the server in another terminal"
|
||||
command: |
|
||||
npx tsx src/server.ts
|
||||
- text: |
|
||||
now that the server is running, send a payload to the '/thread' endpoint
|
||||
- text: __ do the response step
|
||||
- text: __ now handle approvals for divide
|
||||
- text: __ now also handle done_for_now
|
||||
|
||||
Reference in New Issue
Block a user