dvcrn/ex_openai

Dializer error for what looks like valid call

Opened this issue · 8 comments

I'm getting this dialyzer error:

The function call will not succeed.

ExOpenAI.Chat.create_chat_completion(
  _messages :: [%{:content => _, :role => :user}, ...],
  <<103, 112, 116, 45, 51, 46, 53, 45, 116, 117, 114, 98, 111>>,
  _opts :: [{:max_tokens, _} | {:temperature, 0}, ...]
)

breaks the contract
([ExOpenAI.Components.ChatCompletionRequestMessage.t()], any(), [
  {:openai_organization_key, String.t()}
  | {:openai_api_key, String.t()}
  | {:user, String.t()}
  | {:top_p, float()}
  | {:temperature, float()}
  | {:stream, boolean()}
  | {:stop, any()}
  | {:presence_penalty, float()}
  | {:n, integer()}
  | {:max_tokens, integer()}
  | {:logit_bias, map()}
  | {:functions, [ExOpenAI.Components.ChatCompletionFunctions.t()]}
  | {:function_call, any()}
  | {:frequency_penalty, float()}
  | {:stream_to, pid()}
]) :: {:ok, ExOpenAI.Components.CreateChatCompletionResponse.t()} | {:error, any()

For this function call:

  def create_completion(prompt, max_tokens) do
    messages = [
      %{role: :user, content: prompt}
    ]

    opts = [max_tokens: max_tokens, temperature: 0]

    ExOpenAI.Chat.create_chat_completion(messages, "gpt-3.5-turbo", opts)
    |> case do
      {:ok, %{choices: [%{message: %{content: content}}]}} ->
        {:ok, content}

      {:error, %{error: %{message: message}}} ->
        {:error, message}
    end
  end

I've tried:

  • sending an explicit int in max_tokens
  • sending an explicit float in temperature
  • converting the messages to ChatCompletionRequestMessage (which raises a Jason decoder error)

I'm not sure what's causing this, but I'm pretty close to the documented example.

Great library BTW, I'm much happier with it than any of the other implementations I've tried (which is about 4 at this point).

dvcrn commented

Hey!

While the data you pass in is correct, Dialyzer expects a %ExOpenAI.Components.ChatCompletionRequestMessage struct to pass into ExOpenAI.Chat.create_chat_completion, so if you change the function to

  def create_completion(prompt, max_tokens) do
    messages = [
      %ExOpenAI.Components.ChatCompletionRequestMessage{role: :user, content: prompt}
    ]

    opts = [max_tokens: max_tokens, temperature: 0]

    ExOpenAI.Chat.create_chat_completion(messages, "gpt-3.5-turbo", opts)
    |> case do
      {:ok, %{choices: [%{message: %{content: content}}]}} ->
        {:ok, content}

      {:error, %{error: %{message: message}}} ->
        {:error, message}
    end
  end

the complaining should go away. Checked in iex for Jason decoder errors:

iex(3)> defmodule Foo do
...(3)>   def create_completion(prompt, max_tokens) do
...(3)>     messages = [
...(3)>       %ExOpenAI.Components.ChatCompletionRequestMessage{role: :user, content: prompt}
...(3)>     ]
...(3)> 
...(3)>     opts = [max_tokens: max_tokens, temperature: 0]
...(3)> 
...(3)>     ExOpenAI.Chat.create_chat_completion(messages, "gpt-3.5-turbo", opts)
...(3)>     |> case do
...(3)>       {:ok, %{choices: [%{message: %{content: content}}]}} ->
...(3)>         {:ok, content}
...(3)> 
...(3)>       {:error, %{error: %{message: message}}} ->
...(3)>         {:error, message}
...(3)>     end
...(3)>   end
...(3)> end
{:module, Foo,
 <<70, 79, 82, 49, 0, 0, 8, 148, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 1, 11,
   0, 0, 0, 25, 10, 69, 108, 105, 120, 105, 114, 46, 70, 111, 111, 8, 95, 95,
   105, 110, 102, 111, 95, 95, 10, 97, 116, ...>>, {:create_completion, 2}}
iex(4)> Foo.create_completion "hello world", 1024
%{
  max_tokens: 1024,
  messages: [
    %ExOpenAI.Components.ChatCompletionRequestMessage{
      role: :user,
      content: "hello world",
      function_call: nil,
      name: nil
    }
  ],
  model: "gpt-3.5-turbo",
  temperature: 0
}
{:ok, "Hello! How can I assist you today?"}

Nice dude.

Here's what I get:

** (CaseClauseError) no case clause matching: 
%Protocol.UndefinedError{
  protocol: Jason.Encoder, 
  value: %ExOpenAI.Components.ChatCompletionRequestMessage{role: :user, content: "\n          Summarize the d...st relevant information.\n        ", 
  function_call: nil, name: nil}, 
  description: "Jason.Encoder protocol must always be explicitly implemented.\n\nIf you own the struct, you can derive the implementation specifying which fields should be encoded to JSON:\n\n    @derive {Jason.Encoder, only: [....]}\n    defstruct ...\n\nIt is also possible to encode all fields, although this should be used carefully to avoid accidentally leaking private information when new fields are added:\n\n    @derive Jason.Encoder\n    defstruct ...\n\nFinally, if you don't own the struct you want to encode to JSON, you may use Protocol.derive/3 placed outside of any module:\n\n    Protocol.derive(Jason.Encoder, NameOfTheStruct, only: [...])\n    Protocol.derive(Jason.Encoder, NameOfTheStruct)\n"}

Looks like I'm on the right version:

{:ex_openai, "~> 1.2.1"}

Configs:

config :ex_openai,
  api_key: System.get_env("OPENAI_API_KEY"),
  http_options: [recv_timeout: 10 * 60 * 1000]
dvcrn commented

I can't reproduce this. Have you recently upgraded? In that case you may need to clean your deps and recompile everything

I've created a new project from scratch, added 1.2.1 as deps, copied over my my envrc and config.exs for API key setup and ran the code you provided:

/tmp
❯ mix new openaitest
* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating lib
* creating lib/openaitest.ex
* creating test
* creating test/test_helper.exs
* creating test/openaitest_test.exs

Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:

    cd openaitest
    mix test

Run "mix help" for more commands.

/tmp
❯ cd openaitest/

/tmp/openaitest is 📦 v0.1.0
❯ ll
total 16
-rw-r--r--@ 1 david  wheel   481B Aug 21 11:08 README.md
drwxr-xr-x@ 3 david  wheel    96B Aug 21 11:08 lib/
-rw-r--r--@ 1 david  wheel   578B Aug 21 11:08 mix.exs
drwxr-xr-x@ 4 david  wheel   128B Aug 21 11:08 test/

/tmp/openaitest is 📦 v0.1.0
❯ mix deps.get
Resolving Hex dependencies...
Resolution completed in 2.321s
New:
  certifi 2.9.0
  ex_openai 1.2.1
  file_system 0.2.10
  hackney 1.18.1
  httpoison 2.1.0
  idna 6.1.1
  jason 1.4.1
  metrics 1.0.1
  mimerl 1.2.0
  mix_test_watch 1.1.0
  parse_trans 3.3.1
  ssl_verify_fun 1.1.7
  unicode_util_compat 0.7.0
  yamerl 0.10.0
  yaml_elixir 2.9.0
* Getting ex_openai (Hex package)
* Getting httpoison (Hex package)
* Getting jason (Hex package)
* Getting mix_test_watch (Hex package)
* Getting yaml_elixir (Hex package)
* Getting yamerl (Hex package)
* Getting file_system (Hex package)
* Getting hackney (Hex package)
* Getting certifi (Hex package)
* Getting idna (Hex package)
* Getting metrics (Hex package)
* Getting mimerl (Hex package)
* Getting parse_trans (Hex package)
* Getting ssl_verify_fun (Hex package)
* Getting unicode_util_compat (Hex package)

/tmp/openaitest is 📦 v0.1.0 took 10s
❯ direnv allow
direnv: loading /private/tmp/openaitest/.envrc
direnv: export +OPENAI_API_KEY +OPENAI_ORGANIZATION_KEY ~XPC_SERVICE_NAME

/tmp/openaitest is 📦 v0.1.0
❯ iex -S mix
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] [dtrace]


