-
Notifications
You must be signed in to change notification settings - Fork 110
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
hellovai
wants to merge
1
commit into
canary
Choose a base branch
from
usage-proposal
base: canary
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+1,093
−3
Draft
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
|
||
// 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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.