Current section

7 Versions

Jump to

Compare versions

7 files changed
+298 additions
-171 deletions
  @@ -1,9 +1,14 @@
1 1 # Changelog
2 2
3 - ## v0.1.0
3 + ## v0.2.0 (2024-01-16)
4 +
5 + - Add `chat/completions` endpoint.
6 + - Improve ExDocs for developer.
7 +
8 + ## v0.1.0 (2024-01-15)
4 9
5 10 - Add CHANGELOG.md
6 11 - Add `models` endpoint
7 12 - Add `Config` overwrite feature
8 13 - Add ex_docs
9 - - Add `Dependabot` and `c.yml`
\ No newline at end of file
14 + - Add `Dependabot` and `ci.yml`
\ No newline at end of file
  @@ -12,7 +12,7 @@ by adding `hipcall_openai` to your list of dependencies in `mix.exs`:
12 12 ```elixir
13 13 def deps do
14 14 [
15 - {:hipcall_openai, "~> 0.1.0"}
15 + {:hipcall_openai, "~> 0.2.0"}
16 16 ]
17 17 end
18 18 ```
  @@ -28,78 +28,23 @@ struct as last argument of the function you need to use. For instance if you nee
28 28 api_key you can simply do:
29 29
30 30 ```elixir
31 - config_override = %HipcallOpenai.Config{api_key: "mTRwVrbZ4aoHTyjMepleT3BlbkFJ7zZYazuN7F16XuY3WErl"}
31 + config_override = %HipcallOpenai.Config{
32 + api_key: "mTRwVrbZ4aoHTyjMepleT3BlbkFJ7zZYazuN7F16XuY3WErl",
33 + api_organization: "org-blalba"
34 + }
32 35 # pass the overriden configuration as last argument of the function
33 36 HipcallOpenai.models(config_override)
34 37 ```
35 38
36 39 ## Use
37 40
38 - ### Models
39 -
40 - #### List
41 -
42 - https://platform.openai.com/docs/api-reference/models
43 -
44 - ```elixir
45 - iex(1)> HipcallOpenai.models(config_override)
46 - {:ok,
47 - %{
48 - "data" => [
49 - %{
50 - "created" => 1686588896,
51 - "id" => "gpt-4-0613",
52 - "object" => "model",
53 - "owned_by" => "openai"
54 - },
55 - %{
56 - "created" => 1651172509,
57 - "id" => "curie-search-query",
58 - "object" => "model",
59 - "owned_by" => "openai-dev"
60 - },
61 - %{
62 - "created" => 1687882411,
63 - "id" => "gpt-4",
64 - "object" => "model",
65 - "owned_by" => "openai"
66 - },
67 - %{
68 - "created" => 1651172509,
69 - "id" => "babbage-search-query",
70 - "object" => "model",
71 - "owned_by" => "openai-dev"
72 - },
73 - %{
74 - "created" => 1698785189,
75 - "id" => "dall-e-3",
76 - "object" => "model",
77 - "owned_by" => "system"
78 - }
79 - ],
80 - "object" => "list"
81 - }}
82 - ```
83 -
84 - #### Retrieve
85 -
86 - https://platform.openai.com/docs/api-reference/models/retrieve
87 -
88 - ```elixir
89 - iex(1)> HipcallOpenai.model("gpt-3.5-turbo-instruct", config_override)
90 - {:ok,
91 - %{
92 - "created" => 1692901427,
93 - "id" => "gpt-3.5-turbo-instruct",
94 - "object" => "model",
95 - "owned_by" => "system"
96 - }}
97 - ```
41 + Documentation for using, please check the `HipcallOpenai` module.
98 42
99 43 ## Roadmap
100 44
45 + - [x] Add `Models` endpoint
46 + - [x] Add `Chat` endpoint
101 47 - [ ] Add `Audio` endpoints
102 - - [ ] Add `Chat` endpoint
103 48 - [ ] Add `Embeddings` endpoint
104 49 - [ ] Add `Fine-tuning` endpoint
105 50 - [ ] Add `Files` endpoint
  @@ -2,7 +2,7 @@
