Skip to content

Commit

Permalink
Improved 'o1' model detection in OpenAI client and updated documentat…
Browse files Browse the repository at this point in the history
…ion for error handling and client setup (#1290)

Improved 'o1' model detection in OpenAI client and updated documentation
for error handling and client setup.

- **Behavior**:
- Improved detection of 'o1' models in `is_o1_model()` in `openai.rs` to
include exact 'o1' matches.
- **Documentation**:
- Added `BamlClientFinishReasonError` handling in `error-handling.mdx`.
- Updated various provider documentation files (e.g., `anthropic.mdx`,
`aws-bedrock.mdx`) to include `finish-reason.mdx` snippet.
  - Added `enablePlaygroundProxy.mdx` for VSCode extension settings.
  - Fixed `ClientRegistry` docs
  • Loading branch information
hellovai authored Jan 3, 2025
1 parent 6cb5009 commit 479d06e
Show file tree
Hide file tree
Showing 16 changed files with 178 additions and 91 deletions.
26 changes: 0 additions & 26 deletions engine/baml-lib/baml-core/src/ir/walker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,32 +15,6 @@ use super::{
};
use crate::ir::jinja_helpers::render_expression;

fn provider_to_env_vars(
provider: &str,
) -> impl IntoIterator<Item = (Option<&'static str>, &'static str)> {
match provider {
"aws-bedrock" => vec![
(None, "AWS_ACCESS_KEY_ID"),
(None, "AWS_SECRET_ACCESS_KEY"),
(Some("region"), "AWS_REGION"),
],
"openai" => vec![(Some("api_key"), "OPENAI_API_KEY")],
"anthropic" => vec![(Some("api_key"), "ANTHROPIC_API_KEY")],
"google-ai" => vec![(Some("api_key"), "GOOGLE_API_KEY")],
"vertex-ai" => vec![
(Some("credentials"), "GOOGLE_APPLICATION_CREDENTIALS"),
(
Some("credentials_content"),
"GOOGLE_APPLICATION_CREDENTIALS_CONTENT",
),
],
"azure-openai" => vec![(Some("api_key"), "AZURE_OPENAI_API_KEY")],
"openai-generic" => vec![(Some("api_key"), "OPENAI_API_KEY")],
"ollama" => vec![],
other => vec![],
}
}

impl<'a> Walker<'a, &'a FunctionNode> {
pub fn name(&self) -> &'a str {
self.elem().name()
Expand Down
27 changes: 21 additions & 6 deletions engine/baml-lib/llm-client/src/clients/openai.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use std::collections::HashSet;

use crate::{AllowedRoleMetadata, FinishReasonFilter, RolesSelection, SupportedRequestModes, UnresolvedAllowedRoleMetadata, UnresolvedFinishReasonFilter, UnresolvedRolesSelection};
use crate::{
AllowedRoleMetadata, FinishReasonFilter, RolesSelection, SupportedRequestModes,
UnresolvedAllowedRoleMetadata, UnresolvedFinishReasonFilter, UnresolvedRolesSelection,
};
use anyhow::Result;

use baml_types::{GetEnvVar, StringOr, UnresolvedValue};
Expand Down Expand Up @@ -64,7 +67,12 @@ pub struct ResolvedOpenAI {

impl ResolvedOpenAI {
fn is_o1_model(&self) -> bool {
self.properties.get("model").is_some_and(|model| model.as_str().map(|s| s.starts_with("o1-")).unwrap_or(false))
self.properties.get("model").is_some_and(|model| {
model
.as_str()
.map(|s| s.starts_with("o1-") || s.eq("o1"))
.unwrap_or(false)
})
}

pub fn supports_streaming(&self) -> bool {
Expand All @@ -79,7 +87,11 @@ impl ResolvedOpenAI {
if self.is_o1_model() {
vec!["user".to_string(), "assistant".to_string()]
} else {
vec!["system".to_string(), "user".to_string(), "assistant".to_string()]
vec![
"system".to_string(),
"user".to_string(),
"assistant".to_string(),
]
}
})
}
Expand All @@ -89,7 +101,6 @@ impl ResolvedOpenAI {
// TODO: guard against empty allowed_roles
// The compiler should already guarantee that this is non-empty
self.allowed_roles().remove(0)

})
}
}
Expand Down Expand Up @@ -277,7 +288,10 @@ impl<Meta: Clone> UnresolvedOpenAI<Meta> {

let mut instance = Self::create_common(properties, base_url, None)?;
instance.query_params = query_params;
instance.headers.entry("api-key".to_string()).or_insert(api_key);
instance
.headers
.entry("api-key".to_string())
.or_insert(api_key);

Ok(instance)
}
Expand All @@ -300,7 +314,8 @@ impl<Meta: Clone> UnresolvedOpenAI<Meta> {

let api_key = properties.ensure_api_key();

let mut instance = Self::create_common(properties, Some(either::Either::Left(base_url)), api_key)?;
let mut instance =
Self::create_common(properties, Some(either::Either::Left(base_url)), api_key)?;
// Ollama uses smaller models many of which prefer the user role
if instance.role_selection.default.is_none() {
instance.role_selection.default = Some(StringOr::Value("user".to_string()));
Expand Down
39 changes: 37 additions & 2 deletions fern/01-guide/04-baml-basics/error-handling.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ except BamlValidationError as e:
```typescript TypeScript
import { b } from './baml_client'
// For catching parsing errors, you can import this
import { BamlValidationError } from '@boundaryml/baml'
import { BamlValidationError, BamlClientFinishReasonError } from '@boundaryml/baml'
// The rest of the BAML errors contain a string that is prefixed with:
// "BamlError:"
// Subclasses are sequentially appended to the string.
Expand All @@ -42,7 +42,7 @@ async function example() {
try {
await b.CallFunctionThatRaisesError()
} catch (e) {
if (e instanceof BamlValidationError) {
if (e instanceof BamlValidationError || e instanceof BamlClientFinishReasonError) {
// You should be lenient to these fields missing.
// The original prompt sent to the LLM
console.log(e.prompt)
Expand Down Expand Up @@ -116,6 +116,41 @@ Common status codes are:
- 500: Internal Server Error
</ParamField>


#### BamlClientFinishReasonError

Subclass of `BamlClientError`.

Raised when the finish reason of the LLM response is not allowed.

<ParamField
path="finish_reason"
type="string"
>
The finish reason of the LLM response.
</ParamField>

<ParamField
path="message"
type="string"
>
An error message.
</ParamField>

<ParamField
path="prompt"
type="string"
>
The original prompt that was sent to the LLM, formatted as a plain string. Images sent as base64-encoded strings are not serialized into this field.
</ParamField>

<ParamField
path="raw_output"
type="string"
>
The raw text from the LLM that failed to parse into the expected return type of a function.
</ParamField>

### BamlValidationError

Subclass of `BamlError`.
Expand Down
10 changes: 5 additions & 5 deletions fern/01-guide/05-baml-advanced/client-registry.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ import { ClientRegistry } from '@boundaryml/baml'
async function run() {
const cr = new ClientRegistry()
// Creates a new client
cr.addLlmClient({ name: 'MyAmazingClient', provider: 'openai', options: {
cr.addLlmClient('MyAmazingClient', 'openai', {
model: "gpt-4o",
temperature: 0.7,
api_key: process.env.OPENAI_API_KEY
}})
})
// Sets MyAmazingClient as the primary client
cr.setPrimary('MyAmazingClient')

Expand All @@ -60,9 +60,9 @@ def run

# Creates a new client
cr.add_llm_client(
name: 'MyAmazingClient',
provider: 'openai',
options: {
'MyAmazingClient',
'openai',
{
model: 'gpt-4o',
temperature: 0.7,
api_key: ENV['OPENAI_API_KEY']
Expand Down
11 changes: 2 additions & 9 deletions fern/03-reference/baml/clients/providers/anthropic.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,6 @@ The options are passed through directly to the API, barring a few. Here's a shor
The base URL for the API. **Default: `https://api.anthropic.com`**
</ParamField>

<ParamField
path="default_role"
type="string"
>
The default role for any prompts that don't specify a role. **Default: `system`**

We don't have any checks for this field, you can pass any string you wish.
</ParamField>

<ParamField path="headers" type="object">
Additional headers to send with the request.

Expand Down Expand Up @@ -73,6 +64,8 @@ client<llm> MyClient {

<Markdown src="/snippets/supports-streaming.mdx" />

<Markdown src="/snippets/finish-reason.mdx" />

## Forwarded options
<ParamField
path="system"
Expand Down
2 changes: 2 additions & 0 deletions fern/03-reference/baml/clients/providers/aws-bedrock.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ Add these three environment variables to your extension variables to use the AWS
```
</ParamField>

<Markdown src="/snippets/finish-reason.mdx" />

## Forwarded options

<ParamField
Expand Down
10 changes: 1 addition & 9 deletions fern/03-reference/baml/clients/providers/azure.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,6 @@ The options are passed through directly to the API, barring a few. Here's a shor
See the `base_url` field.
</ParamField>

<ParamField
path="default_role"
type="string"
>
The default role for any prompts that don't specify a role. **Default: `system`**

We don't have any checks for this field, you can pass any string you wish.
</ParamField>

<ParamField path="api_version" type="string" required>
Will be passed via a query parameter `api-version`.
</ParamField>
Expand Down Expand Up @@ -101,6 +92,7 @@ client<llm> MyClient {

<Markdown src="/snippets/supports-streaming.mdx" />

<Markdown src="/snippets/finish-reason.mdx" />

## Forwarded options
<ParamField
Expand Down
11 changes: 2 additions & 9 deletions fern/03-reference/baml/clients/providers/google-ai.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,6 @@ The options are passed through directly to the API, barring a few. Here's a shor
The base URL for the API. **Default: `https://generativelanguage.googleapis.com/v1beta`**
</ParamField>

<ParamField
path="default_role"
type="string"
>
The default role for any prompts that don't specify a role. **Default: `user`**

We don't have any checks for this field, you can pass any string you wish.
</ParamField>

<ParamField
path="model"
type="string"
Expand Down Expand Up @@ -87,6 +78,8 @@ client<llm> MyClient {

<Markdown src="/snippets/supports-streaming.mdx" />

<Markdown src="/snippets/finish-reason.mdx" />

## Forwarded options
<ParamField
path="contents"
Expand Down
31 changes: 31 additions & 0 deletions fern/03-reference/baml/clients/providers/litellm.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
title: litellm
---

[LiteLLM](https://www.litellm.ai/) supports the OpenAI client, allowing you to use the
[`openai-generic`](/ref/llm-client-providers/openai-generic) provider with an
overridden `base_url`.


See [OpenAI Generic](/ref/llm-client-providers/openai-generic) for more details about parameters.


## Set up

1. Set up [LiteLLM Proxy server](https://docs.litellm.ai/docs/proxy/docker_quick_start#21-start-proxy)

2. Set up LiteLLM Client in BAML files

3. Use it in a BAML function!


```baml BAML
client<llm> MyClient {
provider "openai-generic"
options {
base_url "http://0.0.0.0:4000"
api_key env.LITELLM_API_KEY
model "gpt-4o"
}
}
```
9 changes: 0 additions & 9 deletions fern/03-reference/baml/clients/providers/ollama.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,6 @@ The options are passed through directly to the API, barring a few. Here's a shor
<Tip>Note the `/v1` at the end of the URL. See [Ollama's OpenAI compatability](https://ollama.com/blog/openai-compatibility)</Tip>
</ParamField>

<ParamField
path="default_role"
type="string"
>
The default role for any prompts that don't specify a role. **Default: `system`**

We don't have any checks for this field, you can pass any string you wish.
</ParamField>

<ParamField path="headers" type="object">
Additional headers to send with the request.

Expand Down
10 changes: 2 additions & 8 deletions fern/03-reference/baml/clients/providers/openai-generic.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,6 @@ client<llm> MyClient {
**Default: `https://api.openai.com/v1`**
</ParamField>

<ParamField path="default_role" type="string">
The default role for any prompts that don't specify a role.

We don't do any validation of this field, so you can pass any string you wish.

**Default: `system`**
</ParamField>

<ParamField path="api_key" type="string" default="<none>">
Will be used to build the `Authorization` header, like so: `Authorization: Bearer $api_key`
If `api_key` is not set, or is set to an empty string, the `Authorization` header will not be sent.
Expand Down Expand Up @@ -68,6 +60,8 @@ client<llm> MyClient {

<Markdown src="/snippets/supports-streaming.mdx" />

<Markdown src="/snippets/finish-reason.mdx" />

## Forwarded options

<ParamField
Expand Down
9 changes: 1 addition & 8 deletions fern/03-reference/baml/clients/providers/openai.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,6 @@ The options are passed through directly to the API, barring a few. Here's a shor
**Default: `https://api.openai.com/v1`**
</ParamField>

<ParamField path="default_role" type="string">
The default role for any prompts that don't specify a role.

We don't do any validation of this field, so you can pass any string you wish.

**Default: `system`**
</ParamField>

<ParamField path="headers" type="object">
Additional headers to send with the request.

Expand All @@ -76,6 +68,7 @@ client<llm> MyClient {

<Markdown src="/snippets/supports-streaming-openai.mdx" />

<Markdown src="/snippets/finish-reason.mdx" />

## Forwarded options

Expand Down
2 changes: 2 additions & 0 deletions fern/03-reference/baml/clients/providers/vertex.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ client<llm> MyClient {

<Markdown src="/snippets/supports-streaming.mdx" />

<Markdown src="/snippets/finish-reason.mdx" />

## Forwarded options
<ParamField
path="safetySettings"
Expand Down
19 changes: 19 additions & 0 deletions fern/03-reference/vscode-ext/enablePlaygroundProxy.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
| Type | Value |
| --- | --- |
| `boolean \| null` | true |


<Tip>
When running VSCode from a remote machine, you likely need to set this to `false`.
</Tip>

Many LLM providers don't accept requests from the browser. This setting enables a proxy that runs in the background and forwards requests to the LLM provider.

## Usage

```json settings.json
{
"baml.enablePlaygroundProxy": false
}
```

Loading

0 comments on commit 479d06e

Please sign in to comment.