Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding new docs for usage #1288

Draft
wants to merge 1 commit into
base: canary
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
207 changes: 207 additions & 0 deletions fern/03-reference/baml_client/id.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
---
title: id
---

<Warning>
`id` is in proposal stage and may change. It is not yet available in any language. Please leave feedback on our discord.
</Warning>

The `id` property allows you to get a unique identifier for each function call. This is particularly useful when tracking specific calls in logs, especially when running multiple functions in parallel or when using streaming responses.

## Quick Start

<Tabs>
<Tab title="Python">
```python
from baml_client import b

# Get both id and result
id, result = b.id.ExtractResume("...")

# Use id with logger
logger = b.create_logger()
other_id, result = b.id.ExtractResume("...", baml_options={"logger": logger})
print(logger.id(other_id).usage) # Get usage for specific call
```
</Tab>

<Tab title="TypeScript">
```typescript
import { b } from 'baml_client'

// Get both id and result
const { id, data: result } = await b.id.ExtractResume("...")

// Use id with logger
const logger = b.createLogger()
const { id: otherId, data: result2 } = await b.id.ExtractResume("...", { logger })
console.log(logger.id(otherId)?.usage) // Get usage for specific call
```
</Tab>

<Tab title="Ruby">
```ruby
require 'baml_client'

# Get both id and result
id, result = Baml.Client.id.ExtractResume("...")

# Use id with logger
logger = Baml.Client.create_internal_logger
other_id, result = Baml.Client.id.ExtractResume("...", baml_options: { logger: logger })
print(logger.id(other_id).usage) # Get usage for specific call
```
</Tab>
</Tabs>

## Common Use Cases

### Tracking Parallel Calls

Use `id` to track individual function calls when running multiple functions in parallel.

<Tabs>
<Tab title="Python">
```python
from baml_client import b
import asyncio

async def run():
logger = b.create_logger()

# Run multiple functions in parallel
resume_id, resume = b.id.ExtractResume("...", baml_options={"logger": logger})
invoice_id, invoice = b.id.ExtractInvoice("...", baml_options={"logger": logger})

# Access specific logs by id
print(f"Resume usage: {logger.id(resume_id).usage}")
print(f"Invoice usage: {logger.id(invoice_id).usage}")

# Access all logs
print(f"Total usage: {logger.usage}")
```
</Tab>

<Tab title="TypeScript">
```typescript
import { b } from 'baml_client'

const logger = b.createLogger()

// Run multiple functions in parallel
const [
{ id: resumeId, data: resume },
{ id: invoiceId, data: invoice }
] = await Promise.all([
b.id.ExtractResume("...", { logger }),
b.id.ExtractInvoice("...", { logger })
])

// Access specific logs by id
console.log(`Resume usage: ${logger.id(resumeId)?.usage}`)
console.log(`Invoice usage: ${logger.id(invoiceId)?.usage}`)

// Access all logs
console.log(`Total usage: ${logger.usage}`)
```
</Tab>

<Tab title="Ruby">
```ruby
require 'baml_client'
require 'async'

Async do
logger = Baml.Client.create_internal_logger

# Run multiple functions in parallel
resume_id, resume = Baml.Client.id.ExtractResume("...", baml_options: { logger: logger })
invoice_id, invoice = Baml.Client.id.ExtractInvoice("...", baml_options: { logger: logger })

# Access specific logs by id
print("Resume usage: #{logger.id(resume_id).usage}")
print("Invoice usage: #{logger.id(invoice_id).usage}")

# Access all logs
print("Total usage: #{logger.usage}")
end
```
</Tab>
</Tabs>

### Using with Streaming

The `id` property works seamlessly with streaming responses, allowing you to track the entire stream's usage.

<Tabs>
<Tab title="Python">
```python
from baml_client.async_client import b

async def run():
logger = b.create_logger()
stream_id, stream = b.id.stream.ExtractResume("...", baml_options={"logger": logger})

async for chunk in stream:
print(chunk)

result = await stream.get_final_result()
print(f"Stream usage: {logger.id(stream_id).usage}")
```
</Tab>