11:09:07.542 [info] Compiling file system watcher for Mac...

11:09:09.538 [info] Done.
==> file_system
Compiling 7 files (.ex)
Generated file_system app
===> Analyzing applications...
===> Compiling unicode_util_compat
===> Analyzing applications...
===> Compiling idna
==> jason
Compiling 10 files (.ex)
Generated jason app
==> mix_test_watch
Compiling 8 files (.ex)
Generated mix_test_watch app
===> Analyzing applications...
===> Compiling yamerl
==> yaml_elixir
Compiling 6 files (.ex)
Generated yaml_elixir app
===> Analyzing applications...
===> Compiling mimerl
==> ssl_verify_fun
Compiling 7 files (.erl)
Generated ssl_verify_fun app
===> Analyzing applications...
===> Compiling certifi
===> Analyzing applications...
===> Compiling parse_trans
===> Analyzing applications...
===> Compiling metrics
===> Analyzing applications...
===> Compiling hackney
==> httpoison
Compiling 3 files (.ex)
Generated httpoison app
==> ex_openai
Compiling 8 files (.ex)
Generated ex_openai app
==> openaitest
Compiling 1 file (.ex)
Generated openaitest app

Interactive Elixir (1.14.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>  Openaitest.create_completion "hello world", 1024
{:ok, "Hello! How can I assist you today?"}

config.exs:

import Config

config :ex_openai,
  # find it at https://platform.openai.com/account/api-keys
  api_key: System.get_env("OPENAI_API_KEY"),
  # find it at https://platform.openai.com/account/api-keys
  organization_key: System.get_env("OPENAI_ORGANIZATION_KEY"),
  # optional, passed to [HTTPoison.Request](https://hexdocs.pm/httpoison/HTTPoison.Request.html) options
  http_options: [recv_timeout: 50_000]

Can you provide me the code/project you're using?

In your test project, I don't think you tried to create a chat completion with that struct?

I tried your suggestion, and cleaned + recompiled the product = no love.

I can't share the project right now, it's a private repo.

dvcrn commented

In your test project, I don't think you tried to create a chat completion with that struct?

I did, see the lines here:

Interactive Elixir (1.14.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>  Openaitest.create_completion "hello world", 1024
{:ok, "Hello! How can I assist you today?"}

This is very odd. Let me do some poking around here and see if I can somehow reproduce this....

If you have time, could you clone this repo, setup your API key, remove the VCR cassette for the chat completion test and re-run mix test?

Jason stuff is implemented in

# Module to package all the protocol stuff for Jason away

And then used when components are generated here:

use ExOpenAI.Jason

So all ExOpenAI.Components.* should implement Jason just fine.

Another random idea, but are you using Jason in your project? If yes, which version are you pulling in? Could be a mismatch that’s causing some weirdness

Ah ok, I assume that

Openaitest.create_completion "hello world", 1024

Does something similar to my code sample.

{:jason, "~> 1.2"}

dvcrn commented

{:jason, "~> 1.2"}

Please run mix deps and see which version is getting pulled into your project. If you can, please also try updating jason to 1.4, the same version this repo is using.

(...But you didn't have any jason issues when you trialed the audio transcription stuff which is using the exact same jason implementing, so this shouldn't matter)

And try a manual Jason.encode in an iex -S mix session after running a mix deps.clean --all && mix deps.get:

 Jason.encode %ExOpenAI.Components.ChatCompletionRequestMessage{content: "Hello!", role: :user, name: nil}

Then, please also run this within your project iex session to check what things within ex_openai are implementing the Jason.Encoder protocol:

path = :code.lib_dir(:ex_openai, :ebin)
Protocol.extract_impls(Jason.Encoder, [path])

Output should be:

[ExOpenAI.Components.ImagesResponse,
 ExOpenAI.Components.CreateChatCompletionResponse,
 ExOpenAI.Components.CreateCompletionRequest,
 ExOpenAI.Components.ChatCompletionFunctions,
 ExOpenAI.Components.CreateEmbeddingRequest,
 ExOpenAI.Components.CreateChatCompletionRequest,
 ExOpenAI.Components.CreateEditResponse,
 ExOpenAI.Components.ChatCompletionFunctionParameters,
 ExOpenAI.Components.CreateCompletionResponse,
 ExOpenAI.Components.CreateFineTuneRequest,
 ExOpenAI.Components.CreateImageRequest, ExOpenAI.Components.CreateEditRequest,
 ExOpenAI.Components.CreateFileRequest,
 ExOpenAI.Components.ChatCompletionResponseMessage,
 ExOpenAI.Components.ChatCompletionRequestMessage,
 ExOpenAI.Components.CreateTranslationRequest,
 ExOpenAI.Components.CreateTranscriptionRequest, ExOpenAI.Components.FineTune,
 ExOpenAI.Components.DeleteFileResponse,
 ExOpenAI.Components.ListFineTunesResponse, ExOpenAI.Components.OpenAIFile,
 ExOpenAI.Components.CreateImageEditRequest,
 ExOpenAI.Components.ListFilesResponse,
 ExOpenAI.Components.CreateChatCompletionStreamResponse,
 ExOpenAI.Components.CreateTranscriptionResponse,
 ExOpenAI.Components.CreateModerationResponse,
 ExOpenAI.Components.CreateImageVariationRequest,
 ExOpenAI.Components.ListModelsResponse, ExOpenAI.Components.ErrorResponse,
 ExOpenAI.Components.ChatCompletionStreamResponseDelta,
 ExOpenAI.Components.CreateModerationRequest,
 ExOpenAI.Components.CreateEmbeddingResponse, ExOpenAI.Components.Model,
 ExOpenAI.Components.ListFineTuneEventsResponse, ExOpenAI.Components.Error,
 ExOpenAI.Components.DeleteModelResponse, ExOpenAI.Components.FineTuneEvent,
 ExOpenAI.Components.CreateTranslationResponse]