Skip to content

Commit 1abd847

Browse files
committed
ChainOfThought + Groq
1 parent 0db54d0 commit 1abd847

File tree

10 files changed

+501
-22
lines changed

10 files changed

+501
-22
lines changed

.formatter.exs

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
# Used by "mix format"
22
[
3-
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
3+
import_deps: [:ecto, :phoenix, :phoenix_live_view],
4+
plugins: [Phoenix.LiveView.HTMLFormatter],
5+
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}", "pages/cookbook/**/*.{ex,exs}"]
46
]

README.md

+2-17
Original file line numberDiff line numberDiff line change
@@ -123,26 +123,12 @@ end
123123

124124
## TODO
125125

126-
- [x] Top-level array support
127-
- [x] Gemini
128-
- [x] tools mode
129-
- [ ] json mode
130-
- [x] json_schema mode
131-
- [x] Figure out a solution for OpenAI's json_schema mode not supporting arbitrary maps.
132126
- [ ] Partial Schemaless doesn't work since fields are set to required in Ecto.
133-
134-
135-
- [ ] llamacpp adapter broken, needs to support openai input/output API
136-
- [ ] GBNF should enforce required properties on objects, currently they're optional.
137-
- [ ] GBNF limit the number of digits in number tokens -- small models can sometimes run off to infinit digits
138-
- [ ] Add instructor tests against llamacpp interface using mocks, there's non-trivial logic in there
127+
- [ ] Groq adapter
128+
- [ ] ChainOfThought doesn't work with max_retries
139129
- [ ] Logging for Distillation / Finetuning
140130
- [ ] Add a Bumblebee adapter
141-
- [ ] Add llamacpp_ex adapter
142131
- [ ] Support naked ecto types by auto-wrapping, not just maps of ecto types, do not wrap if we don't need to... Current codepaths are muddled
143-
- [x] Support Streaming
144-
- [ ] Verify schemaless support `{:array, %{name: :string}}`
145-
- [ ] Support typespec style support for array streaming `[MySchema]`
146132
- [ ] Optional/Maybe types
147133
- [ ] Add Livebook Tutorials, include in Hexdocs
148134
- [x] Text Classification
@@ -160,7 +146,6 @@ end
160146
- [ ] Multi-File Code Generation
161147
- [ ] PII Data Sanitizatiommersed
162148
- [x] Update hexdocs homepage to include example for tutorial
163-
- [ ] Setup Github CI for testing, add badge to README
164149

165150
## Blog Posts
166151

lib/instructor/adapters/groq.ex

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
defmodule Instructor.Adapters.Groq do
2+
@moduledoc """
3+
Adapter for Groq Cloud API. Using the OpenAI API compatible endpoint.
4+
"""
5+
alias Instructor.Adapters
6+
7+
@behaviour Instructor.Adapter
8+
@supported_modes [:tools]
9+
10+
@impl true
11+
def chat_completion(params, user_config \\ nil) do
12+
config = config(user_config)
13+
mode = params[:mode]
14+
15+
if mode not in @supported_modes do
16+
raise "Unsupported mode #{mode} for Groq"
17+
end
18+
19+
Adapters.OpenAI.chat_completion(params, config)
20+
end
21+
22+
@impl true
23+
defdelegate reask_messages(raw_response, params, config), to: Adapters.OpenAI
24+
25+
defp config(nil), do: config(Application.get_env(:instructor, :groq, []))
26+
27+
defp config(base_config) do
28+
default_config = [
29+
api_url: "https://api.groq.com/openai",
30+
http_options: [receive_timeout: 60_000]
31+
]
32+
33+
Keyword.merge(default_config, base_config)
34+
end
35+
end

lib/instructor/adapters/openai.ex

+8-3
Original file line numberDiff line numberDiff line change
@@ -180,10 +180,15 @@ defmodule Instructor.Adapters.OpenAI do
180180

181181
defp parse_stream_chunk_for_mode(:tools, %{
182182
"choices" => [
183-
%{"delta" => %{"content" => chunk}}
183+
%{"delta" => delta}
184184
]
185-
}),
186-
do: chunk
185+
}) do
186+
case delta do
187+
nil -> ""
188+
%{} -> ""
189+
%{"content" => chunk} -> chunk
190+
end
191+
end
187192

188193
defp parse_stream_chunk_for_mode(_, %{"choices" => [%{"finish_reason" => "stop"}]}), do: ""
189194