<Tab title="TypeScript">
```typescript
const logger = b.createLogger()
const { id: streamId, data: stream } = await b.id.stream.ExtractResume("...", { logger })

for await (const chunk of stream) {
console.log(chunk)
}

const result = await stream.getFinalResult()
console.log(`Stream usage: ${logger.id(streamId)?.usage}`)
```
</Tab>

<Tab title="Ruby">
```ruby
require 'baml_client'

logger = Baml.Client.create_internal_logger
stream_id, stream = Baml.Client.id.stream.ExtractResume("...", baml_options: { logger: logger })

stream.each do |chunk|
print(chunk)
end

result = stream.get_final_result
print("Stream usage: #{logger.id(stream_id).usage}")
```
</Tab>
</Tabs>

## API Reference

### Return Types

The `id` property returns different types depending on the language:

| Language | Return Type | Description |
|----------|-------------|-------------|
| Python | `Tuple[str, T]` | A tuple of `(id, result)` where `T` is the function's return type |
| TypeScript | `{ id: string, data: T }` | An object with `id` and `data` properties where `T` is the function's return type |
| Ruby | `[String, T]` | An array of `[id, result]` where `T` is the function's return type |

## Related Topics
- [Logger](./logger) - Track function calls and usage metrics
- [with_options](./with_options) - Configure default options for function calls

## Best Practices
1. Use `id` when you need to track specific function calls in parallel operations
2. Always use the same logger instance when tracking related function calls
3. Consider using `with_options` to set up consistent logging and ID tracking

<Info>
IDs are globally unique and can be used to track function calls across your entire application, including in logs and monitoring systems.
</Info>
470 changes: 470 additions & 0 deletions fern/03-reference/baml_client/logger.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,470 @@
---
title: Logger
---

<Warning>
`Logger` is in proposal stage and may change. It is not yet available in any language. Please leave feedback on our discord.
</Warning>

The `Logger` class allows you to inspect the internal state of BAML function calls, including raw HTTP requests, responses, usage metrics, and timing information.

## Quick Start

<Tabs>
<Tab title="Python">
```python
from baml_client import b

# Create a logger
logger = b.create_logger()

# Use it with a function call
result = b.ExtractResume("...", baml_options={"logger": logger})

# Access logging information
print(logger.last.usage) # Print usage metrics
print(logger.last.raw_response[-1]) # Print final response
```
</Tab>

<Tab title="TypeScript">
```typescript
import {b} from 'baml_client'

// Create a logger
const logger = b.createLogger()

// Use it with a function call
const result = await b.ExtractResume("...", { logger })

// Access logging information
console.log(logger.last?.usage) # Print usage metrics
console.log(logger.last?.raw_response.at(-1)) # Print final response
```
</Tab>

<Tab title="Ruby">
```ruby
require_relative "baml_client/client"

# Create a logger
logger = Baml.Client.create_internal_logger

# Use it with a function call
res = Baml.Client.extract_resume(input: '...', baml_options: { logger: logger })

# Access logging information
print(logger.last.usage) # Print usage metrics
print(logger.last.raw_response.at(-1)) # Print final response
```
</Tab>
</Tabs>

## Common Use Cases

### Basic Logging

<Tabs>
<Tab title="Python">
```python
from baml_client import b

def run():
logger = b.create_logger()
# logger will be modified by the function to include all internal state
res = b.ExtractResume("...", baml_options={"logger": logger})
# This will print the return type of the function
print(res)

# This is guaranteed to be set by the function
assert logger.last is not None

# This will print the id of the last request
print(logger.last.id)

# This will print the usage of the last request
# (due to fallbacks and retries, others may also exist)
print(logger.last.usage)

# This will print the raw request of the last request
print(logger.last.raw_request)

# This will print the raw response of the last request
print(logger.last.raw_response[-1])

# This will print the raw text we used to run the parser.
print(logger.last.pre_parser)
```
</Tab>

<Tab title="TypeScript">
```typescript
import {b} from 'baml_client'

async function run() {
const logger = b.createLogger()
// logger will be modified by the function to include all internal state
const res = await b.ExtractResume("...", { logger })
// This will print the return type of the function
console.log(res)

// This is guaranteed to be set by the function
assert(logger.last)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TypeScript example uses assert(logger.last), which is not a valid TypeScript assertion. Consider using a proper check or TypeScript assertion instead.


// This will print the id of the last request
console.log(logger.last.id)

// This will print the usage of the last request
// (due to fallbacks and retries, others may also exist)
console.log(logger.last.usage)

// This will print the raw request of the last request
console.log(logger.last.raw_request)

// This will print the raw response of the last request
console.log(logger.last.raw_response.at(-1))

// This will print the raw text we used to run the parser.
console.log(logger.last.pre_parser)
}
```
</Tab>

