Skip to content

Commit

Permalink
Add docs for Gemini adapter
Browse files Browse the repository at this point in the history
  • Loading branch information
martosaur committed Oct 21, 2024
1 parent eb4c683 commit a9ac4bf
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 4 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

* Add Gemini adapter

## v0.2.0

* [OpenAI] Do not overwrite `response_format` params key if provided by user
Expand Down
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,26 @@ iex> InstructorLite.instruct(%{
{:ok, %UserInfo{name: "John Doe", age: 42}}
```

### Gemini

```elixir
iex> InstructorLite.instruct(%{
contents: [
%{
role: "user",
parts: [%{text: "John Doe is fourty two years old"}]
}
]
},
response_model: UserInfo,
adapter: InstructorLite.Adapters.Gemini,
adapter_context: [
api_key: Application.fetch_env!(:instructor_lite, :gemini_key)
]
)
{:ok, %UserInfo{name: "John Doe", age: 42}}
```

<!-- tabs-close -->

## Configuration
Expand Down
3 changes: 2 additions & 1 deletion config/config.example.exs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ import Config
config :instructor_lite,
openai_key: "api_key",
anthropic_key: "api_key",
llamacpp_url: "http://localhost:8000/completion"
llamacpp_url: "http://localhost:8000/completion",
gemini_key: "api_key"
25 changes: 25 additions & 0 deletions lib/instructor_lite.ex
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,31 @@ defmodule InstructorLite do
{:ok, %{name: "John Doe", age: 42}}
```
### Gemini
```elixir
iex> InstructorLite.instruct(%{
contents: [
%{
role: "user",
parts: [%{text: "John Doe is fourty two years old"}]
}
]
},
response_model: %{name: :string, age: :integer},
json_schema: %{
type: "object",
required: [:age, :name],
properties: %{name: %{type: "string"}, age: %{type: "integer"}}
},
adapter: InstructorLite.Adapters.Gemini,
adapter_context: [
api_key: Application.fetch_env!(:instructor_lite, :gemini_key)
]
)
{:ok, %{name: "John Doe", age: 42}}
```
<!-- tabs-close -->
### Using `max_retries`
Expand Down
57 changes: 56 additions & 1 deletion lib/instructor_lite/adapters/gemini.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,39 @@
defmodule InstructorLite.Adapters.Gemini do
@moduledoc """
[Gemini](https://ai.google.dev/gemini-api) adapter.
This adapter is implemented using [Text generation](https://ai.google.dev/gemini-api/docs/text-generation) endpoint configured for [structured output](https://ai.google.dev/gemini-api/docs/structured-output?lang=rest#supply-schema-in-config)
## Params
`params` argument should be shaped as a [`models.GenerateBody` request body](https://ai.google.dev/api/generate-content#request-body)
## Example
```
InstructorLite.instruct(
%{contents: [%{role: "user", parts: [%{text: "John is 25yo"}]}]},
response_model: %{name: :string, age: :integer},
json_schema: %{
type: "object",
required: [:age, :name],
properties: %{name: %{type: "string"}, age: %{type: "integer"}}
},
adapter: InstructorLite.Adapters.Gemini,
adapter_context: [
model: "gemini-1.5-flash-8b",
api_key: Application.fetch_env!(:instructor_lite, :gemini_key)
]
)
{:ok, %{name: "John", age: 25}}
```
> #### Specifying model {: .tip}
>
> Note how, unlike other adapters, the Gemini adapter expects `model` under `adapter_context`.
> #### JSON Schema {: .warning}
>
> Gemini's idea of JSON Schema is [quite different](https://ai.google.dev/api/generate-content#generationconfig) from other major models, so `InstructorLite.JSONSchema` won't help you even for simple cases. Luckily, the Gemini API provides detailed errors for invalid schemas.
"""
@behaviour InstructorLite.Adapter
Expand Down Expand Up @@ -34,6 +68,13 @@ defmodule InstructorLite.Adapters.Gemini do
]
)

@doc """
Make request to Gemini API
## Options
#{NimbleOptions.docs(@send_request_schema)}
"""
@impl InstructorLite.Adapter
def send_request(params, opts) do
context =
Expand All @@ -59,6 +100,9 @@ defmodule InstructorLite.Adapters.Gemini do
end
end

@doc """
Puts `systemInstruction` and updates `generationConfig` in `params` with prompt based on `json_schema` and `notes`.
"""
@impl InstructorLite.Adapter
def initial_prompt(params, opts) do
mandatory_part = """
Expand Down Expand Up @@ -93,6 +137,9 @@ defmodule InstructorLite.Adapters.Gemini do
end)
end

@doc """
Updates `params` with prompt for retrying a request.
"""
@impl InstructorLite.Adapter
def retry_prompt(params, resp_params, errors, _response, _opts) do
do_better = [
Expand All @@ -114,13 +161,21 @@ defmodule InstructorLite.Adapters.Gemini do
Map.update(params, :contents, do_better, fn contents -> contents ++ do_better end)
end

@doc """
Parse text generation endpoint response.
Can return:
* `{:ok, parsed_json}` on success.
* `{:error, :refusal, prompt_feedback}` if [request was blocked](https://ai.google.dev/api/generate-content#generatecontentresponse).
* `{:error, :unexpected_response, response}` if response is of unexpected shape.
"""
@impl InstructorLite.Adapter
def parse_response(response, _opts) do
case response do
%{"candidates" => [%{"content" => %{"parts" => [%{"text" => text}]}}]} ->
Jason.decode(text)

%{"promptFeedback" => %{"blockReason" => reason}} ->
%{"promptFeedback" => %{"blockReason" => _} = reason} ->
{:error, :refusal, reason}

other ->
Expand Down
3 changes: 2 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ defmodule InstructorLite.MixProject do
Adapters: [
InstructorLite.Adapters.Anthropic,
InstructorLite.Adapters.OpenAI,
InstructorLite.Adapters.Llamacpp
InstructorLite.Adapters.Llamacpp,
InstructorLite.Adapters.Gemini
]
],
groups_for_extras: [
Expand Down
2 changes: 1 addition & 1 deletion test/instructor_lite/adapters/gemini_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ defmodule InstructorLite.Adapters.GeminiTest do
}
}

assert {:error, :refusal, "OTHER"} =
assert {:error, :refusal, %{"blockReason" => "OTHER"}} =
Gemini.parse_response(response, [])
end

Expand Down

0 comments on commit a9ac4bf

Please sign in to comment.