2 2 [{<<"GitHub">>,<<"https://github.com/hipcall/hipcall_openai">>},
3 3 {<<"Website">>,<<"https://www.hipcall.com/en-gb/">>}]}.
4 4 {<<"name">>,<<"hipcall_openai">>}.
5 - {<<"version">>,<<"0.1.0">>}.
5 + {<<"version">>,<<"0.2.0">>}.
6 6 {<<"description">>,<<"Unofficial OpenAI API Wrapper written in Elixir.">>}.
7 7 {<<"elixir">>,<<"~> 1.14">>}.
8 8 {<<"app">>,<<"hipcall_openai">>}.
  @@ -26,6 +26,7 @@
26 26 {<<"files">>,
27 27 [<<"lib">>,<<"lib/hipcall_openai">>,<<"lib/hipcall_openai/config.ex">>,
28 28 <<"lib/hipcall_openai/models.ex">>,<<"lib/hipcall_openai/application.ex">>,
29 - <<"lib/hipcall_openai.ex">>,<<".formatter.exs">>,<<"mix.exs">>,
30 - <<"README.md">>,<<"LICENSE.md">>,<<"CHANGELOG.md">>]}.
29 + <<"lib/hipcall_openai/chat.ex">>,<<"lib/hipcall_openai.ex">>,
30 + <<".formatter.exs">>,<<"mix.exs">>,<<"README.md">>,<<"LICENSE.md">>,
31 + <<"CHANGELOG.md">>]}.
31 32 {<<"build_tools">>,[<<"mix">>]}.
  @@ -2,12 +2,244 @@ defmodule HipcallOpenai do