<Tab title="Ruby">
```ruby
require_relative "baml_client/client"

def run
logger = Baml.Client.create_internal_logger
# ExtractResume will now use MyAmazingClient as the calling client
res = Baml.Client.extract_resume(input: '...', baml_options: { logger: logger })

# This will print the return type of the function
print(res)

# This is guaranteed to be set by the function
raise "Assertion failed" unless logger.last

# This will print the id of the last request
print(logger.last.id)

# This will print the usage of the last request
# (due to fallbacks and retries, others may also exist)
print(logger.last.usage)

# This will print the raw request of the last request
print(logger.last.raw_request)

# This will print the raw response of the last request
print(logger.last.raw_response.at(-1))

# This will print the raw text we used to run the parser.
print(logger.last.pre_parser)
end

# Call the function
run
```
</Tab>
</Tabs>

### Working with Multiple Functions

The Logger works seamlessly with BAML's [`id`](./id) functionality and [`with_options`](./with_options) for tracking multiple function calls. This is particularly useful when running functions in parallel.

<Tabs>
<Tab title="Python">
```python
from baml_client import b

def run():
logger = b.create_logger()
# You can use `b.id` to include the id along with the return value
first_id, resume = b.id.ExtractResume("...", baml_options={"logger": logger})
second_id, invoice = b.id.ExtractInvoice("...", baml_options={"logger": logger})

# List of all requests
print(logger.logs)

# The first request usage is available
print(logger.id(first_id).usage)

# The second request usage is also available
print(logger.id(second_id).usage)

# The total usage of all requests
print(logger.usage)
```
</Tab>

<Tab title="TypeScript">
```typescript
import {b} from 'baml_client'

async function run() {
const logger = b.createLogger()
const [{ id: first_id, data: resume }, { id: second_id, data: invoice }] = await Promise.all([
b.id.ExtractResume("...", { logger }),
b.id.ExtractInvoice("...", { logger })
])

// List of all requests
console.log(logger.logs)

// The first request usage is available
console.log(logger.id(first_id)?.usage)

// The second request usage is also available
console.log(logger.id(second_id)?.usage)

// The total usage of all requests
console.log(logger.usage)
}
```
</Tab>

<Tab title="Ruby">
```ruby
require_relative "baml_client/client"

def run
logger = Baml.Client.create_internal_logger
first_id, resume = Baml.Client.id.extract_resume(input: '...', baml_options: { logger: logger })
second_id, invoice = Baml.Client.id.extract_invoice(input: '...', baml_options: { logger: logger })

# List of all requests
print(logger.logs)

# The first request usage is available
print(logger.id(first_id).usage)

# The second request usage is also available
print(logger.id(second_id).usage)

# The total usage of all requests
print(logger.usage)
end
```
</Tab>
</Tabs>

### Managing Logger State

<Tabs>
<Tab title="Python">
```python
from baml_client import b

def run():
logger = b.create_logger()
res = b.ExtractResume("...", baml_options={"logger": logger})
# Remove all logs and free up memory
logger.clear()

# Reuse the same logger
res = b.ExtractInvoice("...", baml_options={"logger": logger})
```
</Tab>

<Tab title="TypeScript">
```typescript
import {b} from 'baml_client'

async function run() {
const logger = b.createLogger()
const res = await b.ExtractResume("...", { logger })
// Remove all logs and free up memory
logger.clear()

// Reuse the same logger
const res = await b.ExtractInvoice("...", { logger })
}
```
</Tab>

<Tab title="Ruby">
```ruby
require_relative "baml_client/client"

def run
logger = Baml.Client.create_internal_logger
res = Baml.Client.extract_resume(input: '...', baml_options: { logger: logger })
# Remove all logs and free up memory
logger.clear()

# Reuse the same logger
res = Baml.Client.extract_invoice(input: '...', baml_options: { logger: logger })
end
```
</Tab>
</Tabs>

