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 support for a sync client for Python + Typescript #803

Merged
merged 2 commits into from
Jul 19, 2024
Merged
Show file tree
Hide file tree
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
29 changes: 23 additions & 6 deletions docs/docs/calling-baml/generate-baml-client.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,15 @@ generate code for each of them.
generator target {
// Valid values: "python/pydantic", "typescript", "ruby/sorbet"
output_type "python/pydantic"

// Where the generated code will be saved (relative to baml_src/)
output_dir "../"

// What interface you prefer to use for the generated code (sync/async)
// Both are generated regardless of the choice, just modifies what is exported
// at the top level
default_client_mode "sync"

// Version of runtime to generate code for (should match installed baml-py version)
version "0.50.0"
}
Expand All @@ -86,8 +93,15 @@ generator target {
generator target {
// Valid values: "python/pydantic", "typescript", "ruby/sorbet"
output_type "typescript"

// Where the generated code will be saved (relative to baml_src/)
output_dir "../"

// What interface you prefer to use for the generated code (sync/async)
// Both are generated regardless of the choice, just modifies what is exported
// at the top level
default_client_mode "async"

// Version of runtime to generate code for (should match the package @boundaryml/baml version)
version "0.50.0"
}
Expand All @@ -97,8 +111,13 @@ generator target {
generator target {
// Valid values: "python/pydantic", "typescript", "ruby/sorbet"
output_type "ruby/sorbet"

// Where the generated code will be saved (relative to baml_src/)
output_dir "../"

// This is a no-op for Ruby and can be ignored as futures are colorless in Ruby
// default_client_mode "sync"

// Version of runtime to generate code for (should match installed `baml` package version)
version "0.50.0"
}
Expand Down Expand Up @@ -154,7 +173,7 @@ BAML: Code Generation disabled due to version mismatch... See the fix.
**To fix any version mismatch issues, update all these things to the same version:**
1. VSCode extension
2. the installed baml dependency (e.g., `baml-py`)
- python: `pip install --upgrade package_name`
- python: `pip install --upgrade baml-py`
- typescript: `npm install @boundaryml/baml@latest`
3. the `version` in your `generator` clause

Expand All @@ -173,14 +192,12 @@ You can also tweak the generator settings below for a better experience.
<ParamField
path="--from"
type="string"
default="null">

default="./baml_src">
Path to the BAML source directory. This is where the BAML files are located.
</ParamField>

<ParamField
path="--no-version-check"
type="string"
default="false">
type="flag">
If set, it will disable checking the BAML source version with the installed BAML package version before generating code.
</ParamField>
</ParamField>
25 changes: 24 additions & 1 deletion docs/docs/get-started/quickstart/python.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,30 @@ To set up BAML in python do the following:
### Use a baml function in python!
<Tip>If `baml_client` doesn't exist, make sure to run the previous step!</Tip>

<CodeBlocks>
```python main.py
from baml_client import b
from baml_client.sync_client import b
from baml_client.types import Resume

def example(raw_resume: str) -> Resume:
# BAML's internal parser guarantees ExtractResume
# to be always return a Resume type
response = b.ExtractResume(raw_resume)
return response

def example_stream(raw_resume: str) -> Resume:
stream = b.stream.ExtractResume(raw_resume)
for msg in stream:
print(msg) # This will be a PartialResume type

# This will be a Resume type
final = stream.get_final_response()

return final
```

```python async_main.py
from baml_client.async_client import b
from baml_client.types import Resume

async def example(raw_resume: str) -> Resume:
Expand All @@ -73,5 +95,6 @@ To set up BAML in python do the following:

return final
```
</CodeBlocks>

</Steps>
1 change: 1 addition & 0 deletions engine/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 43 additions & 0 deletions engine/baml-lib/baml-core/src/configuration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,41 @@ pub enum GeneratorOutputType {
RubySorbet,
}

impl GeneratorOutputType {
pub fn default_client_mode(&self) -> GeneratorDefaultClientMode {
match self {
// Due to legacy reasons, PythonPydantic and Typescript default to async
// DO NOT CHANGE THIS DEFAULT EVER OR YOU WILL BREAK EXISTING USERS
Self::PythonPydantic => GeneratorDefaultClientMode::Async,
Self::Typescript => GeneratorDefaultClientMode::Async,
Self::RubySorbet => GeneratorDefaultClientMode::Sync,
}
}

/// Used to new generators when they are created (e.g. during baml-cli init)
pub fn recommended_default_client_mode(&self) -> GeneratorDefaultClientMode {
match self {
Self::PythonPydantic => GeneratorDefaultClientMode::Sync,
Self::Typescript => GeneratorDefaultClientMode::Async,
Self::RubySorbet => GeneratorDefaultClientMode::Sync,
}
}
}

#[derive(Debug, Clone, strum::Display, strum::EnumString, strum::VariantNames, PartialEq, Eq)]
pub enum GeneratorDefaultClientMode {
#[strum(serialize = "sync")]
Sync,
#[strum(serialize = "async")]
Async,
}

#[derive(derive_builder::Builder, Debug, Clone)]
pub struct Generator {
pub name: String,
pub baml_src: PathBuf,
pub output_type: GeneratorOutputType,
default_client_mode: Option<GeneratorDefaultClientMode>,
output_dir: PathBuf,
pub version: String,

Expand All @@ -55,6 +85,19 @@ impl Generator {
)
}

pub fn default_client_mode(&self) -> GeneratorDefaultClientMode {
self.default_client_mode
.clone()
.unwrap_or_else(|| self.output_type.default_client_mode())
}

/// Used to new generators when they are created
pub fn recommended_default_client_mode(&self) -> GeneratorDefaultClientMode {
self.default_client_mode
.clone()
.unwrap_or_else(|| self.output_type.recommended_default_client_mode())
}

pub fn output_dir(&self) -> PathBuf {
self.output_dir.join("baml_client")
}
Expand Down
5 changes: 3 additions & 2 deletions engine/baml-lib/baml-core/src/validate/generator_loader/v0.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,8 +208,9 @@ pub(crate) fn parse_generator(
};

builder.build().map_err(|e| {
vec![DatamodelError::new_internal_error(
anyhow::Error::from(e).context("Internal error while parsing generator (v1 syntax)"),
vec![DatamodelError::new_anyhow_error(
anyhow::Error::from(e)
.context("Internal error while parsing generator (legacy v0 syntax)"),
ast_generator.span().clone(),
)]
})
Expand Down
5 changes: 3 additions & 2 deletions engine/baml-lib/baml-core/src/validate/generator_loader/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,9 @@ pub(crate) fn parse_generator(
}

builder.build().map_err(|e| {
vec![DatamodelError::new_internal_error(
anyhow::Error::from(e).context("Internal error while parsing generator (v1 syntax)"),
vec![DatamodelError::new_anyhow_error(
anyhow::Error::from(e)
.context("Internal error while parsing generator (legacy v1 syntax)"),
ast_generator.span().clone(),
)]
})
Expand Down
32 changes: 29 additions & 3 deletions engine/baml-lib/baml-core/src/validate/generator_loader/v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ use internal_baml_schema_ast::ast::{self, WithName, WithSpan};
use semver::Version;
use strum::VariantNames;

use crate::configuration::{Generator, GeneratorBuilder, GeneratorOutputType};
use crate::configuration::{
Generator, GeneratorBuilder, GeneratorDefaultClientMode, GeneratorOutputType,
};

const FIRST_CLASS_PROPERTIES: &[&str] = &["output_type", "output_dir", "version"];

Expand Down Expand Up @@ -158,13 +160,37 @@ pub(crate) fn parse_generator(
}
}

match parse_optional_key(&args, "default_client_mode") {
Ok(Some("sync")) => {
builder.default_client_mode(Some(GeneratorDefaultClientMode::Sync));
}
Ok(Some("async")) => {
builder.default_client_mode(Some(GeneratorDefaultClientMode::Async));
}
Ok(Some(name)) => {
errors.push(DatamodelError::new_validation_error(
&format!("'{}' is not supported. Use one of: 'async' or 'sync'", name),
args.get("default_client_mode")
.map(|arg| arg.span())
.unwrap_or_else(|| ast_generator.span())
.clone(),
));
}
Ok(None) => {
builder.default_client_mode(None);
}
Err(err) => {
errors.push(err);
}
}

if !errors.is_empty() {
return Err(errors);
}

builder.build().map_err(|e| {
vec![DatamodelError::new_internal_error(
anyhow::Error::from(e).context("Internal error while parsing generator (v1 syntax)"),
vec![DatamodelError::new_anyhow_error(
anyhow::Error::from(e).context("Error parsing generator"),
ast_generator.span().clone(),
)]
})
Expand Down
4 changes: 2 additions & 2 deletions engine/baml-lib/diagnostics/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ impl DatamodelError {
DatamodelError { message, span }
}

pub fn new_internal_error(error: anyhow::Error, span: Span) -> Self {
Self::new(format!("Internal error occurred: {error}"), span)
pub fn new_anyhow_error(error: anyhow::Error, span: Span) -> Self {
Self::new(format!("{error:#}"), span)
}

pub fn new_static(message: &'static str, span: Span) -> Self {
Expand Down
1 change: 1 addition & 0 deletions engine/baml-runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ enum_dispatch = "0.3.13"
ambassador = "0.4.0"
aws-smithy-json = "0.60.7"
jsonwebtoken = "9.3.0"
pretty_assertions = "1.4.0"


[target.'cfg(target_arch = "wasm32")'.dependencies]
Expand Down
20 changes: 18 additions & 2 deletions engine/baml-runtime/src/cli/generate.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::{runtime::runtime_interface::baml_src_files, BamlRuntime};
use anyhow::{Context, Result};
use anyhow::Result;
use colored::*;
use std::{env, path::PathBuf};
use internal_baml_core::configuration::GeneratorDefaultClientMode;
use std::path::PathBuf;

#[derive(clap::Args, Debug)]
pub struct GenerateArgs {
Expand Down Expand Up @@ -40,6 +41,20 @@ impl GenerateArgs {
// give the user a working config to copy-paste (so we need to run it through generator again)
if generated.is_empty() {
let client_type = caller_type.into();

let default_client_mode = match client_type {
internal_baml_core::configuration::GeneratorOutputType::PythonPydantic => {
// TODO: Consider changing this default to sync
GeneratorDefaultClientMode::Async
}
internal_baml_core::configuration::GeneratorOutputType::Typescript => {
GeneratorDefaultClientMode::Async
}
internal_baml_core::configuration::GeneratorOutputType::RubySorbet => {
GeneratorDefaultClientMode::Sync
}
};
// Normally `baml_client` is added via the generator, but since we're not running the generator, we need to add it manually.
let output_dir_relative_to_baml_src = PathBuf::from("..");
let version = env!("CARGO_PKG_VERSION");
let generate_output = runtime.generate_client(
Expand All @@ -50,6 +65,7 @@ impl GenerateArgs {
all_files.iter(),
version.to_string(),
false,
default_client_mode,
)?,
)?;

Expand Down
Loading
Loading