+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
defmodule Instructor.Extras.ChainOfThought do
2+
defmodule ReasoningStep do
3+
use Ecto.Schema
4+
5+
@doc """
6+
For each step, provide a title that describes what you're doing in that step, along with the content.
7+
Decide if you need another step or if you're ready to give the final answer.
8+
Respond in JSON format with 'title', 'content', and 'next_action' (either 'continue' or 'final_answer') keys.
9+
"""
10+
@primary_key false
11+
embedded_schema do
12+
field(:title, :string)
13+
field(:content, :string)
14+
field(:next_action, Ecto.Enum, values: [:final_answer, :continue])
15+
end
16+
end
17+
18+
def chat_completion(params, config \\ nil) do
19+
reasoning_steps = Keyword.pop(params, :reasoning_steps, 3)
20+
response_model = params[:response_model]
21+
22+
initial_messages =
23+
[
24+
%{
25+
role: "system",
26+
content: """
27+
You are an expert AI assistant that explains your reasoning step by step.
28+
For each step, provide a title that describes what you're doing in that step, along with the content.
29+
Decide if you need another step or if you're ready to give the final answer.
30+
Respond in JSON format with 'title', 'content', and 'next_action' (either 'continue' or 'final_answer') keys.
31+
USE AS MANY REASONING STEPS AS POSSIBLE.
32+
AT LEAST 3.
33+
# ... (rest of the system message)
34+
"""
35+
}
36+
] ++
37+
params[:messages] ++
38+
[
39+
%{
40+
role: "assistant",
41+
content: """
42+
Thank you! I will now think step by step following my instructions, starting at the beginning after decomposing the problem.
43+
"""
44+
}
45+
]
46+
47+
params = Keyword.put(params, :messages, initial_messages)
48+
params = Keyword.put(params, :response_model, ReasoningStep)
49+
50+
Stream.resource(
51+
fn -> {params, 0} end,
52+
fn
53+
:halt ->
54+
{:halt, nil}
55+
56+
{:final_answer, params} ->
57+
new_messages =
58+
params[:messages] ++
59+
[
60+
%{
61+
role: "user",
62+
content: """
63+
Please provide the final answer based solely on your reasoning above.
64+
Only provide the text response without any titles or preambles.
65+
Retain any formatting as instructed by the original prompt, such as exact formatting for free response or multiple choice.
66+
"""
67+
}
68+
]
69+
70+
params = Keyword.put(params, :messages, new_messages)
71+
params = Keyword.put(params, :response_model, response_model)
72+
{:ok, final_answer} = Instructor.chat_completion(params, config)
73+
{[{:final_answer, final_answer}], :halt}
74+
75+
{params, step_count} ->
76+
case Instructor.chat_completion(params, config) do
77+
{:ok, %ReasoningStep{} = step} ->
78+
new_messages =
79+
params[:messages] ++
80+
[
81+
%{
82+
role: "assistant",
83+
content: step |> Map.from_struct() |> Jason.encode!()
84+
}
85+
]
86+
87+
params = Keyword.put(params, :messages, new_messages)
88+
89+
acc =
90+
case step.next_action do
91+
:final_answer ->
92+
{:final_answer, params}
93+
94+
:continue ->
95+
{params, step_count + 1}
96+
end
97+
98+
{[step], acc}
99+
100+
{:error, reason} ->
101+
IO.inspect(reason, label: "ERROR")
102+
{:halt, {params, step_count}}
103+
end
104+
end,
105+
fn _ -> nil end
106+
)
107+
|> Stream.transform(0, fn
108+
{:final_answer, final_answer}, _step_count ->
109+
{[final_answer], :halt}
110+
111+
step, step_count when step_count < reasoning_steps ->
112+
{[step], step_count + 1}
113+
114+
_step, _step_count ->
115+
{[{:error, "No final answer within #{reasoning_steps} reasoning steps"}], :halt}
116+
end)
117+
end
118+
end

lib/instructor/sse_stream_parser.ex

+1
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,6 @@ defmodule Instructor.SSEStreamParser do
1616
Jason.decode!(json_string)
1717
end)
1818
end)
19+
# |> Stream.each(&IO.inspect/1)
1920
end
2021
end

mix.exs

+3-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,9 @@ defmodule Instructor.MixProject do
116116
{:req, "~> 0.5 or ~> 1.0"},
117117
{:jaxon, "~> 2.0"},
118118
{:ex_doc, "~> 0.31", only: :dev, runtime: false},
119-
{:mox, "~> 1.1.0", only: :test}
119+
{:mox, "~> 1.1.0", only: :test},
120+
{:phoenix, "~> 1.7", only: :test},
121+
{:phoenix_live_view, "~> 0.20.17", only: :test}
120122
]
121123
end
122124
end

mix.lock

+9
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@
1818
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
1919
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
2020
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
21+
"phoenix": {:hex, :phoenix, "1.7.14", "a7d0b3f1bc95987044ddada111e77bd7f75646a08518942c72a8440278ae7825", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "c7859bc56cc5dfef19ecfc240775dae358cbaa530231118a9e014df392ace61a"},
22+
"phoenix_html": {:hex, :phoenix_html, "4.1.1", "4c064fd3873d12ebb1388425a8f2a19348cef56e7289e1998e2d2fa758aa982e", [:mix], [], "hexpm", "f2f2df5a72bc9a2f510b21497fd7d2b86d932ec0598f0210fed4114adc546c6f"},
23+
"phoenix_live_view": {:hex, :phoenix_live_view, "0.20.17", "f396bbdaf4ba227b82251eb75ac0afa6b3da5e509bc0d030206374237dfc9450", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a61d741ffb78c85fdbca0de084da6a48f8ceb5261a79165b5a0b59e5f65ce98b"},
24+
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"},
25+
"phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
26+
"plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"},
27+
"plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
2128
"req": {:hex, :req, "0.5.0", "6d8a77c25cfc03e06a439fb12ffb51beade53e3fe0e2c5e362899a18b50298b3", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "dda04878c1396eebbfdec6db6f3d4ca609e5c8846b7ee88cc56eb9891406f7a3"},
2229
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
30+
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
31+
"websock_adapter": {:hex, :websock_adapter, "0.5.7", "65fa74042530064ef0570b75b43f5c49bb8b235d6515671b3d250022cb8a1f9e", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "d0f478ee64deddfec64b800673fd6e0c8888b079d9f3444dd96d2a98383bdbd1"},
2332
}

0 commit comments

Comments
 (0)