### Usage Tracking

<Tabs>
<Tab title="Python">
```python
from baml_client import b

def run():
logger_a = b.create_logger()
res = b.ExtractResume("...", baml_options={"logger": logger_a})

logger_b = b.create_logger()
res = b.ExtractInvoice("...", baml_options={"logger": logger_b})

# Merge the usage of both logs
logger_a.merge(logger_b)

# The total usage of both logs is now available
print(logger_a.usage)
```
</Tab>

<Tab title="TypeScript">
```typescript
import {b} from 'baml_client'

async function run() {
const logger_a = b.createLogger()
const res = await b.ExtractResume("...", { logger: logger_a })

const logger_b = b.createLogger()
const res = await b.ExtractInvoice("...", { logger: logger_b })

// Merge the usage of both logs
logger_a.merge(logger_b)

// The total usage of both logs is now available
console.log(logger_a.usage)
}
```
</Tab>

<Tab title="Ruby">
```ruby
require_relative "baml_client/client"

def run
logger_a = Baml.Client.create_internal_logger
res = Baml.Client.extract_resume(input: '...', baml_options: { logger: logger_a })

logger_b = Baml.Client.create_internal_logger
res = Baml.Client.extract_invoice(input: '...', baml_options: { logger: logger_b })

# Merge the usage of both logs
logger_a.merge(logger_b)

# The total usage of both logs is now available
print(logger_a.usage)
end
```
</Tab>
</Tabs>

## API Reference

### Logger Class

Logger provides properties to introspect the internal state of a BAML function.

| Property | Type | Description |
|--------|------|-------------|
| `logs` | `List[FunctionLog]` | A list of all function calls (ordered from oldest to newest) |
| `last` | `FunctionLog \| null` | The most recent function log. |
| `usage` | `Usage` | The total usage of all requests. |


Logger provides the following methods:

| Method | Type | Description |
|--------|------|-------------|
| `merge(other: Logger)` | `void` | Merge the usage of two logs into caller. |
| `id(id: string)` | `FunctionLog \| null` | Get the function log by id. |
| `clear()` | `void` | Clears all logs. |

### FunctionLog Class

The `FunctionLog` class has the following properties:

| Property | Type | Description |
|----------|------|-------------|
| `id` | `string` | The id of the request. |
| `function_name` | `string` | The name of the function. |
| `type` | `"call" \| "stream"` | The manner in which the function was called. |
| `timing` | `Timing` | The timing of the request. |
| `usage` | `Usage` | The usage of the request (aggregated from all calls). |
| `calls` | `(LLMCall \| LLMStreamCall)[]` | Every call made to the LLM (including fallbacks and retries). Sorted from oldest to newest. |
| `raw_llm_response` | `string \| null` | The raw text from the best matching LLM. |
| `metadata` | `Map[str, any]` | Any user provided metadata. |

The `FunctionLog` class has the following methods:

| Method | Type | Description |
|--------|------|-------------|
| `selected_call()` | `LLMCall \| LLMStreamCall \| null` | The selected call that was used for parsing. |

### Timing Class

The `Timing` class has the following properties:

| Property | Type | Description |
|----------|------|-------------|
| `start_time_utc_ms` | `int` | The start time of the request. |
| `duration_ms` | `int` | The duration of the request. |
| `time_to_first_parsed_ms` | `int` | The time to first parsed response. |

#### Timing > StreamTiming

| Property | Type | Description |
|----------|------|-------------|
| `time_to_first_token_ms` | `int` | The time to first token. |

### Usage Class

The `Usage` class has the following properties:

| Property | Type | Description |
|----------|------|-------------|
| `input_tokens` | `int` | The number of tokens used in the input. |
| `output_tokens` | `int` | The number of tokens used in the output. |

<Info>
Note: Usage may not include all things like "thinking_tokens" or "cached_tokens". For that you may need to look at the raw HTTP response.
</Info>

### LLMCall Class

The `LLMCall` class has the following properties:

| Property | Type | Description |
|----------|------|-------------|
| `client_name` | `str` | The name of the client used. |
| `provider` | `str` | The provider of the client used. |
| `timing` | `Timing` | The timing of the request. |
| `request` | `json` | The raw request (often http) sent to the client. |
| `response` | `json \| null` | The raw response (often http) from the client. |
| `usage` | `Usage \| null` | The usage of the request (if available). |
| `selected` | `bool` | Whether this call was selected and used for parsing. |