2 2 @moduledoc """
3 3 Documentation for `HipcallOpenai`.
4 4 """
5 + alias HipcallOpenai.Chat
5 6 alias HipcallOpenai.Config
6 7 alias HipcallOpenai.Models
7 8
8 - # https://platform.openai.com/docs/api-reference/models/
9 - def models(config \\ %Config{}), do: Models.list(config)
9 + @chat_completions_schema [
10 + model: [
11 + type: :string,
12 + doc: """
13 + ID of the model to use. See the model endpoint compatibility table for
14 + details on which models work with the Chat API.
15 + """,
16 + default: "gpt-3.5-turbo"
17 + ],
18 + messages: [
19 + type: {:list, :map},
20 + doc: """
21 + A list of messages comprising the conversation so far.
22 + """,
23 + default: [
24 + %{role: "system", content: "You are a helpful assistant."},
25 + %{role: "user", content: "Hello!"}
26 + ]
27 + ],
28 + frequency_penalty: [
29 + type: :float,
30 + doc: """
31 + Number between -2.0 and 2.0. Positive values penalize new tokens based on
32 + their existing frequency in the text so far, decreasing the model's
33 + likelihood to repeat the same line verbatim.
34 + """
35 + ],
36 + max_tokens: [
37 + type: :pos_integer,
38 + doc: """
39 + The maximum number of tokens that can be generated in the chat completion.
40 + The total length of input tokens and generated tokens is limited by the
41 + model's context length.
42 + """
43 + ],
44 + stream: [
45 + type: :boolean,
46 + doc: """
47 + If set, partial message deltas will be sent, like in ChatGPT. Tokens will
48 + be sent as data-only server-sent events as they become available, with
49 + the stream terminated by a data: [DONE] message.
50 + """
51 + ],
52 + temperature: [
53 + type: :float,
54 + doc: """
55 + What sampling temperature to use, between 0 and 2. Higher values like 0.8
56 + will make the output more random, while lower values like 0.2 will make
57 + it more focused and deterministic.
58 + """
59 + ],
60 + user: [
61 + type: :string,
62 + doc: """
63 + A unique identifier representing your end-user, which can help OpenAI
64 + to monitor and detect abuse.
65 + """
66 + ]
67 + ]
10 68
11 - # https://platform.openai.com/docs/api-reference/models/retrieve
12 - def model(model, config \\ %Config{}), do: Models.retrieve(model, config)
69 + @doc """
70 + List models
71 +
72 + For more information https://platform.openai.com/docs/api-reference/models
73 +
74 + ## Examples
75 +
76 + iex> iex(1)> config_override = %Config{api_key: "asdf_api"}
77 + iex> iex(2)> HipcallOpenai.models(config_override)
78 + ...> {:ok,
79 + ...> %{
80 + ...> "data" => [
81 + ...> %{
82 + ...> "created" => 1686588896,
83 + ...> "id" => "gpt-4-0613",
84 + ...> "object" => "model",
85 + ...> "owned_by" => "openai"
86 + ...> },
87 + ...> %{
88 + ...> "created" => 1651172509,
89 + ...> "id" => "curie-search-query",
90 + ...> "object" => "model",
91 + ...> "owned_by" => "openai-dev"
92 + ...> },
93 + ...> %{
94 + ...> "created" => 1687882411,
95 + ...> "id" => "gpt-4",
96 + ...> "object" => "model",
97 + ...> "owned_by" => "openai"
98 + ...> },
99 + ...> %{
100 + ...> "created" => 1651172509,
101 + ...> "id" => "babbage-search-query",
102 + ...> "object" => "model",
103 + ...> "owned_by" => "openai-dev"
104 + ...> },
105 + ...> %{
106 + ...> "created" => 1698785189,
107 + ...> "id" => "dall-e-3",
108 + ...> "object" => "model",
109 + ...> "owned_by" => "system"
110 + ...> }
111 + ...> ],
112 + ...> "object" => "list"
113 + ...> }}
114 +
115 + ## Arguments
116 +
117 + - `config`
118 +
119 + ## Raises
120 +
121 + - There is no exception.
122 +
123 + ## Returns
124 +
125 + - `{:ok, Finch.Response.t()}`
126 + - `{:error, Exception.t()}`
127 +
128 + """
129 + @spec models(config :: struct()) :: {:ok, map()} | {:error, map()} | {:error, any()}
130 + def models(config \\ %Config{}) when is_struct(config) do
131 + Models.list(config)
132 + end
133 +
134 + @doc """
135 + Retrieve model
136 +
137 + For more information https://platform.openai.com/docs/api-reference/models/retrieve
138 +
139 + ## Examples
140 +
141 + iex> iex(1)> iex(1)> HipcallOpenai.model("gpt-3.5-turbo-instruct")
142 + ...> {:ok,
143 + ...> %{
144 + ...> "created" => 1692901427,
145 + ...> "id" => "gpt-3.5-turbo-instruct",
146 + ...> "object" => "model",
147 + ...> "owned_by" => "system"
148 + ...> }}
149 +
150 + ## Arguments
151 +
152 + - `model`
153 +
154 + ## Raises
155 +
156 + There is no exceptions.
157 +
158 + ## Returns
159 +
160 + - `{:ok, map()}`
161 + - `{:error, Exception.t()}`
162 +
163 + """
164 + @spec model(model :: String.t()) :: {:ok, map()} | {:error, map()} | {:error, any()}
165 + def model(model) when is_bitstring(model) do
166 + model(model, %Config{})
167 + end
168 +
169 + @doc """
170 + The same with `model/1` just overwrite the config.
171 + """
172 + @spec model(model :: String.t(), config :: struct()) ::
173 + {:ok, map()} | {:error, map()} | {:error, any()}
174 + def model(model, %Config{} = config), do: Models.retrieve(model, config)
175 +
176 + @doc """
177 + Create chat completion
178 +
179 + For more information https://platform.openai.com/docs/api-reference/chat/create
180 +
181 + ## Examples
182 +
183 + iex> params = [
184 + iex> model: "gpt-4-1106-preview",
185 + iex> messages: [
186 + iex> %{role: "system", content: "Sen yardımcı olan bir asistansın."},
187 + iex> %{role: "user", content: "Merhaba!"}
188 + iex> ]
189 + iex> ]
190 + iex> HipcallOpenai.chat_completions(params)
191 + ...> {:ok,
192 + ...> %{
193 + ...> "choices" => [
194 + ...> %{
195 + ...> "finish_reason" => "stop",
196 + ...> "index" => 0,
197 + ...> "logprobs" => nil,
198 + ...> "message" => %{
199 + ...> "content" => "Merhaba! Size nasıl yardımcı olabilirim?",
200 + ...> "role" => "assistant"
201 + ...> }
202 + ...> }
203 + ...> ],
204 + ...> "created" => 1705330002,
205 + ...> "id" => "chatcmpl-8hIWYa9L3HBatp1Wyp5fJfneaBMUi",
206 + ...> "model" => "gpt-4-1106-preview",
207 + ...> "object" => "chat.completion",
208 + ...> "system_fingerprint" => "fp_168383a679",
209 + ...> "usage" => %{
210 + ...> "completion_tokens" => 15,
211 + ...> "prompt_tokens" => 27,
212 + ...> "total_tokens" => 42
213 + ...> }
214 + ...> }}
215 +
216 + ## Params
217 +
218 + #{NimbleOptions.docs(@chat_completions_schema)}
219 +
220 + ## Raises
221 +
222 + - Raise `NimbleOptions.ValidationError` if params are not valid.
223 +
224 + ## Returns
225 +
226 + - `{:ok, Finch.Response.t()}`
227 + - `{:error, Exception.t()}`
228 +
229 + """
230 + @spec chat_completions(params :: keyword()) :: {:ok, map()} | {:error, map()} | {:error, any()}
231 + def chat_completions(params) do
232 + NimbleOptions.validate!(params, @chat_completions_schema)
233 + Chat.completions(params, %Config{})
234 + end
235 +
236 + @doc """
237 + The same `chat_completions/1` just overwrite config.
238 + """
239 + @spec chat_completions(params :: keyword(), config :: struct()) ::
240 + {:ok, map()} | {:error, map()} | {:error, any()}
241 + def chat_completions(params, %Config{} = config) when is_struct(config) do
242 + NimbleOptions.validate!(params, @chat_completions_schema)
243 + Chat.completions(params, config)
244 + end
13 245 end
  @@ -0,0 +1,42 @@
