|
| 1 | +--- |
| 2 | +title: Runtime Events |
| 3 | +--- |
| 4 | + |
| 5 | +<Info> |
| 6 | +This feature was added in TODO |
| 7 | +</Info> |
| 8 | + |
| 9 | +When running multi-step workflows, you need to be able to get information about |
| 10 | +the running workflow. You might need this information to show incremental |
| 11 | +results to your app’s users, or to debug a complex workflow combining multiple |
| 12 | +LLM calls. |
| 13 | + |
| 14 | +BAML makes this possible though an event system that connects variables in your |
| 15 | +BAML Workflow code to the Python/TypeScript/etc client code that you used to |
| 16 | +invoke the workflow. |
| 17 | + |
| 18 | +## Using Markdown blocks to track execution |
| 19 | + |
| 20 | +Markdown Blocks are automatically tracked when you run BAML |
| 21 | +workflows, and your client code can track which block is currently executing. In |
| 22 | +the following example, your client could directly use the markdown headers to |
| 23 | +render the current status on a status page: |
| 24 | + |
| 25 | +```baml BAML |
| 26 | +struct Post { |
| 27 | + title string |
| 28 | + content string |
| 29 | +} |
| 30 | +
|
| 31 | +// Browse a URL and produce a number of posts describing |
| 32 | +// its what was found there for our marketing site. |
| 33 | +function MakePosts(source_url: string, count: int) -> Post[] { |
| 34 | + # Summarize Source |
| 35 | + let source = LLMSummarizeSource(source_url); |
| 36 | + |
| 37 | + # Determine Topic |
| 38 | + let topic = LLMInferTopic(source); |
| 39 | + |
| 40 | + # Generate Marketing Post Ideas |
| 41 | + let ideas: string[] = LLMIdeas(topic, source); |
| 42 | + |
| 43 | + # Generate posts |
| 44 | + let posts: Post[] = []; |
| 45 | + for (idea in ideas) { |
| 46 | + |
| 47 | + ## Create the post |
| 48 | + let post = LLMGeneratePost(idea, source); |
| 49 | + |
| 50 | + ## Quality control |
| 51 | + let quality = LLMJudgePost(post, idea, source); |
| 52 | + if (quality > 8) { |
| 53 | + posts.push(post); |
| 54 | + } |
| 55 | + } |
| 56 | +} |
| 57 | +``` |
| 58 | + |
| 59 | +In your client code, you can bind events to callbacks: |
| 60 | + |
| 61 | +<Tabs> |
| 62 | +<Tab title="Python" language="python"> |
| 63 | +```python |
| 64 | + # app.py |
| 65 | + from baml_client.sync_client import { b } |
| 66 | + from baml_client.types import Event |
| 67 | + import baml_client.events |
| 68 | + |
| 69 | + def Example(): |
| 70 | + # Get an Events callback collector with the right type |
| 71 | + # for your MakePosts() function. |
| 72 | + ev = events.MakePosts() |
| 73 | + |
| 74 | + # Associate the block event with your own callback. |
| 75 | + events.on_block(lambda ev: print(ev.block_label)) |
| 76 | + |
| 77 | + # Invoke the function. |
| 78 | + posts = b.MakePosts("https://wikipedia.org/wiki/DNA", {"events": ev}) |
| 79 | + print(posts) |
| 80 | +``` |
| 81 | +</Tab> |
| 82 | +<Tab title="TypeScript" language="typescript"> |
| 83 | +```typescript |
| 84 | + // index.ts |
| 85 | + import { b, events } from "./baml-client" |
| 86 | + import type { Event } from "./baml-client/types" |
| 87 | + |
| 88 | + async function Example() { |
| 89 | + // Get an Events callback collector with the right type |
| 90 | + // for your MakePosts() function. |
| 91 | + let ev = events.MakePosts() |
| 92 | + |
| 93 | + // Associate the block event with your own callback. |
| 94 | + events.on_block((ev) => { |
| 95 | + console.log(ev.block_label) |
| 96 | + }); |
| 97 | + |
| 98 | + // Invoke the function. |
| 99 | + const posts = await b.MakePosts( |
| 100 | + "https://wikipedia.org/wiki/DNA", |
| 101 | + {"events": ev} |
| 102 | + ) |
| 103 | + console.log(posts) |
| 104 | + } |
| 105 | +``` |
| 106 | +</Tab> |
| 107 | +<Tab title="Go" language="go"> |
| 108 | +```go |
| 109 | +// main.go |
| 110 | +package main |
| 111 | + |
| 112 | +import ( |
| 113 | + "context" |
| 114 | + "fmt" |
| 115 | + "log" |
| 116 | + |
| 117 | + b "example.com/myproject/baml_client" |
| 118 | + "example.com/myproject/baml_client/events" |
| 119 | + "example.com/myproject/baml_client/types" |
| 120 | +) |
| 121 | + |
| 122 | +func main() { |
| 123 | + ctx := context.Background() |
| 124 | + |
| 125 | + // Get an Events callback collector with the right type |
| 126 | + // for your MakePosts() function. |
| 127 | + ev := events.NewMakePosts() |
| 128 | + |
| 129 | + // Associate the block event with your own callback. |
| 130 | + events.OnBlock(func(ev *types.BlockEvent) { |
| 131 | + fmt.Println(ev.BlockLabel) |
| 132 | + }) |
| 133 | + |
| 134 | + // Invoke the function. |
| 135 | + posts, err := b.MakePosts(ctx, "https://wikipedia.org/wiki/DNA", &b.MakePostsOptions{ |
| 136 | + Events: ev, |
| 137 | + }) |
| 138 | + if err != nil { |
| 139 | + log.Fatal(err) |
| 140 | + } |
| 141 | + fmt.Printf("%+v\n", posts) |
| 142 | +} |
| 143 | +``` |
| 144 | +</Tab> |
| 145 | +</Tabs> |
| 146 | + |
| 147 | +## Using `emit` to track variables |
| 148 | + |
| 149 | +Variable update can also be tracked with events. To mark a variable as visible |
| 150 | +to the event system, use the `emit` keyword when you declare the variable. Let’s |
| 151 | +see how we would use this capability to track the progress of our marketing post |
| 152 | +generation workflow: |
| 153 | + |
| 154 | +```tsx |
| 155 | +function MakePosts(source_url: string) -> Post[] { |
| 156 | + # Summarize Source |
| 157 | + let source = LLMSummarizeSource(source_url); |
| 158 | + |
| 159 | + # Determine Topic |
| 160 | + let topic = LLMInferTopic(source); |
| 161 | + |
| 162 | + # Generate Marketing Post Ideas |
| 163 | + let ideas: string[] = LLMIdeas(topic, source); |
| 164 | + |
| 165 | + // Track how many posts we need to generate. <-*** |
| 166 | + let posts_target_length = ideas.len(); |
| 167 | + emit let progress_percent: int = 0; |
| 168 | + |
| 169 | + # Generate posts |
| 170 | + let posts: Post[] = []; |
| 171 | + for ((i,idea) in ideas.enumerate()) { |
| 172 | + |
| 173 | + ## Create the post |
| 174 | + let post = LLMGeneratePost(idea, source); |
| 175 | + |
| 176 | + ## Quality control |
| 177 | + let quality = LLMJudgePost(post, idea, source); |
| 178 | + if (quality > 8) { |
| 179 | + posts.push(post); |
| 180 | + } else { |
| 181 | + posts_target_length -= 1; |
| 182 | + } |
| 183 | + |
| 184 | + // *** This update will trigger events visible to the client. |
| 185 | + progress_percent = i * 100 / posts_target_length |
| 186 | + } |
| 187 | +} |
| 188 | +``` |
| 189 | + |
| 190 | +When you generate a BAML client, the events structure for ` MakePosts` will |
| 191 | +accept callbacks for `progress_percent` because we marked that variable with |
| 192 | +`emit`, and the callbacks will receive an `int` data payload, because |
| 193 | +`progress_percent` is an `int`. |
| 194 | + |
| 195 | +In your client code, you can track these emitted variables: |
| 196 | + |
| 197 | +<Tabs> |
| 198 | +<Tab title="Python" language="python"> |
| 199 | +```python |
| 200 | +# app.py |
| 201 | +from baml_client.sync_client import { b } |
| 202 | +from baml_client.types import Event |
| 203 | +import baml_client.events |
| 204 | + |
| 205 | +def Example(): |
| 206 | + # Get an Events callback collector with the right type |
| 207 | + # for your MakePosts() function. |
| 208 | + ev = events.MakePosts() |
| 209 | + |
| 210 | + # Track the progress_percent variable updates |
| 211 | + events.on_progress_percent(lambda percent: print(f"Progress: {percent}%")) |
| 212 | + |
| 213 | + # Invoke the function. |
| 214 | + posts = b.MakePosts("https://wikipedia.org/wiki/DNA", {"events": ev}) |
| 215 | + print(posts) |
| 216 | +``` |
| 217 | +</Tab> |
| 218 | +<Tab title="TypeScript" language="typescript"> |
| 219 | +```typescript |
| 220 | +// index.ts |
| 221 | +import { b, events } from "./baml-client" |
| 222 | +import type { Event } from "./baml-client/types" |
| 223 | + |
| 224 | +async function Example() { |
| 225 | + // Get an Events callback collector with the right type |
| 226 | + // for your MakePosts() function. |
| 227 | + let ev = events.MakePosts() |
| 228 | + |
| 229 | + // Track the progress_percent variable updates |
| 230 | + events.on_progress_percent((percent) => { |
| 231 | + console.log(`Progress: ${percent}%`) |
| 232 | + }); |
| 233 | + |
| 234 | + // Invoke the function. |
| 235 | + const posts = await b.MakePosts( |
| 236 | + "https://wikipedia.org/wiki/DNA", |
| 237 | + {"events": ev} |
| 238 | + ) |
| 239 | + console.log(posts) |
| 240 | +} |
| 241 | +``` |
| 242 | +</Tab> |
| 243 | +<Tab title="Go" language="go"> |
| 244 | +```go |
| 245 | +// main.go |
| 246 | +package main |
| 247 | + |
| 248 | +import ( |
| 249 | + "context" |
| 250 | + "fmt" |
| 251 | + "log" |
| 252 | + |
| 253 | + b "example.com/myproject/baml_client" |
| 254 | + "example.com/myproject/baml_client/events" |
| 255 | + "example.com/myproject/baml_client/types" |
| 256 | +) |
| 257 | + |
| 258 | +func main() { |
| 259 | + ctx := context.Background() |
| 260 | + |
| 261 | + // Get an Events callback collector with the right type |
| 262 | + // for your MakePosts() function. |
| 263 | + ev := events.NewMakePosts() |
| 264 | + |
| 265 | + // Track the progress_percent variable updates |
| 266 | + events.OnProgressPercent(func(percent int) { |
| 267 | + fmt.Printf("Progress: %d%%\n", percent) |
| 268 | + }) |
| 269 | + |
| 270 | + // Invoke the function. |
| 271 | + posts, err := b.MakePosts(ctx, "https://wikipedia.org/wiki/DNA", &b.MakePostsOptions{ |
| 272 | + Events: ev, |
| 273 | + }) |
| 274 | + if err != nil { |
| 275 | + log.Fatal(err) |
| 276 | + } |
| 277 | + fmt.Printf("%+v\n", posts) |
| 278 | +} |
| 279 | +``` |
| 280 | +</Tab> |
| 281 | +</Tabs> |
| 282 | + |
| 283 | +For details about the types of events, see [BAML Language Reference](/ref/baml_client/events) |
| 284 | + |
| 285 | +# Event Details |
| 286 | + |
| 287 | +It helps to understand the following concepts when trying to do more complex |
| 288 | +things with Events: |
| 289 | + |
| 290 | + 1. **Separate Thread** To avoid interfering with the rest of your BAML code, |
| 291 | + callbacks are run concurrently in a separate execution thread. |
| 292 | + 2. Events are meant for local tracking. If you asign a value to a new (non- |
| 293 | + emit) variable, this new variable doesn't get its updates tracked. If you |
| 294 | + pass a value as a parameter to a function, updates made within that |
| 295 | + function will not be tracked. |
0 commit comments