#### LLMCall > LLMStreamCall

The `LLMStreamCall` class additionally has the following properties:

| Property | Type | Description |
|----------|------|-------------|
| `chunks` | `json[]` | The raw chunks of each stream part (often http) from the client. |
| `timing` | `StreamTiming` | The timing of the request. |

## Related Topics
- [Using with_options](./with_options) - Learn how to configure logging globally
- [Function IDs](./id) - Track specific function calls
- [TypeBuilder](./typebuilder) - Build custom types for your BAML functions
- [Client Registry](./client-registry) - Manage LLM clients and their configurations

## Best Practices
1. Use a single logger instance when tracking related function calls in a chain.
2. Clear the logger when reusing it for unrelated operations
3. Consider using `with_options` for consistent logging across multiple function calls
4. Use function IDs when tracking specific calls in parallel operations
91 changes: 91 additions & 0 deletions fern/03-reference/baml_client/proposal.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
---
title: Client Features Proposal
---

# BAML Client Features Proposal

This document outlines proposed enhancements to the BAML client library that aim to improve observability, debugging, and configuration management. These features are currently in the proposal stage and may evolve based on community feedback.

## Overview

We're proposing three major features to enhance the BAML client experience:

1. **Function IDs** - Unique identifiers for tracking individual function calls
2. **Enhanced Logging** - Comprehensive logging system for debugging and monitoring
3. **Client Configuration** - Flexible configuration management through `with_options`

## Motivation

### Current Challenges

1. **Limited Observability**: Currently, tracking individual function calls, especially in parallel operations or streaming scenarios, is difficult.
2. **Debugging Complexity**: Without detailed logs of LLM interactions, debugging issues is time-consuming.
3. **Configuration Management**: Setting up consistent configurations across multiple function calls requires boilerplate code.

### Goals

1. **Improved Traceability**: Enable tracking of individual function calls through unique IDs
2. **Better Debugging**: Provide comprehensive logging of all LLM interactions
3. **Simplified Configuration**: Allow setting default options that apply across multiple calls
4. **Performance Monitoring**: Track usage metrics and timing information

## Proposed Features

### 1. Function IDs (`id`)

Function IDs provide a way to uniquely identify and track individual function calls:

- Unique identifier for each function call
- Works with both regular and streaming calls
- Integrates with logging system
- Essential for parallel operations

### 2. Enhanced Logging (`logger`)

A comprehensive logging system that captures:

- Raw requests and responses
- Usage metrics (tokens, costs)
- Timing information
- Streaming chunks
- Fallback and retry attempts

### 3. Client Configuration (`with_options`)

A flexible configuration system that allows:

- Setting default options for multiple calls
- Managing client registries
- Configuring logging
- Setting up type builders
- Per-call option overrides

## Implementation Status

These features are currently in proposal stage and not yet available in any language. We're seeking community feedback on:

1. API design and ergonomics
2. Additional features or requirements
3. Language-specific considerations
4. Performance implications

## Next Steps

1. Gather community feedback
2. Finalize API design
3. Implement in supported languages
4. Create comprehensive documentation
5. Release beta version for testing

## Related Topics

- [Client Registry](./client-registry)
- [TypeBuilder](./typebuilder)

## Feedback

Please join our Discord to provide feedback on these proposed features. Your input will help shape the final implementation.

<Info>
These features are designed to work together seamlessly while remaining optional. You can adopt them incrementally as needed.
</Info>
314 changes: 314 additions & 0 deletions fern/03-reference/baml_client/with_options.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,314 @@
---
title: with_options
---

<Warning>
`with_options` is in proposal stage and may change. It is not yet available in any language. Please leave feedback on our discord.
</Warning>

The `with_options` function creates a new client with default configuration options for logging, client registry, and type builders. These options are automatically applied to all function calls made through this client, but can be overridden on a per-call basis when needed.

## Quick Start

