Skip to content

Commit 27f0694

Browse files
aaronvgcursoragent
andauthored
Expose tags in the collector. Allow passing tags via baml function baml_options (#2528)
# Pull Request Template Thanks for taking the time to fill out this pull request! ## Issue Reference Please link to any related issues - [ ] This PR fixes/closes #[issue number] ## Changes Please describe the changes proposed in this pull request This PR surfaces span/function call tags via the `Collector`'s `FunctionLog` across multiple language clients. This allows users to retrieve tags, including those inherited from parent `@trace` contexts, directly from the `FunctionLog` object. Specifically: - The Rust core `FunctionLog` now exposes a `tags()` method (an alias for `metadata()`). - This `tags` accessor is exposed in the Python, TypeScript, and Ruby language clients. - Integration tests have been added for Python and TypeScript to verify that tags set in a parent `@trace` context are correctly propagated and visible in the child function's `FunctionLog.tags`. ## Testing Please describe how you tested these changes - [x] Unit tests added/updated (Rust core) - [x] Manual testing performed (Python and TypeScript integration tests) - [ ] Tested in [environment] The Rust core changes were verified with `cargo test -p baml-runtime --lib`. New Python integration test `test_functionlog_tags_inherit_from_parent_trace` was run and passed. New TypeScript integration test `"should include parent trace tags in FunctionLog.tags"` was added and compiles; it will run in CI. ## Screenshots If applicable, add screenshots to help explain your changes [Add screenshots here...] ## PR Checklist Please ensure you've completed these items - [ ] I have read and followed the contributing guidelines - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings ## Additional Notes Add any other context about the PR here The Go client was mentioned in the original task, but no explicit `tags` accessor was added to its `FunctionLog` as part of these changes. The existing `metadata` accessor in Go would provide similar functionality if `tags` is an alias for `metadata`. The TypeScript integration test compiles against the updated `native.d.ts` and runtime wrapper implementation; it will run in CI once the workspace’s TS dependencies are fully available. --- <a href="https://cursor.com/background-agent?bcId=bc-83c0460d-95c0-4ce7-afd0-bee8c222af3a"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/open-in-cursor-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/open-in-cursor-light.svg"><img alt="Open in Cursor" src="https://cursor.com/open-in-cursor.svg"></picture></a>&nbsp;<a href="https://cursor.com/agents?id=bc-83c0460d-95c0-4ce7-afd0-bee8c222af3a"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/open-in-web-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/open-in-web-light.svg"><img alt="Open in Web" src="https://cursor.com/open-in-web.svg"></picture></a> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Add `tags` to TS sync client call options and pass them to runtime, with tests ensuring FunctionLog includes parent trace and per-call tags. > > - **TypeScript Client**: > - Add `tags?: Record<string, string>` to `BamlCallOptions` in `sync_client.ts` and forward `options.tags || {}` to all `runtime.callFunctionSync` calls. > - **Tracing/Tests**: > - Update tests to set parent trace tags and assert they appear in `FunctionLog.tags`. > - Add test to pass per-call tags and verify both parent and function-specific tags are preserved. > - **Generated Code**: > - Large auto-formatted changes in generated TypeScript request code (no functional deltas highlighted). > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 73cef16. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Cursor Agent <[email protected]>
1 parent 4bb2f33 commit 27f0694

553 files changed

Lines changed: 165920 additions & 158429 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

engine/baml-runtime/src/async_vm_runtime.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ impl BamlAsyncVmRuntime {
147147
cb: Option<&ClientRegistry>,
148148
collectors: Option<Vec<Arc<Collector>>>,
149149
env_vars: HashMap<String, String>,
150+
tags: Option<HashMap<String, String>>,
150151
cancel_tripwire: Arc<TripWire>,
151152
) -> (anyhow::Result<FunctionResult>, FunctionCallId) {
152153
// Find the function.
@@ -172,6 +173,7 @@ impl BamlAsyncVmRuntime {
172173
tb,
173174
cb,
174175
collectors,
176+
tags,
175177
env_vars,
176178
cancel_tripwire,
177179
)
@@ -181,7 +183,15 @@ impl BamlAsyncVmRuntime {
181183
.llm_runtime
182184
.tracer_wrapper
183185
.get_or_create_tracer(&env_vars)
184-
.start_call(&function_name, ctx, params, true, false, collectors.clone())
186+
.start_call(
187+
&function_name,
188+
ctx,
189+
params,
190+
true,
191+
false,
192+
collectors.clone(),
193+
None,
194+
)
185195
.curr_call_id();
186196

187197
let Some(expr_fn) = self
@@ -246,6 +256,7 @@ impl BamlAsyncVmRuntime {
246256
(anyhow::Result<FunctionResult>, FunctionCallId),
247257
)>();
248258

259+
let tags_clone = tags.clone();
249260
let vm_result = 'mainloop: loop {
250261
match vm.exec() {
251262
Ok(VmExecState::Await(idx)) => {
@@ -372,6 +383,7 @@ impl BamlAsyncVmRuntime {
372383
// TODO: Collectors are not supported yet.
373384
// let collectors = collectors.clone();
374385
let env_vars = env_vars.clone();
386+
let tags_for_future = tags_clone.clone();
375387

376388
let futures_tx = futures_tx.clone();
377389

@@ -388,6 +400,7 @@ impl BamlAsyncVmRuntime {
388400
tb.as_ref(),
389401
cb.as_ref(),
390402
None,
403+
tags_for_future,
391404
env_vars,
392405
cancel_tripwire,
393406
)
@@ -646,6 +659,7 @@ impl BamlAsyncVmRuntime {
646659
cb: Option<&ClientRegistry>,
647660
collectors: Option<Vec<Arc<Collector>>>,
648661
env_vars: HashMap<String, String>,
662+
tags: Option<HashMap<String, String>>,
649663
cancel_tripwire: Arc<TripWire>,
650664
) -> (anyhow::Result<FunctionResult>, FunctionCallId) {
651665
self.async_runtime.block_on(self.call_function(
@@ -656,6 +670,7 @@ impl BamlAsyncVmRuntime {
656670
cb,
657671
collectors,
658672
env_vars,
673+
tags,
659674
cancel_tripwire,
660675
))
661676
}
@@ -669,6 +684,7 @@ impl BamlAsyncVmRuntime {
669684
cb: Option<&ClientRegistry>,
670685
collectors: Option<Vec<Arc<Collector>>>,
671686
env_vars: HashMap<String, String>,
687+
tags: Option<HashMap<String, String>>,
672688
// FunctionResultStream is responsible for freeing the TripWire and the clean up.
673689
cancel_tripwire: Arc<TripWire>,
674690
) -> anyhow::Result<FunctionResultStream> {
@@ -680,6 +696,7 @@ impl BamlAsyncVmRuntime {
680696
cb,
681697
collectors,
682698
env_vars,
699+
tags,
683700
cancel_tripwire,
684701
)
685702
}

engine/baml-runtime/src/cli/repl.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,7 @@ impl ReplState {
527527
None,
528528
None,
529529
None,
530+
None, // tags
530531
env_vars,
531532
TripWire::new_with_on_drop(
532533
None,

engine/baml-runtime/src/cli/serve/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,7 @@ Streaming is available via http://localhost:{port}/stream/{{FunctionName}}, but
357357
None,
358358
client_registry.as_ref(),
359359
None,
360+
None, // tags
360361
env_vars,
361362
TripWire::new(None),
362363
)
@@ -460,6 +461,7 @@ Streaming is available via http://localhost:{port}/stream/{{FunctionName}}, but
460461
client_registry.as_ref(),
461462
Some(vec![]),
462463
env_vars,
464+
None, // tags
463465
TripWire::new(None),
464466
);
465467

engine/baml-runtime/src/eval_expr.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@ async fn beta_reduce<'a>(
326326
None,
327327
None,
328328
None,
329+
None, // tags
329330
env.env_vars.clone(),
330331
TripWire::new(None),
331332
)
@@ -1159,6 +1160,7 @@ test Poems {
11591160
Some(on_event),
11601161
None,
11611162
HashMap::new(),
1163+
None,
11621164
TripWire::new(None),
11631165
None::<Box<dyn Fn()>>,
11641166
)
@@ -1293,6 +1295,7 @@ test TestMakePerson() {
12931295
Some(on_event),
12941296
None,
12951297
HashMap::new(),
1298+
None,
12961299
TripWire::new(None),
12971300
None::<Box<dyn Fn()>>,
12981301
)
@@ -1371,6 +1374,7 @@ test UseFunction() {
13711374
"OPENAI_API_KEY".to_string(),
13721375
std::env::var("OPENAI_API_KEY").expect("OPENAI_API_KEY is not set."),
13731376
)]),
1377+
None,
13741378
TripWire::new(None),
13751379
None::<Box<dyn Fn()>>,
13761380
)

engine/baml-runtime/src/lib.rs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,7 @@ impl BamlRuntime {
452452
expr_tx: Option<mpsc::UnboundedSender<Vec<internal_baml_diagnostics::SerializedSpan>>>,
453453
collector: Option<Arc<Collector>>,
454454
env_vars: HashMap<String, String>,
455+
tags: Option<HashMap<String, String>>,
455456
cancel_tripwire: Arc<TripWire>,
456457
on_tick: Option<G>,
457458
) -> (Result<TestResponse>, FunctionCallId)
@@ -471,6 +472,7 @@ impl BamlRuntime {
471472
true,
472473
true, // tests always stream which is why there's an on_event
473474
collector.as_ref().map(|c| vec![c.clone()]),
475+
tags.as_ref(),
474476
);
475477

476478
let expr_fn = self.inner.ir().find_expr_fn(function_name);
@@ -576,6 +578,7 @@ impl BamlRuntime {
576578
self.async_runtime.clone(),
577579
// TODO: collectors here?
578580
vec![],
581+
None, // tags
579582
cancel_tripwire,
580583
)?;
581584
let (response_res, call_uuid) = stream
@@ -673,6 +676,7 @@ impl BamlRuntime {
673676
on_event: Option<F>,
674677
collector: Option<Arc<Collector>>,
675678
env_vars: HashMap<String, String>,
679+
tags: Option<HashMap<String, String>>,
676680
cancel_tripwire: Arc<TripWire>,
677681
on_tick: Option<G>,
678682
) -> (Result<TestResponse>, FunctionCallId)
@@ -689,6 +693,7 @@ impl BamlRuntime {
689693
None,
690694
collector,
691695
env_vars,
696+
tags,
692697
cancel_tripwire,
693698
on_tick,
694699
)
@@ -706,6 +711,7 @@ impl BamlRuntime {
706711
cb: Option<&ClientRegistry>,
707712
collectors: Option<Vec<Arc<Collector>>>,
708713
env_vars: HashMap<String, String>,
714+
tags: Option<HashMap<String, String>>,
709715
cancel_tripwire: Arc<TripWire>,
710716
) -> (Result<FunctionResult>, FunctionCallId) {
711717
let fut = self.call_function(
@@ -715,6 +721,7 @@ impl BamlRuntime {
715721
tb,
716722
cb,
717723
collectors,
724+
tags,
718725
env_vars,
719726
cancel_tripwire,
720727
);
@@ -729,6 +736,7 @@ impl BamlRuntime {
729736
tb: Option<&TypeBuilder>,
730737
cb: Option<&ClientRegistry>,
731738
collectors: Option<Vec<Arc<Collector>>>,
739+
tags: Option<HashMap<String, String>>,
732740
env_vars: HashMap<String, String>,
733741
cancel_tripwire: Arc<TripWire>,
734742
) -> (Result<FunctionResult>, FunctionCallId) {
@@ -742,6 +750,7 @@ impl BamlRuntime {
742750
env_vars,
743751
None,
744752
cancel_tripwire,
753+
tags,
745754
))
746755
.await;
747756
res
@@ -779,6 +788,7 @@ impl BamlRuntime {
779788
env_vars: HashMap<String, String>,
780789
expr_tx: Option<mpsc::UnboundedSender<Vec<internal_baml_diagnostics::SerializedSpan>>>,
781790
cancel_tripwire: Arc<TripWire>,
791+
tags: Option<HashMap<String, String>>,
782792
) -> (Result<FunctionResult>, FunctionCallId) {
783793
// baml_log::info!("env vars: {:#?}", env_vars.clone());
784794
baml_log::set_from_env(&env_vars).unwrap();
@@ -789,7 +799,15 @@ impl BamlRuntime {
789799
let call = self
790800
.tracer_wrapper
791801
.get_or_create_tracer(&env_vars)
792-
.start_call(&function_name, ctx, params, true, false, collectors);
802+
.start_call(
803+
&function_name,
804+
ctx,
805+
params,
806+
true,
807+
false,
808+
collectors,
809+
tags.as_ref(),
810+
);
793811
let curr_call_id = call.curr_call_id();
794812

795813
let fake_syntax_span = Span::fake();
@@ -984,6 +1002,7 @@ impl BamlRuntime {
9841002
cb: Option<&ClientRegistry>,
9851003
collectors: Option<Vec<Arc<Collector>>>,
9861004
env_vars: HashMap<String, String>,
1005+
tags: Option<HashMap<String, String>>,
9871006
expr_tx: Option<mpsc::UnboundedSender<Vec<SerializedSpan>>>,
9881007
cancel_tripwire: Arc<TripWire>,
9891008
) -> Result<FunctionResultStream> {
@@ -996,6 +1015,7 @@ impl BamlRuntime {
9961015
#[cfg(not(target_arch = "wasm32"))]
9971016
self.async_runtime.clone(),
9981017
collectors.unwrap_or_default(),
1018+
tags,
9991019
cancel_tripwire,
10001020
)
10011021
}
@@ -1009,6 +1029,7 @@ impl BamlRuntime {
10091029
cb: Option<&ClientRegistry>,
10101030
collectors: Option<Vec<Arc<Collector>>>,
10111031
env_vars: HashMap<String, String>,
1032+
tags: Option<HashMap<String, String>>,
10121033
cancel_tripwire: Arc<TripWire>,
10131034
) -> Result<FunctionResultStream> {
10141035
self.stream_function_with_expr_events(
@@ -1019,6 +1040,7 @@ impl BamlRuntime {
10191040
cb,
10201041
collectors,
10211042
env_vars,
1043+
tags,
10221044
None,
10231045
cancel_tripwire,
10241046
)
@@ -1330,7 +1352,7 @@ impl ExperimentalTracingInterface for BamlRuntime {
13301352
) -> TracingCall {
13311353
self.tracer_wrapper
13321354
.get_or_create_tracer(env_vars)
1333-
.start_call(function_name, ctx, params, false, false, None)
1355+
.start_call(function_name, ctx, params, false, false, None, None)
13341356
}
13351357

13361358
#[cfg(not(target_arch = "wasm32"))]
@@ -1505,7 +1527,7 @@ async fn expr_eval_result(
15051527
Ok(expr_fn) => {
15061528
log::trace!("Calling function: {function_name}");
15071529
let collectors = collector.as_ref().map(|c| vec![c.clone()]);
1508-
let call = tracer.start_call(function_name, mgr, params, true, false, collectors);
1530+
let call = tracer.start_call(function_name, mgr, params, true, false, collectors, None);
15091531

15101532
let ctx = mgr.create_ctx(tb, cb, env_vars.clone(), call.new_call_id_stack.clone())?;
15111533
let env = EvalEnv {

engine/baml-runtime/src/runtime_methods/stream_function.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ impl InternalBamlRuntime {
5050
ctx: RuntimeContext,
5151
#[cfg(not(target_arch = "wasm32"))] tokio_runtime: Arc<tokio::runtime::Runtime>,
5252
collectors: Vec<Arc<Collector>>,
53+
tags: Option<HashMap<String, String>>,
5354
cancel_tripwire: Arc<TripWire>,
5455
) -> Result<FunctionResultStream> {
5556
let is_expr_fn = self.get_expr_function(&function_name, &ctx).is_ok();
@@ -84,6 +85,7 @@ impl InternalBamlRuntime {
8485
#[cfg(not(target_arch = "wasm32"))]
8586
tokio_runtime,
8687
collectors,
88+
tags,
8789
cancel_tripwire,
8890
})
8991
} else {
@@ -104,6 +106,7 @@ impl InternalBamlRuntime {
104106
#[cfg(not(target_arch = "wasm32"))]
105107
tokio_runtime,
106108
collectors,
109+
tags,
107110
cancel_tripwire,
108111
})
109112
}

engine/baml-runtime/src/test_executor/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ impl TestExecutor for BamlRuntime {
294294
on_event,
295295
None,
296296
env_vars,
297+
None, // tags
297298
TripWire::new(None), // No tripwire for test executor,
298299
on_tick,
299300
)

engine/baml-runtime/src/tests.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,17 @@ async fn test_call_function() -> Result<FunctionResult> {
9090
);
9191

9292
let (res, _) = runtime
93-
.call_function("ExtractNames".to_string(), &params, &ctx)
93+
.call_function(
94+
"ExtractNames".to_string(),
95+
&params,
96+
&ctx,
97+
None,
98+
None,
99+
None,
100+
None, // tags
101+
std::env::vars().collect(),
102+
stream_cancel::Tripwire::new(None),
103+
)
94104
.await;
95105
let res = res?;
96106

engine/baml-runtime/src/tracing/mod.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -408,9 +408,23 @@ impl BamlTracer {
408408
is_stream: bool,
409409
// baml_src_hash: Option<String>,
410410
collectors: Option<Vec<Arc<Collector>>>,
411+
tags: Option<&HashMap<String, String>>,
411412
) -> TracingCall {
412413
self.trace_stats.guard().start();
413-
let (call_id, call_stack, last_tags, global_tags) = ctx.enter(function_name);
414+
let (call_id, call_stack, mut ctx_tags, global_tags) = ctx.enter(function_name);
415+
416+
if let Some(tag_map) = tags {
417+
if !tag_map.is_empty() {
418+
log::debug!("start_call: incoming tags: {:#?}", tag_map);
419+
let tag_values: HashMap<String, BamlValue> = tag_map
420+
.iter()
421+
.map(|(k, v)| (k.clone(), BamlValue::String(v.clone())))
422+
.collect();
423+
ctx.upsert_tags(tag_values.clone());
424+
ctx_tags.extend(tag_values);
425+
log::debug!("start_call: ctx_tags after extend: {:#?}", ctx_tags);
426+
}
427+
}
414428

415429
log::trace!(
416430
"\n{}------------------- Entering {:?}, ctx chain {:#?}",
@@ -427,7 +441,7 @@ impl BamlTracer {
427441
start_time: web_time::SystemTime::now(),
428442
// Note these tags are the ones currently on the stack. While the function runs we may register
429443
// more tags with set_tags(). Those are picked up via a diff event (SetTags)
430-
tags: last_tags.clone(),
444+
tags: ctx_tags.clone(),
431445
};
432446
// println!("---- {} ctx {:#?}", function_name, ctx);
433447
// baml_log::info!("---- {} ctx {:#?}", function_name, ctx);
@@ -460,7 +474,7 @@ impl BamlTracer {
460474
EvaluationContext {
461475
tags: global_tags
462476
.into_iter()
463-
.chain(last_tags)
477+
.chain(ctx_tags)
464478
.map(|(k, v)| (k, serde_json::to_value(v).unwrap_or_default()))
465479
.collect(),
466480
},

engine/baml-runtime/src/tracingv2/storage/storage.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,11 @@ impl FunctionLog {
520520
pub fn metadata(&mut self) -> HashMap<String, serde_json::Value> {
521521
self.get_inner().lock().unwrap().metadata.clone()
522522
}
523+
524+
/// Backwards-compatible alias for metadata used by some language clients as "tags"
525+
pub fn tags(&mut self) -> HashMap<String, serde_json::Value> {
526+
self.get_inner().lock().unwrap().metadata.clone()
527+
}
523528
}
524529

525530
impl Drop for FunctionLog {

0 commit comments

Comments
 (0)