From 01cf855500159066fdcd162dc2e2087768d5ba28 Mon Sep 17 00:00:00 2001 From: aaronvg Date: Thu, 12 Sep 2024 12:16:47 -0700 Subject: [PATCH 1/7] update nextjs hooks and docs (#952) --- docs/docs/baml-nextjs/baml-nextjs.mdx | 158 ++++++++++++++------------ 1 file changed, 84 insertions(+), 74 deletions(-) diff --git a/docs/docs/baml-nextjs/baml-nextjs.mdx b/docs/docs/baml-nextjs/baml-nextjs.mdx index 159148b62..424319f48 100644 --- a/docs/docs/baml-nextjs/baml-nextjs.mdx +++ b/docs/docs/baml-nextjs/baml-nextjs.mdx @@ -20,94 +20,95 @@ You will need to use Server Actions, from the App Router, for this tutorial. You - Install the VSCode extension and Save a baml file to generate the client (or use `npx baml-cli generate`). -### Create streamable baml server actions +### Create some helper utilities to stream BAML functions Let's add some helpers to export our baml functions as streamable server actions. See the last line in this file, where we export the `extractResume` function. -In `app/actions/streamable_objects.tsx` add the following code: +In `app/utils/streamableObject.tsx` add the following code: ```typescript -"use server"; -import { createStreamableValue, StreamableValue } from "ai/rsc"; -import { b, Resume } from "@/baml_client"; +import { createStreamableValue, StreamableValue as BaseStreamableValue } from "ai/rsc"; import { BamlStream } from "@boundaryml/baml"; -const MAX_ERROR_LENGTH = 3000; -const TRUNCATION_MARKER = "[ERROR_LOG_TRUNCATED]"; -function truncateError(error: string): string { - if (error.length <= MAX_ERROR_LENGTH) return error; - const halfLength = Math.floor( - (MAX_ERROR_LENGTH - TRUNCATION_MARKER.length) / 2 - ); - return ( - error.slice(0, halfLength) + TRUNCATION_MARKER + error.slice(-halfLength) - ); -} - -type BamlStreamReturnType = T extends BamlStream ? P : never; - -type StreamFunction = (...args: any[]) => BamlStream; +// ------------------------------ +// Helper functions +// ------------------------------ +/** + * Type alias for defining a StreamableValue based on a BamlStream. + * It captures either a partial or final result depending on the stream state. + */ +type StreamableValue> = + | { partial: T extends BamlStream ? StreamRet : never } + | { final: T extends BamlStream ? Ret : never }; -async function streamHelper( - streamFunction: (...args: any[]) => BamlStream, - ...args: Parameters +/** + * Helper function to manage and handle a BamlStream. + * It consumes the stream, updates the streamable value for each partial event, + * and finalizes the stream when complete. + * + * @param bamlStream - The BamlStream to be processed. + * @returns A promise that resolves with an object containing the BaseStreamableValue. + */ +export async function streamHelper>( + bamlStream: T, ): Promise<{ - object: StreamableValue>; + object: BaseStreamableValue>; }> { - const stream = createStreamableValue(); + const stream = createStreamableValue>(); + // Asynchronous function to process the BamlStream events (async () => { try { - const bamlStream = streamFunction(...args); + // Iterate through the stream and update the stream value with partial data for await (const event of bamlStream) { - console.log("event", event); - if (event) { - stream.update(event as T); - } + stream.update({ partial: event }); } + + // Obtain the final response once all events are processed const response = await bamlStream.getFinalResponse(); - stream.update(response as T); - stream.done(); + stream.done({ final: response }); } catch (err) { - const errorMsg = truncateError((err as Error).message); - console.log("error", errorMsg); - stream.error(errorMsg); + // Handle any errors during stream processing + stream.error(err); } })(); return { object: stream.value }; } -const streamableFunctions = { - extractResume: b.stream.ExtractResume, - extractUnstructuredResume: b.stream.ExtractResumeNoStructure, - analyzeBook: b.stream.AnalyzeBooks, - answerQuestion: b.stream.AnswerQuestion, - getRecipe: b.stream.GetRecipe, -} as const; - -type StreamableFunctionName = keyof typeof streamableFunctions; - -function createStreamableFunction( - functionName: T -): (...args: Parameters<(typeof streamableFunctions)[T]>) => Promise<{ - object: StreamableValue< - Partial>> - >; +/** + * Utility function to create a streamable function from a BamlStream-producing function. + * This function returns an asynchronous function that manages the streaming process. + * + * @param func - A function that produces a BamlStream when called. + * @returns An asynchronous function that returns a BaseStreamableValue for the stream. + */ +export function makeStreamable< + BamlStreamFunc extends (...args: any) => BamlStream, +>( + func: BamlStreamFunc +): (...args: Parameters) => Promise<{ + object: BaseStreamableValue>>; }> { - return async (...args) => - // need to bind to b.stream since we lose context here. - streamHelper( - streamableFunctions[functionName].bind(b.stream) as any, - ...args - ); + return async (...args) => { + const stream = func(...args); + return streamHelper(stream); + }; } -export const extractResume = createStreamableFunction("extractResume"); ``` +### Export your BAML functions to streamable server actions -### Create a hook to use the streamable functions +In `app/actions/extract.tsx` add the following code: +```typescript +import { makeStreamable } from "../_baml_utils/streamableObjects"; + + +export const extractResume = makeStreamable(b.stream.ExtractResume); +``` + +### Create a hook to use the streamable functions in React Components This hook will work like [react-query](https://react-query.tanstack.com/), but for BAML functions. It will give you partial data, the loading status, and whether the stream was completed. @@ -116,23 +117,28 @@ In `app/_hooks/useStream.ts` add: import { useState, useEffect } from "react"; import { readStreamableValue, StreamableValue } from "ai/rsc"; +/** + * A hook that streams data from a server action. The server action must return a StreamableValue. + * See the example actiimport { useState, useEffect } from "react"; +import { readStreamableValue, StreamableValue } from "ai/rsc"; + /** * A hook that streams data from a server action. The server action must return a StreamableValue. * See the example action in app/actions/streamable_objects.tsx * **/ -export function useStream( - serverAction: (...args: P) => Promise<{ object: StreamableValue, any> }> +export function useStream( + serverAction: (...args: P) => Promise<{ object: StreamableValue<{ partial: PartialRet } | { final: Ret }, any> }> ) { const [isLoading, setIsLoading] = useState(false); const [isComplete, setIsComplete] = useState(false); const [isError, setIsError] = useState(false); const [error, setError] = useState(null); - const [partialData, setPartialData] = useState | undefined>(undefined); // Initialize data state - const [data, setData] = useState(undefined); // full non-partial data + const [partialData, setPartialData] = useState(undefined); // Initialize data state + const [streamResult, setData] = useState(undefined); // full non-partial data const mutate = async ( ...params: Parameters - ): Promise => { + ): Promise => { console.log("mutate", params); setIsLoading(true); setIsError(false); @@ -142,7 +148,6 @@ export function useStream( const { object } = await serverAction(...params); const asyncIterable = readStreamableValue(object); - let streamedData: Partial | undefined; for await (const value of asyncIterable) { if (value !== undefined) { @@ -151,16 +156,18 @@ export function useStream( // options.onData(value as T); // } console.log("value", value); - streamedData = value; - setPartialData(streamedData); // Update data state with the latest value + if ("partial" in value) { + setPartialData(value.partial); // Update data state with the latest value + } else if ("final" in value) { + setData(value.final); // Update data state with the latest value + setIsComplete(true); + return value.final; + } } } - - setIsComplete(true); - setData(streamedData as T); - // If it completes, it means it's the full data. - return streamedData as T; + // // If it completes, it means it's the full data. + // return streamedData; } catch (err) { console.log("error", err); @@ -173,8 +180,9 @@ export function useStream( }; // If you use the "data" property, your component will re-render when the data gets updated. - return { data, partialData, isLoading, isComplete, isError, error, mutate }; + return { data: streamResult, partialData, isLoading, isComplete, isError, error, mutate }; } + ``` @@ -193,11 +201,13 @@ import { Resume } from "@/baml_client"; export default function Home() { // you can also rename these fields by using ":", like how we renamed partialData to "partialResume" - const { data, partialData: partialResume, isLoading, isError, error, mutate } = useStream(extractResume); + // `mutate` is a function that will start the stream. It takes in the same arguments as the BAML function. + const { data: completedData, partialData: partialResume, isLoading, isError, error, mutate } = useStream(extractResume); return (

BoundaryML Next.js Example

+ {isLoading &&

Loading...

} {isError &&

Error: {error?.message}

} From 1c6f975d8cc793841745da0db82ee1e2f1908e56 Mon Sep 17 00:00:00 2001 From: aaronvg Date: Thu, 12 Sep 2024 12:33:43 -0700 Subject: [PATCH 2/7] Keywords AI router (#953) --- docs/docs.yml | 3 ++- docs/docs/snippets/clients/providers/keywordsai.mdx | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 docs/docs/snippets/clients/providers/keywordsai.mdx diff --git a/docs/docs.yml b/docs/docs.yml index b0c6a8c02..46ef4ebd6 100644 --- a/docs/docs.yml +++ b/docs/docs.yml @@ -8,7 +8,6 @@ instances: repo: baml branch: canary - title: BAML Documentation navigation: @@ -100,6 +99,8 @@ navigation: path: docs/snippets/clients/providers/vllm.mdx - page: LMStudio path: docs/snippets/clients/providers/lmstudio.mdx + - page: KeywordsAI + path: docs/snippets/clients/providers/keywordsai.mdx - section: provider strategies contents: - page: fallback diff --git a/docs/docs/snippets/clients/providers/keywordsai.mdx b/docs/docs/snippets/clients/providers/keywordsai.mdx new file mode 100644 index 000000000..d3382e8b0 --- /dev/null +++ b/docs/docs/snippets/clients/providers/keywordsai.mdx @@ -0,0 +1,7 @@ +--- +title: Keywords AI +slug: docs/snippets/clients/providers/keywordsai +--- +Keywords AI is a proxying layer that allows you to route requests to hundreds of models. + +Follow the [Keywords AI + BAML Installation Guide](https://docs.keywordsai.co/integration/development-frameworks/baml) to get started! \ No newline at end of file From e99c5dd1903078d08aef451e4addc6110d7ca279 Mon Sep 17 00:00:00 2001 From: Samuel Lijin Date: Thu, 12 Sep 2024 13:24:47 -0700 Subject: [PATCH 3/7] docs: suggest correct python init command in vscode readme (#954) --- typescript/vscode-ext/packages/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript/vscode-ext/packages/README.md b/typescript/vscode-ext/packages/README.md index 248584f1d..404b03f7d 100644 --- a/typescript/vscode-ext/packages/README.md +++ b/typescript/vscode-ext/packages/README.md @@ -23,7 +23,7 @@ This VS Code extension provides support for the Baml language used to define LLM ```bash Python # If using your local installation, venv or conda: pip install baml-py -baml-cli generate init +baml-cli init ``` ```bash TypeScript From 342b657da69441306fa7711d7d14893cf8036f84 Mon Sep 17 00:00:00 2001 From: Samuel Lijin Date: Thu, 12 Sep 2024 15:01:03 -0700 Subject: [PATCH 4/7] docs: add more vscode debugging instructions (#955) --- .../docs_latest/vscode-output-dropdown.png | Bin 0 -> 13912 bytes .../docs_latest/vscode-reload-window.png | Bin 0 -> 20047 bytes .../debugging/vscode-playground.mdx | 28 +++++++++++++++--- 3 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 docs/assets/images/docs_latest/vscode-output-dropdown.png create mode 100644 docs/assets/images/docs_latest/vscode-reload-window.png diff --git a/docs/assets/images/docs_latest/vscode-output-dropdown.png b/docs/assets/images/docs_latest/vscode-output-dropdown.png new file mode 100644 index 0000000000000000000000000000000000000000..f24a4669f06f67392d9df4e5a470935db903d3d8 GIT binary patch literal 13912 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 r^cB1-F*$iNB~nj_JR|y~zT5r}2rrfK -- If nothing works, please file an issue on [Github](https://github.com/BoundaryML/baml/issues), ideally with a screenshot of the error and the steps to reproduce it. + +- Ensure your VSCode Extension for BAML is up-to-date (Cursor users must [update manually][cursor-ext-update]) + + + +- Check the BAML extension logs + + - Open the `Output` panel in VSCode (press `Ctrl + Shift + U` or `Cmd + Shift + U`) + + + + - Find the dropdown currently showing `Main`, and select `BAML Language Server` from the dropdown + + - Share these logs with us when you reach out to us + +If you continue to have issues, please [let us know on Discord](https://discord.gg/BTNBeXGuaS) or [file an issue on Github](https://github.com/BoundaryML/baml/issues). + +[cursor-ext-update]: https://www.cursor.com/how-to-install-extension + +{/* placeholder split */} ## Common Issues + ### No code lens in BAML files This can happen in two cases: @@ -19,8 +38,9 @@ This can happen in two cases: 2. BAML extension is broken. Please try the tools above! -### BAML extension is not working +{/* placeholder split */} +## BAML extension is not working ### Tests hanging We've seen sparse repros of this, but closing the playground and reopening it should fix it. From ee80451de85063b37e658ba58571c791e8514273 Mon Sep 17 00:00:00 2001 From: aaronvg Date: Fri, 13 Sep 2024 11:18:58 -0700 Subject: [PATCH 5/7] fix util for nextjs (#957) --- docs/docs/baml-nextjs/baml-nextjs.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/docs/baml-nextjs/baml-nextjs.mdx b/docs/docs/baml-nextjs/baml-nextjs.mdx index 424319f48..f035aa193 100644 --- a/docs/docs/baml-nextjs/baml-nextjs.mdx +++ b/docs/docs/baml-nextjs/baml-nextjs.mdx @@ -27,6 +27,7 @@ In `app/utils/streamableObject.tsx` add the following code: ```typescript import { createStreamableValue, StreamableValue as BaseStreamableValue } from "ai/rsc"; import { BamlStream } from "@boundaryml/baml"; +import { b } from "@/baml_client"; // You can change the path of this to wherever your baml_client is located. // ------------------------------ @@ -91,7 +92,8 @@ export function makeStreamable< object: BaseStreamableValue>>; }> { return async (...args) => { - const stream = func(...args); + const boundFunc = func.bind(b.stream); + const stream = boundFunc(...args); return streamHelper(stream); }; } From 1e911e73246589306fa5039ddafac18d1dd7ab4f Mon Sep 17 00:00:00 2001 From: Samuel Lijin Date: Fri, 13 Sep 2024 12:43:18 -0700 Subject: [PATCH 6/7] docs!: require Ruby users to go through OpenAPI, instead of through the gem (#956) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PSA to Ruby users: if you currently rely on the `baml` gem, please reach out to us either in the Discord or on GitHub so we can migrate you to the OpenAPI-generated Ruby client. Over the next few weeks, we plan to provide white-glove support to migrate all Ruby users from the `baml` gem to the OpenAPI-generated client and move the `baml` gem into maintenance mode. This will mean that: - we will remove `baml`-gem-specific documentation from https://docs.boundaryml.com/ - we will only publish security fixes for the `baml` gem - we will ask that you have a plan to migrate to the OpenAPI-generated Ruby client, before providing any support for ## Why are we doing this? Bluntly speaking: we don’t think we can provide users of the `baml` gem with a good experience. It’s in a pretty sorry state today: - it doesn't work on Windows - it doesn't work out of the box on macOS (some issue with `darwin` vs `darwin23`?) - it still doesn't support tracing - it's incompatible with async (the underlying FFI libraries do not make this easy, and we don’t have the engineering resources to do this well) We're also not aware of anyone _using_ the `baml` gem today, we suspect in large part because of how buggy it is. Due to the combination of all of these issues, we will be dropping the `baml` gem in favor of the OpenAPI-generated Ruby client. --- docs/docs.yml | 4 +- docs/docs/calling-baml/calling-functions.mdx | 22 -- docs/docs/calling-baml/client-registry.mdx | 35 +-- docs/docs/calling-baml/concurrent-calls.mdx | 10 +- docs/docs/calling-baml/dynamic-types.mdx | 278 +++++++++--------- docs/docs/calling-baml/exceptions.mdx | 12 +- .../calling-baml/generate-baml-client.mdx | 63 ++-- docs/docs/calling-baml/multi-modal.mdx | 9 +- docs/docs/calling-baml/set-env-vars.mdx | 9 +- docs/docs/calling-baml/streaming.mdx | 67 +---- docs/docs/get-started/quickstart/ruby.mdx | 77 ----- docs/docs/get-started/what-is-baml.mdx | 2 +- docs/docs/snippets/clients/overview.mdx | 2 +- .../snippets/functions/classification.mdx | 16 +- docs/docs/snippets/functions/extraction.mdx | 15 +- docs/docs/snippets/functions/overview.mdx | 38 +-- docs/docs/snippets/supported-types.mdx | 40 --- engine/.gitignore | 10 + engine/baml-runtime/src/cli/init.rs | 14 +- typescript/vscode-ext/packages/README.md | 56 ++-- 20 files changed, 248 insertions(+), 531 deletions(-) delete mode 100644 docs/docs/get-started/quickstart/ruby.mdx diff --git a/docs/docs.yml b/docs/docs.yml index 46ef4ebd6..0f75448cd 100644 --- a/docs/docs.yml +++ b/docs/docs.yml @@ -24,8 +24,6 @@ navigation: path: docs/get-started/quickstart/python.mdx - page: TypeScript path: docs/get-started/quickstart/typescript.mdx - - page: Ruby - path: docs/get-started/quickstart/ruby.mdx - page: Any Language (OpenAPI) path: docs/get-started/quickstart/openapi.mdx - page: VSCode @@ -151,7 +149,7 @@ navigation: path: docs/calling-baml/dynamic-types.mdx - page: Client Registry path: docs/calling-baml/client-registry.mdx - - section: BAML with Python/TS/Ruby + - section: BAML with Python/TS/OpenAPI contents: - page: Generate the BAML Client path: docs/calling-baml/generate-baml-client.mdx diff --git a/docs/docs/calling-baml/calling-functions.mdx b/docs/docs/calling-baml/calling-functions.mdx index 0772bb40a..ad969c5d3 100644 --- a/docs/docs/calling-baml/calling-functions.mdx +++ b/docs/docs/calling-baml/calling-functions.mdx @@ -83,28 +83,6 @@ if (require.main === module) { ``` - - -BAML will generate `Baml.Client.ClassifyMessage()` for you, which you can use like so: - -```ruby main.rb -require_relative "baml_client/client" - -$b = Baml.Client - -def main - category = $b.ClassifyMessage(input: "I want to cancel my order") - puts category - category == Baml::Types::Category::CancelOrder -end - -if __FILE__ == $0 - puts main -end - -``` - - If you're using an OpenAPI-generated client, every BAML function will generate diff --git a/docs/docs/calling-baml/client-registry.mdx b/docs/docs/calling-baml/client-registry.mdx index f4b5f1a83..16f82078c 100644 --- a/docs/docs/calling-baml/client-registry.mdx +++ b/docs/docs/calling-baml/client-registry.mdx @@ -2,7 +2,7 @@ title: Client Registry slug: docs/calling-baml/client-registry --- -{/* TODO: use fern Group elements instead of CodeBlock elements for Python/TS/Ruby */} +{/* TODO: use fern Group elements instead of CodeBlock elements for Python/TS/OpenAPI */} If you need to modify the model / parameters for an LLM client at runtime, you can modify the `ClientRegistry` for any specified function. @@ -52,39 +52,8 @@ async function run() { ``` - - -```ruby -require_relative "baml_client/client" - -def run - cr = Baml::ClientRegistry.new - - # Creates a new client - cr.add_llm_client( - name: 'MyAmazingClient', - provider: 'openai', - options: { - model: 'gpt-4o', - temperature: 0.7, - api_key: ENV['OPENAI_API_KEY'] - } - ) - - # Sets MyAmazingClient as the primary client - cr.set_primary('MyAmazingClient') - - # ExtractResume will now use MyAmazingClient as the calling client - res = Baml.Client.extract_resume(input: '...', baml_options: { client_registry: cr }) -end - -# Call the asynchronous function -run -``` - - -Dynamic types are not yet supported when used via OpenAPI. +Client registries are not yet supported when used via OpenAPI. Please let us know if you want this feature, either via [Discord] or [GitHub][openapi-feedback-github-issue]. diff --git a/docs/docs/calling-baml/concurrent-calls.mdx b/docs/docs/calling-baml/concurrent-calls.mdx index 5e6dec1d7..eb1a2411c 100644 --- a/docs/docs/calling-baml/concurrent-calls.mdx +++ b/docs/docs/calling-baml/concurrent-calls.mdx @@ -33,6 +33,7 @@ function ClassifyMessage(input: string) -> Category { + You can make concurrent `b.ClassifyMessage()` calls like so: @@ -77,11 +78,10 @@ if (require.main === module) { ``` - - -BAML Ruby (beta) does not currently support async/concurrent calls. - -Please [contact us](/contact) if this is something you need. + +Please reference the concurrency docs for your language of choice. +We'll add examples for how to do this soon, though! + \ No newline at end of file diff --git a/docs/docs/calling-baml/dynamic-types.mdx b/docs/docs/calling-baml/dynamic-types.mdx index 2a79df2fe..bf3c340e6 100644 --- a/docs/docs/calling-baml/dynamic-types.mdx +++ b/docs/docs/calling-baml/dynamic-types.mdx @@ -2,9 +2,9 @@ slug: docs/calling-baml/dynamic-types --- -Sometimes you have a **output schemas that change at runtime** -- for example if -you have a list of Categories that you need to classify that come from a -database, or your schema is user-provided. +Sometimes you have **output schemas that change at runtime** -- for example if +the list of `Categories` you need to classify comes from a database, or your +users can customize a prompt schema. **Dynamic types are types that can be modified at runtime**, which means you can change the output schema of a function at runtime. @@ -12,95 +12,78 @@ change the output schema of a function at runtime. Here are the steps to make this work: 1. Add `@@dynamic` to the class or enum definition to mark it as dynamic -```rust baml -enum Category { - VALUE1 // normal static enum values that don't change - VALUE2 - @@dynamic // this enum can have more values added at runtime -} - -function DynamicCategorizer(input: string) -> Category { - client GPT4 - prompt #" - Given a string, classify it into a category - {{ input }} - - {{ ctx.output_format }} - "# -} - -``` + ```rust baml + enum Category { + VALUE1 // normal static enum values that don't change + VALUE2 + @@dynamic // this enum can have more values added at runtime + } + + function DynamicCategorizer(input: string) -> Category { + client GPT4 + prompt #" + Given a string, classify it into a category + {{ input }} + + {{ ctx.output_format }} + "# + } + + ``` 2. Create a TypeBuilder and modify the existing type. All dynamic types you define in BAML will be available as properties of `TypeBuilder`. Think of the typebuilder as a registry of modified runtime types that the baml function will read from when building the output schema in the prompt. - - - -```python -from baml_client.type_builder import TypeBuilder -from baml_client import b - -async def run(): - tb = TypeBuilder() - tb.Category.add_value('VALUE3') - tb.Category.add_value('VALUE4') - # Pass the typebuilder in the baml_options argument -- the last argument of the function. - res = await b.DynamicCategorizer("some input", { "tb": tb }) - # Now res can be VALUE1, VALUE2, VALUE3, or VALUE4 - print(res) - -``` - - - -```typescript -import TypeBuilder from '../baml_client/type_builder' -import { - b -} from '../baml_client' - -async function run() { - const tb = new TypeBuilder() - tb.Category.addValue('VALUE3') - tb.Category.addValue('VALUE4') - const res = await b.DynamicCategorizer("some input", { tb: tb }) - // Now res can be VALUE1, VALUE2, VALUE3, or VALUE4 - console.log(res) -} -``` - - - -```ruby -require_relative '../baml_client' - -def run - tb = Baml::TypeBuilder.new - tb.Category.add_value('VALUE3') - tb.Category.add_value('VALUE4') - res = Baml.Client.dynamic_categorizer(input: "some input", baml_options: {tb: tb}) - # Now res can be VALUE1, VALUE2, VALUE3, or VALUE4 - puts res -end -``` - - - -Dynamic types are not yet supported when used via OpenAPI. - -Please let us know if you want this feature, either via [Discord] or [GitHub][openapi-feedback-github-issue]. - -[Discord]: https://discord.gg/BTNBeXGuaS -[openapi-feedback-github-issue]: https://github.com/BoundaryML/baml/issues/892 - - - + + + + ```python + from baml_client.type_builder import TypeBuilder + from baml_client import b + + async def run(): + tb = TypeBuilder() + tb.Category.add_value('VALUE3') + tb.Category.add_value('VALUE4') + # Pass the typebuilder in the baml_options argument -- the last argument of the function. + res = await b.DynamicCategorizer("some input", { "tb": tb }) + # Now res can be VALUE1, VALUE2, VALUE3, or VALUE4 + print(res) + + ``` + + + + ```typescript + import TypeBuilder from '../baml_client/type_builder' + import { + b + } from '../baml_client' + + async function run() { + const tb = new TypeBuilder() + tb.Category.addValue('VALUE3') + tb.Category.addValue('VALUE4') + const res = await b.DynamicCategorizer("some input", { tb: tb }) + // Now res can be VALUE1, VALUE2, VALUE3, or VALUE4 + console.log(res) + } + ``` + + + + Dynamic types are not yet supported when used via OpenAPI. + + Please let us know if you want this feature, either via [Discord] or [GitHub][openapi-feedback-github-issue]. + + + ### Dynamic BAML Classes -Existing BAML classes marked with @@dynamic will be available as properties of `TypeBuilder`. + +Existing BAML classes marked with `@@dynamic` will be available as properties of `TypeBuilder`. ```rust BAML class User { @@ -122,7 +105,8 @@ function DynamicUserCreator(user_info: string) -> User { Modify the `User` schema at runtime: - + + ```python Python from baml_client.type_builder import TypeBuilder @@ -137,8 +121,11 @@ async def run(): print(res) ``` + + + -```typescript TypeScript +```typescript import TypeBuilder from '../baml_client/type_builder' import { b @@ -153,29 +140,23 @@ async function run() { console.log(res) } ``` + -```ruby Ruby -require_relative 'baml_client/client' + -def run - tb = Baml::TypeBuilder.new - tb.User.add_property('email', tb.string) - tb.User.add_property('address', tb.string) - - res = Baml.Client.dynamic_user_creator(input: "some user info", baml_options: {tb: tb}) - # Now res can have email and address fields - puts res -end -``` - +Dynamic types are not yet supported when used via OpenAPI. -### Creating new dynamic classes or enums not in BAML -Here we create a new `Hobbies` enum, and a new class called `Address`. +Please let us know if you want this feature, either via [Discord] or [GitHub][openapi-feedback-github-issue]. + + - +### Creating new dynamic classes or enums not in BAML +Here we create a new `Hobbies` enum, and a new class called `Address`. -```python Python + + +```python from baml_client.type_builder import TypeBuilder from baml_client import b @@ -195,8 +176,11 @@ async def run(): print(res) ``` + + + -```typescript TypeScript +```typescript import TypeBuilder from '../baml_client/type_builder' import { b } from '../baml_client' @@ -217,49 +201,46 @@ async function run() { console.log(res) } ``` + -```ruby Ruby -require_relative 'baml_client/client' + -def run - tb = Baml::TypeBuilder.new - hobbies_enum = tb.add_enum('Hobbies') - hobbies_enum.add_value('Soccer') - hobbies_enum.add_value('Reading') +Dynamic types are not yet supported when used via OpenAPI. - address_class = tb.add_class('Address') - address_class.add_property('street', tb.string) +Please let us know if you want this feature, either via [Discord] or [GitHub][openapi-feedback-github-issue]. - tb.User.add_property('hobby', hobbies_enum.type.optional) - tb.User.add_property('address', address_class.type.optional) - - res = Baml::Client.dynamic_user_creator(input: "some user info", baml_options: { tb: tb }) - # Now res might have the hobby property, which can be Soccer or Reading - puts res -end -``` - + + ### Adding descriptions to dynamic types - + + +```python +from baml_client.type_builder import TypeBuilder +from baml_client import b -```python Python -tb = TypeBuilder() -tb.User.add_property("email", tb.string()).description("The user's email") +async def run(): + tb = TypeBuilder() + tb.User.add_property("email", tb.string()).description("The user's email") ``` + -```typescript TypeScript + +```typescript const tb = new TypeBuilder() tb.User.addProperty("email", tb.string()).description("The user's email") ``` + -```ruby Ruby -tb = Baml::TypeBuilder.new -tb.User.add_property("email", tb.string).description("The user's email") -``` + + +Dynamic types are not yet supported when used via OpenAPI. + +Please let us know if you want this feature, either via [Discord] or [GitHub][openapi-feedback-github-issue]. - + + ### Building dynamic types from JSON schema @@ -267,9 +248,10 @@ We have a working implementation of this, but are waiting for a concrete use cas Please chime in on [the GitHub issue](https://github.com/BoundaryML/baml/issues/771) if this is something you'd like to use. - - -```python Python + + +```python +import pydantic import pydantic from baml_client import b @@ -285,8 +267,11 @@ res = await b.ExtractPeople( {"tb": tb}, ) ``` + -```typescript TypeScript + +```typescript +import 'z' from zod import 'z' from zod import 'zodToJsonSchema' from zod-to-json-schema import { b } from '../baml_client' @@ -308,15 +293,16 @@ const res = await b.ExtractPeople( ) ``` -```ruby Ruby -tb = Baml::TypeBuilder.new -tb.unstable_features.add_json_schema(...) + -res = Baml::Client.extract_people( - input: "My name is Harrison. My hair is black and I'm 6 feet tall. I'm pretty good around the hoop. I like giraffes.", - baml_options: { tb: tb } -) + -puts res -``` - +Dynamic types are not yet supported when used via OpenAPI. + +Please let us know if you want this feature, either via [Discord] or [GitHub][openapi-feedback-github-issue]. + + + + +[Discord]: https://discord.gg/BTNBeXGuaS +[openapi-feedback-github-issue]: https://github.com/BoundaryML/baml/issues/892 \ No newline at end of file diff --git a/docs/docs/calling-baml/exceptions.mdx b/docs/docs/calling-baml/exceptions.mdx index ae666abe9..1cee2a1e8 100644 --- a/docs/docs/calling-baml/exceptions.mdx +++ b/docs/docs/calling-baml/exceptions.mdx @@ -21,8 +21,8 @@ from baml_py.errors import BamlError, BamlInvalidArgumentError, BamlClientError, // "BamlError: BamlClientError: BamlClientHttpError:" ``` -```ruby Ruby -Not available yet +```text OpenAPI +We do not generate error types in OpenAPI. ``` @@ -44,6 +44,8 @@ Subclass of `BamlError`. Raised when one or multiple arguments to a function are invalid. +When using BAML-over-HTTP a.k.a. OpenAPI, this is `400 Bad Request`. + ### BamlClientError Subclass of `BamlError`. @@ -54,11 +56,13 @@ Raised when a client fails to return a valid response. In the case of aggregate clients like `fallback` or those with `retry_policy`, only the last client's error is raised. +When using BAML-over-HTTP a.k.a. OpenAPI, this is `502 Bad Gateway`. + #### BamlClientHttpError Subclass of `BamlClientError`. -Raised when the HTTP request made by a client fails with a non-200 status code. +Raised when BAML successfully makes an HTTP request to an LLM provider, but the provider returns a non-200 status code. The raw text from the LLM that failed to parse into the expected return type of a function. + +When using BAML-over-HTTP a.k.a. OpenAPI, this is `500 Internal Server Error` (we expect to use a more specific status code here soon). diff --git a/docs/docs/calling-baml/generate-baml-client.mdx b/docs/docs/calling-baml/generate-baml-client.mdx index fa5f0b0ec..42afedaa9 100644 --- a/docs/docs/calling-baml/generate-baml-client.mdx +++ b/docs/docs/calling-baml/generate-baml-client.mdx @@ -16,50 +16,60 @@ you save a BAML file. Otherwise, you can generate the client manually: [BAML extension]: https://marketplace.visualstudio.com/items?itemName=Boundary.baml-extension - + + -```bash Python + +```bash pipx pipx run baml-cli generate --from path/to/baml_src +``` -# If using your local installation, venv or conda: +```bash pip pip install baml-py baml-cli generate --from path/to/baml_src +``` -# If using poetry: +```bash poetry poetry add baml-py poetry run baml-cli generate --from path/to/baml_src +``` -# If using pipenv: +```bash pipenv pipenv install baml-py pipenv run baml-cli generate --from path/to/baml_src ``` + + -```bash TypeScript + + +```bash npx npx @boundaryml/baml generate --from path/to/baml_src +``` -# If using npm: +```bash npm npm install @boundaryml/baml npm run baml-cli generate --from path/to/baml_src +``` -# If using pnpm: +```bash pnpm pnpm install @boundaryml/baml pnpm run baml-cli generate --from path/to/baml_src +``` -# If using pnpm: +```bash yarn yarn add @boundaryml/baml yarn run baml-cli generate --from path/to/baml_src ``` + + -```bash Ruby (beta) -bundle add baml -bundle exec baml-cli generate --from path/to/baml_src -``` - + ```bash OpenAPI npx @boundaryml/baml-cli generate --from path/to/baml_src ``` - - + + ## Best Practices @@ -76,7 +86,7 @@ generate code for each of them. ```baml Python generator target { - // Valid values: "python/pydantic", "typescript", "ruby/sorbet" + // Valid values: "python/pydantic", "typescript", "rest/openapi" output_type "python/pydantic" // Where the generated code will be saved (relative to baml_src/) @@ -94,7 +104,7 @@ generator target { ```baml TypeScript generator target { - // Valid values: "python/pydantic", "typescript", "ruby/sorbet" + // Valid values: "python/pydantic", "typescript", "rest/openapi" output_type "typescript" // Where the generated code will be saved (relative to baml_src/) @@ -110,22 +120,9 @@ generator target { } ``` -```baml Ruby (beta) -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 "../" - - // Version of runtime to generate code for (should match installed `baml` package version) - version "0.54.0" -} -``` - ```baml OpenAPI generator target { - // Valid values: "python/pydantic", "typescript", "ruby/sorbet", "rest/openapi" + // Valid values: "python/pydantic", "typescript", "rest/openapi" output_type "rest/openapi" // Where the generated code will be saved (relative to baml_src/) @@ -170,7 +167,7 @@ jobs: steps: - uses: actions/checkout@v4 - # Install your Python/Node/Ruby (beta) dependencies here + # Install your Python/Node/etc dependencies here - name: Generate BAML client run: baml-cli generate --from baml_src diff --git a/docs/docs/calling-baml/multi-modal.mdx b/docs/docs/calling-baml/multi-modal.mdx index 90af56e51..a7fd7e91f 100644 --- a/docs/docs/calling-baml/multi-modal.mdx +++ b/docs/docs/calling-baml/multi-modal.mdx @@ -47,8 +47,8 @@ import { Image } from "@boundaryml/baml" ``` -```ruby Ruby (beta) -we're working on it! +```bash OpenAPI +# Use the generated BamlImage type. ``` @@ -94,7 +94,8 @@ import { Audio } from "@boundaryml/baml" ``` -```ruby Ruby (beta) -we're working on it! +```bash OpenAPI +# Use the generated BamlAudio type. ``` + diff --git a/docs/docs/calling-baml/set-env-vars.mdx b/docs/docs/calling-baml/set-env-vars.mdx index 7f6d0ceb9..f2bc7ecf5 100644 --- a/docs/docs/calling-baml/set-env-vars.mdx +++ b/docs/docs/calling-baml/set-env-vars.mdx @@ -52,14 +52,12 @@ dotenv.config() import { b } from 'baml-client' ``` -```ruby Ruby (beta) -require 'dotenv/load' +```text OpenAPI +Make sure to set the environment variables wherever you run `baml-cli dev` or `baml-cli serve`. -# Wait to import the BAML client until after loading environment variables -require 'baml_client' +See the [deployment instructions](/docs/get-started/deploying/openapi) to learn more. ``` - @@ -69,7 +67,6 @@ Environment variables are primarily used in clients to propagate authorization credentials, such as API keys, like so: ```baml - client GPT4o { provider baml-openai-chat options { diff --git a/docs/docs/calling-baml/streaming.mdx b/docs/docs/calling-baml/streaming.mdx index f03b6e291..ce5a012a8 100644 --- a/docs/docs/calling-baml/streaming.mdx +++ b/docs/docs/calling-baml/streaming.mdx @@ -163,74 +163,9 @@ if (require.main === module) { ``` - -BAML will generate `Baml.Client.stream.ExtractReceiptInfo()` for you, -which you can use like so: - -```ruby main.rb -require_relative "baml_client/client" - -$b = Baml.Client - -# Using both iteration and get_final_response() from a stream -def example1(receipt) - stream = $b.stream.ExtractReceiptInfo(receipt) - - stream.each do |partial| - puts "partial: #{partial.items&.length} items" - end - - final = stream.get_final_response - puts "final: #{final.items.length} items" -end - -# Using only iteration of a stream -def example2(receipt) - $b.stream.ExtractReceiptInfo(receipt).each do |partial| - puts "partial: #{partial.items&.length} items" - end -end - -# Using only get_final_response() of a stream -# -# In this case, you should just use BamlClient.ExtractReceiptInfo(receipt) instead, -# which is faster and more efficient. -def example3(receipt) - final = $b.stream.ExtractReceiptInfo(receipt).get_final_response - puts "final: #{final.items.length} items" -end - -receipt = <<~RECEIPT - 04/14/2024 1:05 pm - - Ticket: 220000082489 - Register: Shop Counter - Employee: Connor - Customer: Sam - Item # Price - Guide leash (1 Pair) uni UNI - 1 $34.95 - The Index Town Walls - 1 $35.00 - Boot Punch - 3 $60.00 - Subtotal $129.95 - Tax ($129.95 @ 9%) $11.70 - Total Tax $11.70 - Total $141.65 -RECEIPT - -if __FILE__ == $0 - example1(receipt) - example2(receipt) - example3(receipt) -end -``` - - -Streaming is not yet supported via OpenAPI, but it will be coming soon! +Streaming is not yet supported via OpenAPI, but it's coming soon! \ No newline at end of file diff --git a/docs/docs/get-started/quickstart/ruby.mdx b/docs/docs/get-started/quickstart/ruby.mdx deleted file mode 100644 index e649b38ee..000000000 --- a/docs/docs/get-started/quickstart/ruby.mdx +++ /dev/null @@ -1,77 +0,0 @@ ---- -slug: docs/get-started/quickstart/ruby ---- - -Here's a sample repository: https://github.com/BoundaryML/baml-examples/tree/main/ruby-example - -To set up BAML in ruby do the following: - - - ### Install BAML VSCode Extension - https://marketplace.visualstudio.com/items?itemName=boundary.baml-extension - - - syntax highlighting - - testing playground - - prompt previews - - - ### Install baml - ```bash - bundle init - bundle add baml sorbet-runtime - ``` - - ### Add some starter code - This will give you some starter BAML code in a `baml_src` directory. - - ```bash - bundle exec baml-cli init - ``` - - - ### Generate Ruby code from .baml files - - This command will help you convert `.baml` files to `.rb` files. Everytime you modify your `.baml` files, - you must re-run this command, and regenerate the `baml_client` folder. - - - Our [VSCode extension](https://marketplace.visualstudio.com/items?itemName=Boundary.baml-extension) automatically runs this command when you save a BAML file. - - - ```bash - bundle exec baml-cli generate - ``` - - - ### Use a baml function in Ruby! - If `baml_client` doesn't exist, make sure to run the previous step! - - ```ruby main.rb - require_relative "baml_client/client" - - def example(raw_resume) - # r is an instance of Baml::Types::Resume, defined in baml_client/types - r = Baml.Client.ExtractResume(resume: raw_resume) - - puts "ExtractResume response:" - puts r.inspect - end - - def example_stream(raw_resume) - stream = Baml.Client.stream.ExtractResume(resume: raw_resume) - - stream.each do |msg| - # msg is an instance of Baml::PartialTypes::Resume - # defined in baml_client/partial_types - puts msg.inspect - end - - stream.get_final_response - end - - example 'Grace Hopper created COBOL' - example_stream 'Grace Hopper created COBOL' - ``` - - - diff --git a/docs/docs/get-started/what-is-baml.mdx b/docs/docs/get-started/what-is-baml.mdx index 40d552f3d..467a4d67b 100644 --- a/docs/docs/get-started/what-is-baml.mdx +++ b/docs/docs/get-started/what-is-baml.mdx @@ -23,7 +23,7 @@ Share your creations and ask questions in our [Discord](https://discord.gg/BTNBe ## Features ### Language features -- **Python / Typescript / Ruby support**: Plug-and-play BAML with other languages +- **Python / Typescript / OpenAPI / HTTP support**: use BAML with any language or tech stack - **JSON correction**: BAML fixes bad JSON returned by LLMs (e.g. unquoted keys, newlines, comments, extra quotes, and more) - **Wide model support**: Ollama, Openai, Anthropic, Gemini. Tested on small models like Llama2 - **Streaming**: Stream structured partial outputs diff --git a/docs/docs/snippets/clients/overview.mdx b/docs/docs/snippets/clients/overview.mdx index 64ba4d081..42aaca7de 100644 --- a/docs/docs/snippets/clients/overview.mdx +++ b/docs/docs/snippets/clients/overview.mdx @@ -40,7 +40,7 @@ variable, or you want to point `base_url` to a different endpoint, you should us the latter form. -If you want to specify which client to use at runtime, in your Python/TS/Ruby code, +If you want to specify which client to use at runtime, in your Python/TS code, you can use the [client registry](/docs/calling-baml/client-registry) to do so. This can come in handy if you're trying to, say, send 10% of your requests to a diff --git a/docs/docs/snippets/functions/classification.mdx b/docs/docs/snippets/functions/classification.mdx index f7dd0b521..f2eadf5df 100644 --- a/docs/docs/snippets/functions/classification.mdx +++ b/docs/docs/snippets/functions/classification.mdx @@ -59,20 +59,10 @@ import { Category } from 'baml_client/types' ... const result = await b.ClassifyMessage("I want to cancel my order") assert(result === Category.Cancel) +``` -```ruby ruby -require_relative "baml_client/client" - -$b = Baml.Client - -def main - category = $b.ClassifyMessage(input: "I want to cancel my order") - puts category == Baml::Types::Category::CancelOrder -end - -if __FILE__ == $0 - main -end +```bash OpenAPI +curl localhost:2024/call/ClassifyMessage -d '{"input": "I want to cancel my order"}' ``` diff --git a/docs/docs/snippets/functions/extraction.mdx b/docs/docs/snippets/functions/extraction.mdx index 5f7c45c81..2b59422df 100644 --- a/docs/docs/snippets/functions/extraction.mdx +++ b/docs/docs/snippets/functions/extraction.mdx @@ -66,14 +66,11 @@ import { Category } from 'baml_client/types' const result_from_image = await b.DescribeCharacter(Image.fromUrl("http://...")) ``` -```ruby ruby -require_relative "baml_client/client" - -$b = Baml.Client - -# images are not supported in Ruby -def example - stream = $b.DescribeCharacter("Bob the builder wears overalls") -end +```bash OpenAPI +curl localhost:2024/call/ClassifyMessage -d '{ \ + "image_or_paragraph": { \ + "url": "http://..." \ + } +}' ``` \ No newline at end of file diff --git a/docs/docs/snippets/functions/overview.mdx b/docs/docs/snippets/functions/overview.mdx index c7990162a..0e2b99d80 100644 --- a/docs/docs/snippets/functions/overview.mdx +++ b/docs/docs/snippets/functions/overview.mdx @@ -46,7 +46,7 @@ from baml_client import b from baml_client.types import Resume async def main(): -resume_text = """Jason Doe\nPython, Rust\nUniversity of California, Berkeley, B.S.\nin Computer Science, 2020\nAlso an expert in Tableau, SQL, and C++\n""" + resume_text = """Jason Doe\nPython, Rust\nUniversity of California, Berkeley, B.S.\nin Computer Science, 2020\nAlso an expert in Tableau, SQL, and C++\n""" # this function comes from the autogenerated "baml_client". # It calls the LLM you specified and handles the parsing. @@ -72,19 +72,10 @@ async function main() { } ``` -```ruby ruby - -require_relative "baml_client/client" -b = Baml.Client - -# Note this is not async -res = b.TestFnNamedArgsSingleClass( - myArg: Baml::Types::Resume.new( - key: "key", - key_two: true, - key_three: 52, - ) -) +```bash OpenAPI +curl localhost:2024/call/AnalyzeResume -d '{ \ + "resume_text": "Jason Doe\nPython, Rust\nUniversity of California, Berkeley, B.S.\nin Computer Science, 2020\nAlso an expert in Tableau, SQL, and C++\n" +}' ``` @@ -103,7 +94,7 @@ from baml_client import b ```` -```typescript typescript +```typescript TypeScript import { Resume, b } from "baml_client" ... @@ -113,16 +104,13 @@ import { Resume, b } from "baml_client" }) ```` -```ruby Ruby -require_relative "baml_client/client" -b = Baml.Client -... -res = b.AnalyzeResume( - myArg: Baml::Types::Resume.new( - name: "key", - education: [...] - ) -) +```bash OpenAPI +curl localhost:2024/call/AnalyzeResume -d '{ \ + "input": { \ + "name": "Mark", \ + "education": [...] \ + } +}' ``` diff --git a/docs/docs/snippets/supported-types.mdx b/docs/docs/snippets/supported-types.mdx index 2181c258c..793cbe7cd 100644 --- a/docs/docs/snippets/supported-types.mdx +++ b/docs/docs/snippets/supported-types.mdx @@ -70,25 +70,6 @@ import { Image } from "@boundaryml/baml" ) ``` -```ruby Ruby -require_relative "baml_client/client" - -b = Baml.Client -Image = Baml::Image - -def test_image_input - # from URL - res = b.TestImageInput( - img: Image.from_url("https://upload.wikimedia.org/wikipedia/en/4/4d/Shrek_%28character%29.png") - ) - - # Base64 image - image_b64 = "iVBORw0K...." - res = b.TestImageInput( - img: Image.from_base64("image/png", image_b64) - ) -end -``` ### `audio` @@ -144,27 +125,6 @@ import { Audio } from "@boundaryml/baml" ``` -```ruby Ruby -require_relative "baml_client/client" - -b = Baml.Client -Audio = Baml::Audio - -def test_audio_input - # from URL - res = b.TestAudioInput( - audio: Audio.from_url( - "https://actions.google.com/sounds/v1/emergency/beeper_emergency_call.ogg" - ) - ) - - # Base64 image - audio_b64 = "iVBORw0K...." - res = b.TestAudioInput( - audio: Audio.from_base64("audio/mp3", audio_b64) - ) -end -``` ## Composite/Structured Types diff --git a/engine/.gitignore b/engine/.gitignore index 6e771f815..8a48541a9 100644 --- a/engine/.gitignore +++ b/engine/.gitignore @@ -3,6 +3,16 @@ debug/ target/ +# NOTE(sam): I use this on my own laptop, so that `cargo build` doesn't have +# to fight rust-analyzer for the build directory lock. +# +# To get the same, add this to your VSCode settings: +# +# "rust-analyzer.cargo.targetDir": "./target-rust-analyzer", +# +# I'm not turning it on for everyone, because this is expensive compute/storage wise. +target-rust-analyzer/ + # These are backup files generated by rustfmt **/*.rs.bk diff --git a/engine/baml-runtime/src/cli/init.rs b/engine/baml-runtime/src/cli/init.rs index 78975481d..ef5bae29f 100644 --- a/engine/baml-runtime/src/cli/init.rs +++ b/engine/baml-runtime/src/cli/init.rs @@ -187,7 +187,7 @@ fn generate_main_baml_content( // your choice. You can have multiple generators if you use multiple languages. // Just ensure that the output_dir is different for each generator. generator target {{ - // Valid values: "python/pydantic", "typescript", "ruby/sorbet", "rest/openapi" + // Valid values: "python/pydantic", "typescript", "rest/openapi" output_type "{output_type}" // Where the generated code will be saved (relative to baml_src/) @@ -225,7 +225,7 @@ mod tests { // your choice. You can have multiple generators if you use multiple languages. // Just ensure that the output_dir is different for each generator. generator target {{ - // Valid values: "python/pydantic", "typescript", "ruby/sorbet", "rest/openapi" + // Valid values: "python/pydantic", "typescript", "rest/openapi" output_type "python/pydantic" // Where the generated code will be saved (relative to baml_src/) @@ -254,7 +254,7 @@ generator target {{ // your choice. You can have multiple generators if you use multiple languages. // Just ensure that the output_dir is different for each generator. generator target {{ - // Valid values: "python/pydantic", "typescript", "ruby/sorbet", "rest/openapi" + // Valid values: "python/pydantic", "typescript", "rest/openapi" output_type "typescript" // Where the generated code will be saved (relative to baml_src/) @@ -283,7 +283,7 @@ generator target {{ // your choice. You can have multiple generators if you use multiple languages. // Just ensure that the output_dir is different for each generator. generator target {{ - // Valid values: "python/pydantic", "typescript", "ruby/sorbet", "rest/openapi" + // Valid values: "python/pydantic", "typescript", "rest/openapi" output_type "ruby/sorbet" // Where the generated code will be saved (relative to baml_src/) @@ -308,7 +308,7 @@ generator target {{ // your choice. You can have multiple generators if you use multiple languages. // Just ensure that the output_dir is different for each generator. generator target {{ - // Valid values: "python/pydantic", "typescript", "ruby/sorbet", "rest/openapi" + // Valid values: "python/pydantic", "typescript", "rest/openapi" output_type "rest/openapi" // Where the generated code will be saved (relative to baml_src/) @@ -337,7 +337,7 @@ generator target {{ // your choice. You can have multiple generators if you use multiple languages. // Just ensure that the output_dir is different for each generator. generator target {{ - // Valid values: "python/pydantic", "typescript", "ruby/sorbet", "rest/openapi" + // Valid values: "python/pydantic", "typescript", "rest/openapi" output_type "rest/openapi" // Where the generated code will be saved (relative to baml_src/) @@ -366,7 +366,7 @@ generator target {{ // your choice. You can have multiple generators if you use multiple languages. // Just ensure that the output_dir is different for each generator. generator target {{ - // Valid values: "python/pydantic", "typescript", "ruby/sorbet", "rest/openapi" + // Valid values: "python/pydantic", "typescript", "rest/openapi" output_type "rest/openapi" // Where the generated code will be saved (relative to baml_src/) diff --git a/typescript/vscode-ext/packages/README.md b/typescript/vscode-ext/packages/README.md index 404b03f7d..9011667b8 100644 --- a/typescript/vscode-ext/packages/README.md +++ b/typescript/vscode-ext/packages/README.md @@ -1,48 +1,30 @@ -# Baml Language VS Code Extension +# BAML Language VSCode Extension -This VS Code extension provides support for the Baml language used to define LLM functions, test them in the integrated LLM Playground and build agentic workflows. +Provides integrated support for BAML, an expressive language for structured text generation. -### General features +Please check out our [documentation] for the most up-to-date information. -1. **Syntax Highlighting**: Provides enhanced readability and coding experience by highlighting the Baml language syntax for any file with the `.baml` extension. -2. **Dynamic playground**: Run and test your prompts in real-time. -3. **Build typed clients in several languages**: Command +S a baml file to build a baml client to call your functions in Python or TS. +To provide feedback, please join our [Discord], file an issue on [Github], or +any [other support channel](https://docs.boundaryml.com/contact). -## Usage - -1. **Install BAML dependency**: - -- python: `pip install baml-py` -- typescript: `npm install @boundaryml/baml` -- ruby: `bundle init && bundle add baml sorbet-runtime` - -2. **Create a baml_src directory with a main.baml file and you're all set!** +[Discord]: https://discord.gg/BTNBeXGuaS +[Github]: https://github.com/BoundaryML/baml/issues - Or you can try our `init` script to get an example directory setup for you: +### Features -```bash Python -# If using your local installation, venv or conda: -pip install baml-py -baml-cli init -``` +- **Syntax Highlighting**: a cornerstone of any programming language +- **Playground**: preview your prompt in real-time as you edit your prompts +- **Code Lenses**: press "▶ Open Playground" or "▶ Run Test" from any BAML file +- **100% local**: your prompts and data stay on your machine +- **Statically typed templates**: get compile-time errors for prompt template expansion +- **Integrated support for any programming language**: we auto-generate native bindings for Python and TypeScript, and expose an HTTP/RESTful interface with built-in OpenAPI support for all other languages -```bash TypeScript -# If using npm: -npm install @boundaryml/baml -npm run baml-cli init -``` - -```bash Ruby -bundle add baml -bundle exec baml-cli init -``` - -3. **Add your own api keys in the playground (settings icon) to test your functions** +## Usage -4. See more examples at \*\*[promptfiddle.com](promptfiddle.com) +To get started, choose the relevant quickstart guide in our [documentation]. -## Documentation +**Add your own API keys in the playground (settings icon) to test your functions** -See our [documentation](https://docs.boundaryml.com) +Check out more examples at [promptfiddle.com](https://promptfiddle.com) -For any issues, feature requests, or contributions, please reach out at contact@boundaryml.com +[documentation]: https://docs.boundaryml.com From b74ef15fd4dc09ecc7d1ac8284e7f22cd6d5864c Mon Sep 17 00:00:00 2001 From: Samuel Lijin Date: Fri, 13 Sep 2024 14:03:54 -0700 Subject: [PATCH 7/7] feat: use better default for openapi/rust client (#958) The OpenAPI-generated Rust client by default wraps types in `Box`, which can get annoying pretty fast, in everything from `.into()` or `Box::new` on object construction to having to do `.as_ref()` on every `match` construct. Setting `avoidBoxedModels=true` makes this behavior much more sane. --- engine/baml-runtime/src/cli/init.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/baml-runtime/src/cli/init.rs b/engine/baml-runtime/src/cli/init.rs index ef5bae29f..9149f412e 100644 --- a/engine/baml-runtime/src/cli/init.rs +++ b/engine/baml-runtime/src/cli/init.rs @@ -149,7 +149,7 @@ fn generate_main_baml_content( "{cmd} --additional-properties gemName=baml_client", ), Some("rust") => format!( - "{cmd} --additional-properties packageName=baml-client", + "{cmd} --additional-properties packageName=baml-client,avoidBoxedModels=true", ), _ => cmd, };