<Tabs>
<Tab title="Python">
```python
from baml_client import b
from baml_py import ClientRegistry

# Set up default options for this client
logger = b.create_logger()
client_registry = ClientRegistry()
client_registry.set_primary("openai/gpt-4o-mini")

# Create client with default options
my_b = b.with_options(internal_logger=logger, client_registry=client_registry)

# Uses the default options
result = my_b.ExtractResume("...")

# Override options for a specific call
other_logger = b.create_logger()
result2 = my_b.ExtractResume("...", baml_options={"logger": other_logger})
```
</Tab>

<Tab title="TypeScript">
```typescript
import { b } from "baml_client"
import { ClientRegistry } from "baml_client/client_registry"

// Set up default options for this client
const logger = b.createLogger()
const clientRegistry = new ClientRegistry()
clientRegistry.setPrimary("openai/gpt-4o-mini")

// Create client with default options
const myB = b.withOptions({ logger, clientRegistry })

// Uses the default options
const result = await myB.ExtractResume("...")

// Override options for a specific call
const otherLogger = b.createLogger()
const result2 = await myB.ExtractResume("...", { logger: otherLogger })
```
</Tab>

<Tab title="Ruby">
```ruby
require 'baml_client'

# Set up default options for this client
logger = Baml.Client.create_internal_logger
client_registry = Baml::ClientRegistry.new
client_registry.set_primary("openai/gpt-4o-mini")

# Create client with default options
my_b = Baml.Client.with_options(internal_logger: logger, client_registry: client_registry)

# Uses the default options
result = my_b.ExtractResume("...")

# Override options for a specific call
other_logger = Baml.Client.create_internal_logger
result2 = my_b.ExtractResume("...", baml_options: { logger: other_logger })
```
</Tab>
</Tabs>

## Common Use Cases

### Basic Configuration

Use `with_options` to create a client with default settings that will be applied to all function calls made through this client. These defaults can be overridden when needed.

<Tabs>
<Tab title="Python">
```python
from baml_client import b
from baml_py import ClientRegistry

def run():
# Configure options
logger = b.create_internal_logger()
client_registry = ClientRegistry()
client_registry.set_primary("openai/gpt-4o-mini")

# Create configured client
my_b = b.with_options(internal_logger=logger, client_registry=client_registry)

# All calls will use the configured options
res = my_b.ExtractResume("...")
invoice = my_b.ExtractInvoice("...")

# Access configuration
print(my_b.client_registry)
# Will print 2 logs from the logger
print(my_b.internal_logger.logs)
```
</Tab>

<Tab title="TypeScript">
```typescript
import { b } from "baml_client"
import { ClientRegistry } from "baml_client/client_registry"

const logger = b.createLogger()
const clientRegistry = new ClientRegistry()
clientRegistry.setPrimary("openai/gpt-4o-mini")

const myB = b.withOptions({ logger, clientRegistry })

// All calls will use the configured options
const res = await myB.ExtractResume("...")
const invoice = await myB.ExtractInvoice("...")

// Access configuration
console.log(myB.clientRegistry)
console.log(myB.logger.last?.usage)
```
</Tab>

<Tab title="Ruby">
```ruby
require 'baml_client'

logger = Baml.Client.create_internal_logger
client_registry = Baml::ClientRegistry.new
client_registry.set_primary("openai/gpt-4o-mini")

my_b = Baml.Client.with_options(internal_logger: logger, client_registry: client_registry)

# All calls will use the configured options
res = my_b.ExtractResume("...")
invoice = my_b.ExtractInvoice("...")

# Access configuration
print(my_b.client_registry)
print(my_b.internal_logger.last.usage)
```
</Tab>
</Tabs>

### Parallel Execution

When running functions in parallel, `with_options` helps maintain consistent configuration across all calls. This works seamlessly with the [`Logger`](./logger) and [`id`](./id) functionality.

<Tabs>
<Tab title="Python">
```python
from baml_client.async_client import b

async def run():
my_b = b.with_options(client_registry=client_registry)

# Run multiple functions in parallel
res, invoice = await asyncio.gather(
my_b.ExtractResume("..."),
my_b.ExtractInvoice("...")
)

# Access results and logs
print(res)
print(invoice)
print(my_b.internal_logger.id(res.id).usage)
print(my_b.internal_logger.id(invoice.id).usage)
```
</Tab>

