mistral 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.env.example +1 -1
- data/CHANGELOG.md +29 -2
- data/PYTHON_CLIENT_COMPARISON.md +12 -1
- data/README.md +11 -9
- data/examples/chatbot_with_streaming.rb +5 -4
- data/examples/code_completion.rb +24 -0
- data/examples/completion_with_streaming.rb +24 -0
- data/examples/function_calling.rb +4 -2
- data/lib/mistral/client.rb +68 -1
- data/lib/mistral/client_base.rb +58 -3
- data/lib/mistral/models/chat_completion.rb +1 -0
- data/lib/mistral/version.rb +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a13548c7603e4a353f94fb7c798f3256c8c84fcb24f6b9a35a0d34539026641a
|
4
|
+
data.tar.gz: 12d4ebe2515e9970d00e086c90bd69c10ab86d7e786b8417d534b3d6b30fc091
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 29ce8bafc147e843e8d9f8057a9b6fde4ab331ae1bd39d5b19e73fd2cc362940368d84c97c3433b69d80ec516ad710e0314e2eab509e1706cc4b206e77787ad7
|
7
|
+
data.tar.gz: 93013c03b1047734c3a97b3a20bc98b5d3768bc2d2b0ac64a67a4b5d14bb03c85d16fc146b8eb8d87690278f9012eccfe4ca07d67357d1b66310ce19a36fb1e2
|
data/.env.example
CHANGED
data/CHANGELOG.md
CHANGED
@@ -5,8 +5,35 @@ All notable changes to this project will be documented in this file.
|
|
5
5
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.1.1/)
|
6
6
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
7
7
|
|
8
|
+
## [0.3.0] - 2024-05-30
|
9
|
+
|
10
|
+
### Added
|
11
|
+
|
12
|
+
- Added support for completion requests which you can use to query their latest model
|
13
|
+
[codestral](https://mistral.ai/news/codestral/).
|
14
|
+
See [this example](https://github.com/wilsonsilva/mistral/blob/0.3.0/examples/code_completion.rb) to get started.
|
15
|
+
|
16
|
+
## [0.2.0] - 2024-05-23
|
17
|
+
|
18
|
+
### Added
|
19
|
+
|
20
|
+
- We now support tool_call_id for tool messages. This will be mandatory in the future but you can start using right
|
21
|
+
away to improve the model performance during function calling (especially multiple).
|
22
|
+
Ports [mistralai/client-python#93](https://github.com/mistralai/client-python/pull/93)
|
23
|
+
|
24
|
+
### Changed
|
25
|
+
|
26
|
+
- Renamed `LOG_LEVEL` to `MISTRAL_LOG_LEVEL`. This is not a direct port of the Python client because Python has a
|
27
|
+
global logger in the `logging` module, but Ruby doesn't.
|
28
|
+
Ports [mistralai/client-python#86](https://github.com/mistralai/client-python/pull/86)
|
29
|
+
- Get API key at client initialization. Ports
|
30
|
+
[mistralai/client-python#57](https://github.com/mistralai/client-python/pull/57)
|
31
|
+
|
8
32
|
## [0.1.0] - 2024-05-04
|
9
33
|
|
10
|
-
- Initial release. Feature parity with `v0.1.8` of the
|
34
|
+
- Initial release. Feature parity with `v0.1.8` of the
|
35
|
+
[mistralai/client-python](https://github.com/mistralai/client-python)
|
11
36
|
|
12
|
-
[0.
|
37
|
+
[0.3.0]: https://github.com/wilsonsilva/mistral/compare/v0.2.0...v0.3.0
|
38
|
+
[0.2.0]: https://github.com/wilsonsilva/mistral/compare/v0.1.0...v0.2.0
|
39
|
+
[0.1.0]: https://github.com/wilsonsilva/mistral/compare/28e7c9...v0.1.0
|
data/PYTHON_CLIENT_COMPARISON.md
CHANGED
@@ -175,10 +175,21 @@ This code resides in `lib/http/features/line_iterable_body.rb`.
|
|
175
175
|
## Testing
|
176
176
|
|
177
177
|
The Ruby gem aims for 1:1 parity with the Python client. As such, it uses `Minitest` (similar to Python's `pytest`).
|
178
|
-
However, testing was simplified by using
|
178
|
+
However, testing was simplified by using `webmock` for stubbing requests, instead of implementing 100% test
|
179
179
|
coverage and using RSpec, which is usually what I do.
|
180
180
|
|
181
181
|
## Examples
|
182
182
|
|
183
183
|
The `function_calling.rb` example omits the unnecessary `n_rows = data['transaction_id'].length` line present in
|
184
184
|
the Python version.
|
185
|
+
|
186
|
+
## Logging
|
187
|
+
|
188
|
+
Python has a global logger:
|
189
|
+
|
190
|
+
```python
|
191
|
+
self._logger = logging.getLogger(__name__)
|
192
|
+
```
|
193
|
+
|
194
|
+
Ruby doesn't. Thus, in order to allow users to customize the logging level, they can set the environment variable
|
195
|
+
`MISTRAL_LOG_LEVEL` to `DEBUG`, `INFO`, `WARN`, `ERROR` or `FATAL`.
|
data/README.md
CHANGED
@@ -80,15 +80,17 @@ end
|
|
80
80
|
|
81
81
|
In the [`examples`](https://github.com/wilsonsilva/mistral/tree/main/examples) folder, you will find how to do:
|
82
82
|
|
83
|
-
| File Name
|
84
|
-
|
85
|
-
| [`chat_no_streaming.rb`](https://github.com/wilsonsilva/mistral/blob/main/examples/chat_no_streaming.rb)
|
86
|
-
| [`chat_with_streaming.rb`](https://github.com/wilsonsilva/mistral/blob/main/examples/chat_with_streaming.rb)
|
87
|
-
| [`chatbot_with_streaming.rb`](https://github.com/wilsonsilva/mistral/blob/main/examples/chatbot_with_streaming.rb)
|
88
|
-
| [`
|
89
|
-
| [`
|
90
|
-
| [`
|
91
|
-
| [`
|
83
|
+
| File Name | Description |
|
84
|
+
|--------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------|
|
85
|
+
| [`chat_no_streaming.rb`](https://github.com/wilsonsilva/mistral/blob/main/examples/chat_no_streaming.rb) | How to use the chat endpoint without streaming |
|
86
|
+
| [`chat_with_streaming.rb`](https://github.com/wilsonsilva/mistral/blob/main/examples/chat_with_streaming.rb) | How to use the chat endpoint with streaming |
|
87
|
+
| [`chatbot_with_streaming.rb`](https://github.com/wilsonsilva/mistral/blob/main/examples/chatbot_with_streaming.rb) | A simple interactive chatbot using streaming |
|
88
|
+
| [`code_completion.rb`](https://github.com/wilsonsilva/mistral/blob/main/examples/code_completion.rb) | How to perform a code completion |
|
89
|
+
| [`completion_with_streaming.rb`](https://github.com/wilsonsilva/mistral/blob/main/examples/completion_with_streaming.rb) | How to perform a code completion with streaming |
|
90
|
+
| [`embeddings.rb`](https://github.com/wilsonsilva/mistral/blob/main/examples/embeddings.rb) | How to use the embeddings endpoint |
|
91
|
+
| [`function_calling.rb`](https://github.com/wilsonsilva/mistral/blob/main/examples/function_calling.rb) | How to call functions using the chat endpoint |
|
92
|
+
| [`json_format.rb`](https://github.com/wilsonsilva/mistral/blob/main/examples/json_format.rb) | How to request and parse JSON responses from the chat endpoint |
|
93
|
+
| [`list_models.rb`](https://github.com/wilsonsilva/mistral/blob/main/examples/list_models.rb) | How to list available models |
|
92
94
|
|
93
95
|
## 🔨 Development
|
94
96
|
|
@@ -10,11 +10,12 @@ require 'optparse'
|
|
10
10
|
require 'mistral'
|
11
11
|
|
12
12
|
MODEL_LIST = %w[
|
13
|
-
mistral-tiny
|
14
|
-
mistral-small
|
15
|
-
mistral-medium
|
13
|
+
mistral-tiny-latest
|
14
|
+
mistral-small-latest
|
15
|
+
mistral-medium-latest
|
16
|
+
codestral-latest
|
16
17
|
].freeze
|
17
|
-
DEFAULT_MODEL = 'mistral-small'
|
18
|
+
DEFAULT_MODEL = 'mistral-small-latest'
|
18
19
|
DEFAULT_TEMPERATURE = 0.7
|
19
20
|
LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s'
|
20
21
|
# A hash of all commands and their arguments, used for tab completion.
|
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'dotenv/load'
|
6
|
+
require 'mistral'
|
7
|
+
|
8
|
+
api_key = ENV.fetch('MISTRAL_API_KEY')
|
9
|
+
client = Mistral::Client.new(api_key: api_key)
|
10
|
+
|
11
|
+
prompt = 'def fibonacci(n: int):'
|
12
|
+
suffix = "n = int(input('Enter a number: '))\nprint(fibonacci(n))"
|
13
|
+
|
14
|
+
response = client.completion(
|
15
|
+
model: 'codestral-latest',
|
16
|
+
prompt: prompt,
|
17
|
+
suffix: suffix
|
18
|
+
)
|
19
|
+
|
20
|
+
print <<~COMPLETION
|
21
|
+
#{prompt}
|
22
|
+
#{response.choices[0].message.content}
|
23
|
+
#{suffix}
|
24
|
+
COMPLETION
|
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'dotenv/load'
|
6
|
+
require 'mistral'
|
7
|
+
|
8
|
+
api_key = ENV.fetch('MISTRAL_API_KEY')
|
9
|
+
client = Mistral::Client.new(api_key: api_key)
|
10
|
+
|
11
|
+
prompt = 'def fibonacci(n: int):'
|
12
|
+
suffix = "n = int(input('Enter a number: '))\nprint(fibonacci(n))"
|
13
|
+
|
14
|
+
print(prompt)
|
15
|
+
|
16
|
+
client.completion_stream(
|
17
|
+
model: 'codestral-latest',
|
18
|
+
prompt: prompt,
|
19
|
+
suffix: suffix
|
20
|
+
).each do |chunk|
|
21
|
+
print(chunk.choices[0].delta.content) unless chunk.choices[0].delta.content.nil?
|
22
|
+
end
|
23
|
+
|
24
|
+
print(suffix)
|
@@ -73,7 +73,7 @@ tools = [
|
|
73
73
|
]
|
74
74
|
|
75
75
|
api_key = ENV.fetch('MISTRAL_API_KEY')
|
76
|
-
model = 'mistral-
|
76
|
+
model = 'mistral-small-latest'
|
77
77
|
|
78
78
|
client = Mistral::Client.new(api_key: api_key)
|
79
79
|
|
@@ -97,7 +97,9 @@ puts "calling function_name: #{function_name}, with function_params: #{function_
|
|
97
97
|
function_result = names_to_functions[function_name].call(function_params['transaction_id'])
|
98
98
|
|
99
99
|
messages << response.choices[0].message
|
100
|
-
messages << Mistral::ChatMessage.new(
|
100
|
+
messages << Mistral::ChatMessage.new(
|
101
|
+
role: 'tool', name: function_name, content: function_result, tool_call_id: tool_call.id
|
102
|
+
)
|
101
103
|
|
102
104
|
response = client.chat(model: model, messages: messages, tools: tools)
|
103
105
|
|
data/lib/mistral/client.rb
CHANGED
@@ -6,7 +6,7 @@ module Mistral
|
|
6
6
|
# Synchronous wrapper around the async client
|
7
7
|
class Client < ClientBase
|
8
8
|
def initialize(
|
9
|
-
api_key:
|
9
|
+
api_key: nil,
|
10
10
|
endpoint: ENDPOINT,
|
11
11
|
max_retries: 5,
|
12
12
|
timeout: 120
|
@@ -158,6 +158,73 @@ module Mistral
|
|
158
158
|
raise Mistral::Error.new(message: 'No response received')
|
159
159
|
end
|
160
160
|
|
161
|
+
# A completion endpoint that returns a single response.
|
162
|
+
#
|
163
|
+
# @param model [String] model the name of the model to get completion with, e.g. codestral-latest
|
164
|
+
# @param prompt [String] the prompt to complete
|
165
|
+
# @param suffix [String, nil] the suffix to append to the prompt for fill-in-the-middle completion
|
166
|
+
# @param temperature [Float, nil] temperature the temperature to use for sampling, e.g. 0.5.
|
167
|
+
# @param max_tokens [Integer, nil] the maximum number of tokens to generate, e.g. 100. Defaults to nil.
|
168
|
+
# @param top_p [Float, nil] the cumulative probability of tokens to generate, e.g. 0.9. Defaults to nil.
|
169
|
+
# @param random_seed [Integer, nil] the random seed to use for sampling, e.g. 42. Defaults to nil.
|
170
|
+
# @param stop [Array<String>, nil] a list of tokens to stop generation at, e.g. ['/n/n']
|
171
|
+
# @return [ChatCompletionResponse] a response object containing the generated text.
|
172
|
+
#
|
173
|
+
def completion(
|
174
|
+
model:,
|
175
|
+
prompt:,
|
176
|
+
suffix: nil,
|
177
|
+
temperature: nil,
|
178
|
+
max_tokens: nil,
|
179
|
+
top_p: nil,
|
180
|
+
random_seed: nil,
|
181
|
+
stop: nil
|
182
|
+
)
|
183
|
+
request = make_completion_request(
|
184
|
+
prompt:, model:, suffix:, temperature:, max_tokens:, top_p:, random_seed:, stop:
|
185
|
+
)
|
186
|
+
single_response = request('post', 'v1/fim/completions', json: request, stream: false)
|
187
|
+
|
188
|
+
single_response.each do |response|
|
189
|
+
return ChatCompletionResponse.new(**response)
|
190
|
+
end
|
191
|
+
|
192
|
+
raise Error, 'No response received'
|
193
|
+
end
|
194
|
+
|
195
|
+
# An asynchronous completion endpoint that streams responses.
|
196
|
+
#
|
197
|
+
# @param model [String] model the name of the model to get completions with, e.g. codestral-latest
|
198
|
+
# @param prompt [String] the prompt to complete
|
199
|
+
# @param suffix [String, nil] the suffix to append to the prompt for fill-in-the-middle completion
|
200
|
+
# @param temperature [Float, nil] temperature the temperature to use for sampling, e.g. 0.5.
|
201
|
+
# @param max_tokens [Integer, nil] the maximum number of tokens to generate, e.g. 100. Defaults to nil.
|
202
|
+
# @param top_p [Float, nil] the cumulative probability of tokens to generate, e.g. 0.9. Defaults to nil.
|
203
|
+
# @param random_seed [Integer, nil] the random seed to use for sampling, e.g. 42. Defaults to nil.
|
204
|
+
# @param stop [Array<String>, nil] a list of tokens to stop generation at, e.g. ['/n/n']
|
205
|
+
# @return [Enumerator<ChatCompletionStreamResponse>] a generator that yields response objects containing the
|
206
|
+
# generated text.
|
207
|
+
#
|
208
|
+
def completion_stream(
|
209
|
+
model:,
|
210
|
+
prompt:,
|
211
|
+
suffix: nil,
|
212
|
+
temperature: nil,
|
213
|
+
max_tokens: nil,
|
214
|
+
top_p: nil,
|
215
|
+
random_seed: nil,
|
216
|
+
stop: nil
|
217
|
+
)
|
218
|
+
request = make_completion_request(
|
219
|
+
prompt:, model:, suffix:, temperature:, max_tokens:, top_p:, random_seed:, stop:, stream: true
|
220
|
+
)
|
221
|
+
response = request('post', 'v1/fim/completions', json: request, stream: true)
|
222
|
+
|
223
|
+
response.lazy.map do |json_streamed_response|
|
224
|
+
ChatCompletionStreamResponse.new(**json_streamed_response)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
161
228
|
private
|
162
229
|
|
163
230
|
def request(method, path, json: nil, stream: false, attempt: 1)
|
data/lib/mistral/client_base.rb
CHANGED
@@ -5,11 +5,15 @@ module Mistral
|
|
5
5
|
attr_reader :endpoint, :api_key, :max_retries, :timeout
|
6
6
|
|
7
7
|
def initialize(endpoint:, api_key: nil, max_retries: 5, timeout: 120)
|
8
|
-
@endpoint = endpoint
|
9
|
-
@api_key = api_key
|
10
8
|
@max_retries = max_retries
|
11
9
|
@timeout = timeout
|
12
10
|
|
11
|
+
api_key = ENV['MISTRAL_API_KEY'] if api_key.nil?
|
12
|
+
|
13
|
+
raise Error, 'API key not provided. Please set MISTRAL_API_KEY environment variable.' if api_key.nil?
|
14
|
+
|
15
|
+
@api_key = api_key
|
16
|
+
@endpoint = endpoint
|
13
17
|
@logger = config_logger
|
14
18
|
|
15
19
|
# For azure endpoints, we default to the mistral model
|
@@ -64,6 +68,57 @@ module Mistral
|
|
64
68
|
parsed_messages
|
65
69
|
end
|
66
70
|
|
71
|
+
def make_completion_request(
|
72
|
+
prompt:,
|
73
|
+
model: nil,
|
74
|
+
suffix: nil,
|
75
|
+
temperature: nil,
|
76
|
+
max_tokens: nil,
|
77
|
+
top_p: nil,
|
78
|
+
random_seed: nil,
|
79
|
+
stop: nil,
|
80
|
+
stream: false
|
81
|
+
)
|
82
|
+
request_data = {
|
83
|
+
'prompt' => prompt,
|
84
|
+
'suffix' => suffix,
|
85
|
+
'model' => model,
|
86
|
+
'stream' => stream
|
87
|
+
}
|
88
|
+
|
89
|
+
request_data['stop'] = stop unless stop.nil?
|
90
|
+
|
91
|
+
if model.nil?
|
92
|
+
raise Error.new(message: 'model must be provided') if @default_model.nil?
|
93
|
+
|
94
|
+
request_data['model'] = @default_model
|
95
|
+
else
|
96
|
+
request_data['model'] = model
|
97
|
+
end
|
98
|
+
|
99
|
+
request_data.merge!(
|
100
|
+
build_sampling_params(
|
101
|
+
temperature: temperature,
|
102
|
+
max_tokens: max_tokens,
|
103
|
+
top_p: top_p,
|
104
|
+
random_seed: random_seed
|
105
|
+
)
|
106
|
+
)
|
107
|
+
|
108
|
+
@logger.debug("Completion request: #{request_data}")
|
109
|
+
|
110
|
+
request_data
|
111
|
+
end
|
112
|
+
|
113
|
+
def build_sampling_params(max_tokens: nil, random_seed: nil, temperature: nil, top_p: nil)
|
114
|
+
params = {}
|
115
|
+
params['temperature'] = temperature unless temperature.nil?
|
116
|
+
params['max_tokens'] = max_tokens unless max_tokens.nil?
|
117
|
+
params['top_p'] = top_p unless top_p.nil?
|
118
|
+
params['random_seed'] = random_seed unless random_seed.nil?
|
119
|
+
params
|
120
|
+
end
|
121
|
+
|
67
122
|
def make_chat_request(
|
68
123
|
messages:,
|
69
124
|
model: nil,
|
@@ -115,7 +170,7 @@ module Mistral
|
|
115
170
|
|
116
171
|
def config_logger
|
117
172
|
Logger.new($stdout).tap do |logger|
|
118
|
-
logger.level = ENV.fetch('
|
173
|
+
logger.level = ENV.fetch('MISTRAL_LOG_LEVEL', 'ERROR')
|
119
174
|
|
120
175
|
logger.formatter = proc do |severity, datetime, progname, msg|
|
121
176
|
"#{datetime.strftime("%Y-%m-%d %H:%M:%S")} #{severity} #{progname}: #{msg}\n"
|
@@ -43,6 +43,7 @@ module Mistral
|
|
43
43
|
attribute :content, Types::Strict::Array.of(Types::Strict::String) | Types::Strict::String
|
44
44
|
attribute? :name, Types::String.optional
|
45
45
|
attribute? :tool_calls, Types::Strict::Array.of(ToolCall).optional
|
46
|
+
attribute? :tool_call_id, Types::String.optional
|
46
47
|
end
|
47
48
|
|
48
49
|
class DeltaMessage < Dry::Struct
|
data/lib/mistral/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mistral
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Wilson Silva
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-05-
|
11
|
+
date: 2024-05-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dry-struct
|
@@ -108,7 +108,8 @@ dependencies:
|
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '3.23'
|
111
|
-
description:
|
111
|
+
description: A 1:1 Ruby port of the official Mistral Python client, with feature and
|
112
|
+
API parity.
|
112
113
|
email:
|
113
114
|
- wilson.dsigns@gmail.com
|
114
115
|
executables: []
|
@@ -127,6 +128,8 @@ files:
|
|
127
128
|
- examples/chat_no_streaming.rb
|
128
129
|
- examples/chat_with_streaming.rb
|
129
130
|
- examples/chatbot_with_streaming.rb
|
131
|
+
- examples/code_completion.rb
|
132
|
+
- examples/completion_with_streaming.rb
|
130
133
|
- examples/embeddings.rb
|
131
134
|
- examples/function_calling.rb
|
132
135
|
- examples/json_format.rb
|