Skip to content

Commit 1c5b99a

Browse files
committed
Fix streaming and its codegen
1 parent 343318a commit 1c5b99a

File tree

5 files changed

+180
-141
lines changed

5 files changed

+180
-141
lines changed

README.md

Lines changed: 46 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,29 @@
11
# BamlElixir
22

3-
Call BAML functions from Elixir.
4-
Uses the BAML Rust NIF to call the BAML library.
3+
Call BAML functions from Elixir, using a Rust NIF.
4+
5+
## First of all, can this be used in production?
6+
7+
Well, I use it in production. But it's way too early for you if you expect stable APIs
8+
and things to not break at all. If you're okay with debugging issues with me when things go wrong,
9+
please go ahead!
510

611
What this library does:
712

8-
- Call functions in BAML files.
13+
- Generates Elixir structs, types and functions from BAML files.
14+
- Gives you autocomplete and dialyzer type checking.
15+
- Parses BAML results into Elixir structs.
916
- Switch between different LLM clients.
1017
- Get usage data using collectors.
1118

1219
What this library does not do:
1320

14-
- Code generation of Elixir `baml_client` from BAML files.
21+
- Generate Elixir `baml_client` files from BAML files. Codegen happens at compile time.
1522
- Automatically parse BAML results into Elixir structs.
1623

1724
## Usage
1825

19-
First add a BAML file in the `priv` directory.
26+
Create a baml_src directory in priv and add a BAML file in there:
2027

2128
```baml
2229
client GPT4 {
@@ -53,98 +60,86 @@ function ExtractResume(resume: string) -> Resume {
5360
}
5461
```
5562

56-
Now call the BAML function:
63+
Now create a BAML client module:
5764

5865
```elixir
59-
BamlElixir.Client.call("ExtractResume", %{resume: "John Doe is the CTO of Acme Inc."}, %{
60-
path: "priv/baml_src"
61-
})
66+
defmodule MyApp.BamlClient do
67+
use BamlElixir.Client, path: "priv/baml_src"
68+
end
6269
```
6370

64-
### Stream results
71+
Now call the BAML function:
6572

6673
```elixir
67-
BamlElixir.Client.stream!("ExtractResume", %{resume: "John Doe is the CTO of Acme Inc."}, %{
68-
path: "priv/baml_src"
69-
})
70-
|> Enum.each(&IO.inspect/1)
74+
MyApp.BamlClient.ExtractResume.call(%{resume: "John Doe is the CTO of Acme Inc."})
7175
```
7276

73-
#### Parsing results
74-
75-
If BAML returns a class type, you will get a map with keys as atoms and a special key `__baml_class__` with the BAML class name.
76-
77-
Example:
77+
### Stream results
7878

7979
```elixir
80-
%{
81-
__baml_class__: "Resume",
82-
name: "John Doe",
83-
job_title: "CTO",
84-
company: %{
85-
__baml_class__: "Company",
86-
name: "Acme Inc."
87-
}
88-
}
89-
```
80+
MyApp.BamlClient.ExtractResume.stream(%{resume: "John Doe is the CTO of Acme Inc."}, fn
81+
{:ok, result} ->
82+
IO.inspect(result)
9083

91-
If BAML returns an enum type, you will get a map two special keys: `__baml_enum__` with the BAML enum name and `value` with the enum value.
84+
{:error, error} ->
85+
IO.inspect(error)
9286

93-
Example:
94-
95-
```elixir
96-
%{
97-
__baml_enum__: "Color",
98-
value: "Red"
99-
}
87+
:done ->
88+
IO.inspect("Done")
89+
end)
10090
```
10191

10292
### Images
10393

10494
Send an image URL:
10595

10696
```elixir
107-
BamlElixir.Client.call("DescribeImage", %{
97+
MyApp.BamlClient.DescribeImage.call(%{
10898
myImg: %{
109-
"url" => "https://upload.wikimedia.org/wikipedia/en/4/4d/Shrek_%28character%29.png"
99+
url: "https://upload.wikimedia.org/wikipedia/en/4/4d/Shrek_%28character%29.png"
110100
}
111101
})
102+
|> IO.inspect()
112103
```
113104

114105
Or send base64 encoded image data:
115106

116107
```elixir
117-
BamlElixir.Client.call("DescribeImage", %{
108+
MyApp.BamlClient.DescribeImage.stream(%{
118109
myImg: %{
119-
"base64" => "data:image/png;base64,..."
110+
base64: "data:image/png;base64,..."
120111
}
121-
})
112+
}, fn result ->
113+
IO.inspect(result)
114+
end)
122115
```
123116

124117
### Collect usage data
125118

126119
```elixir
127120
collector = BamlElixir.Collector.new("my_collector")
128121

129-
BamlElixir.Client.call("ExtractResume", %{resume: "John Doe is the CTO of Acme Inc."}, %{
122+
MyApp.BamlClient.ExtractResume.call(%{resume: "John Doe is the CTO of Acme Inc."}, %{
130123
collectors: [collector]
131124
})
132125

133126
BamlElixir.Collector.usage(collector)
134127
```
135128

129+
When streaming, you can get the usage after :done message is received.
130+
136131
### Switch LLM clients
137132

138133
From the existing list of LLM clients, you can switch to a different one by calling `Client.use_llm_client/2`.
139134

140135
```elixir
141-
BamlElixir.Client.call("WhichModel", %{}, %{
136+
MyApp.BamlClient.WhichModel.call(%{}, %{
142137
llm_client: "GPT4oMini"
143138
})
144139
|> IO.inspect()
145140
# => "gpt-4o-mini"
146141

147-
BamlElixir.Client.call("WhichModel", %{}, %{
142+
MyApp.BamlClient.WhichModel.call(%{}, %{
148143
llm_client: "DeepSeekR1"
149144
})
150145
|> IO.inspect()
@@ -170,6 +165,12 @@ This also downloads the pre built NIFs for these targets:
170165

171166
If you need to build the NIFs for other targets, you need to clone the repo and build it locally as documented below.
172167

168+
### TODO
169+
170+
- Type aliases
171+
- Dynamic types
172+
- Partial types for streaming
173+
173174
### Development
174175

175176
This project includes Git submodules. To clone the repository with all its submodules, use:

0 commit comments

Comments
 (0)