<Tab title="TypeScript">
```typescript
const myB = b.withOptions({ logger, clientRegistry })

// Run multiple functions in parallel
const [
{data: res, id: resumeId},
{data: invoice, id: invoiceId}
] = await Promise.all([
myB.raw.ExtractResume("..."),
myB.raw.ExtractInvoice("...")
])

// Access results and logs
console.log(res)
console.log(invoice)
console.log(myB.logger.id(resumeId)?.usage)
console.log(myB.logger.id(invoiceId)?.usage)
```
</Tab>

<Tab title="Ruby">
```ruby
require 'baml_client'
require 'async'

Async do
my_b = Baml.Client.with_options(client_registry: client_registry)

# Run multiple functions in parallel
res, invoice = await [
my_b.ExtractResume("..."),
my_b.ExtractInvoice("...")
]

# Access results and logs
print(res)
print(invoice)
print(my_b.internal_logger.id(res.id).usage)
print(my_b.internal_logger.id(invoice.id).usage)
end
```
</Tab>
</Tabs>

### Streaming Mode

`with_options` can be used with streaming functions while maintaining all configured options.

<Tabs>
<Tab title="Python">
```python
from baml_client.async_client import b

async def run():
my_b = b.with_options(client_registry=client_registry)

stream = my_b.stream.ExtractResume("...")
async for chunk in stream:
print(chunk)

result = await stream.get_final_result()
print(my_b.internal_logger.id(stream.id).usage)
```
</Tab>

<Tab title="TypeScript">
```typescript
const myB = b.withOptions({ logger, clientRegistry })

const stream = myB.stream.ExtractResume("...")
for await (const chunk of stream) {
console.log(chunk)
}

const result = await stream.getFinalResult()
console.log(myB.logger.id(stream.id)?.usage)
```
</Tab>

<Tab title="Ruby">
```ruby
require 'baml_client'

my_b = Baml.Client.with_options(client_registry: client_registry)

stream = my_b.stream.ExtractResume("...")
stream.each do |chunk|
print(chunk)
end

result = stream.get_final_result
print(my_b.internal_logger.id(stream.id).usage)
```
</Tab>
</Tabs>

## API Reference

### with_options Parameters

<Note>
These can always be overridden on a per-call basis with the `baml_options` parameter in any function call.
</Note>

| Parameter | Type | Description |
|-----------|------|-------------|
| `logger` / `internal_logger` | [`Logger`](./logger) | Logger instance for tracking function calls and usage |
| `client_registry` | `ClientRegistry` | Registry for managing LLM clients and their configurations |
| `type_builder` | [`TypeBuilder`](./typebuilder) | Custom type builder for function inputs and outputs |

### Configured Client Properties

<Info>
The configured client maintains the same interface as the base `baml_client`, so you can use all the same functions and methods.
</Info>


## Related Topics
- [Logger](./logger) - Track function calls and usage metrics
- [Function IDs](./id) - Track specific function calls
- [TypeBuilder](./typebuilder) - Build custom types for your functions
- [Client Registry](./client-registry) - Manage LLM clients and their configurations

## Best Practices
1. Use `with_options` when you need consistent configuration across multiple function calls
2. Configure logging and client registry at the start of your application
3. Create separate configured clients for different parts of your application that need different settings
4. Use the configured client's properties to access logs and metrics

<Info>
The configured client maintains the same interface as the base client, so you can use all the same functions and methods.
</Info>


14 changes: 11 additions & 3 deletions fern/docs.yml
Original file line number Diff line number Diff line change
@@ -415,11 +415,19 @@ navigation:
path: 03-reference/baml/clients/strategy/round-robin.mdx
- section: baml_client
contents:
- page: TypeBuilder
- page: id
path: 03-reference/baml_client/id.mdx
- page: with_options
path: 03-reference/baml_client/with_options.mdx
- page: baml_options > Logger
path: 03-reference/baml_client/logger.mdx
slug: logger
- page: baml_options > TypeBuilder
path: 03-reference/baml_client/typebuilder.mdx
- page: ClientRegistry
slug: typebuilder
- page: baml_options > ClientRegistry
path: 01-guide/05-baml-advanced/client-registry.mdx

slug: client-registry
- section: Prompt Syntax
contents:
- page: What is jinja?