1 + defmodule HipcallOpenai.Chat do
2 + @moduledoc false
3 +
4 + alias HipcallOpenai.Config
5 +
6 + @endpoint_url "chat"
7 +
8 + def completions(params, config \\ %Config{}) do
9 + Finch.build(
10 + :post,
11 + "#{Config.api_url()}#{@endpoint_url}/completions",
12 + header(config),
13 + body(params)
14 + )
15 + |> Finch.request(HipcallOpenaiFinch, receive_timeout: 600_000)
16 + |> case do
17 + {:ok, %Finch.Response{status: 200, body: body}} ->
18 + {:ok, body |> Jason.decode!()}
19 +
20 + {:ok, %Finch.Response{status: status, body: body, headers: headers}} ->
21 + {:error, %{status: status, body: body |> Jason.decode!(), headers: headers}}
22 +
23 + {:error, reason} ->
24 + {:error, reason}
25 + end
26 + end
27 +
28 + defp body(params) do
29 + params |> Enum.into(%{}) |> Jason.encode!()
30 + end
31 +
32 + defp header(config) do
33 + api_key = config.api_key || Config.api_key()
34 + api_organization = config.api_organization || Config.api_organization()
35 +
36 + [
37 + {"Authorization", "Bearer #{api_key}"},
38 + {"OpenAI-Organization", "#{api_organization}"},
39 + {"content-type", "application/json"}
40 + ]
41 + end
42 + end
Loading more files…