mistral-ai 1.0.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 +7 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +6 -0
- data/.ruby-version +1 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +64 -0
- data/LICENSE +9 -0
- data/README.md +526 -0
- data/components/errors.rb +26 -0
- data/controllers/client.rb +117 -0
- data/mistral-ai.gemspec +36 -0
- data/ports/dsl/mistral-ai/errors.rb +5 -0
- data/ports/dsl/mistral-ai.rb +14 -0
- data/static/gem.rb +15 -0
- data/tasks/generate-readme.clj +39 -0
- data/template.md +497 -0
- metadata +95 -0
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'event_stream_parser'
|
4
|
+
require 'faraday'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
require_relative '../ports/dsl/mistral-ai/errors'
|
8
|
+
|
9
|
+
module Mistral
|
10
|
+
module Controllers
|
11
|
+
class Client
|
12
|
+
DEFAULT_ADDRESS = 'https://api.mistral.ai'
|
13
|
+
|
14
|
+
ALLOWED_REQUEST_OPTIONS = %i[timeout open_timeout read_timeout write_timeout].freeze
|
15
|
+
|
16
|
+
def initialize(config)
|
17
|
+
@api_key = config.dig(:credentials, :api_key)
|
18
|
+
@server_sent_events = config.dig(:options, :server_sent_events)
|
19
|
+
|
20
|
+
@address = if config[:credentials][:address].nil? || config[:credentials][:address].to_s.strip.empty?
|
21
|
+
"#{DEFAULT_ADDRESS}/"
|
22
|
+
else
|
23
|
+
"#{config[:credentials][:address].to_s.sub(%r{/$}, '')}/"
|
24
|
+
end
|
25
|
+
|
26
|
+
if @api_key.nil? && @address == "#{DEFAULT_ADDRESS}/"
|
27
|
+
raise MissingAPIKeyError, 'Missing API Key, which is required.'
|
28
|
+
end
|
29
|
+
|
30
|
+
@request_options = config.dig(:options, :connection, :request)
|
31
|
+
|
32
|
+
@request_options = if @request_options.is_a?(Hash)
|
33
|
+
@request_options.select do |key, _|
|
34
|
+
ALLOWED_REQUEST_OPTIONS.include?(key)
|
35
|
+
end
|
36
|
+
else
|
37
|
+
{}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def chat_completions(payload, server_sent_events: nil, &callback)
|
42
|
+
server_sent_events = false if payload[:stream] != true
|
43
|
+
request('v1/chat/completions', payload, server_sent_events:, &callback)
|
44
|
+
end
|
45
|
+
|
46
|
+
def embeddings(payload, server_sent_events: nil, &callback)
|
47
|
+
request('v1/embeddings', payload, server_sent_events: false, &callback)
|
48
|
+
end
|
49
|
+
|
50
|
+
def request(path, payload, server_sent_events: nil, &callback)
|
51
|
+
server_sent_events_enabled = server_sent_events.nil? ? @server_sent_events : server_sent_events
|
52
|
+
url = "#{@address}/#{path}"
|
53
|
+
|
54
|
+
if !callback.nil? && !server_sent_events_enabled
|
55
|
+
raise BlockWithoutServerSentEventsError,
|
56
|
+
'You are trying to use a block without Server Sent Events (SSE) enabled.'
|
57
|
+
end
|
58
|
+
|
59
|
+
results = []
|
60
|
+
|
61
|
+
response = Faraday.new(request: @request_options) do |faraday|
|
62
|
+
faraday.response :raise_error
|
63
|
+
end.post do |request|
|
64
|
+
request.url url
|
65
|
+
request.headers['Content-Type'] = 'application/json'
|
66
|
+
|
67
|
+
request.headers['Authorization'] = "Bearer #{@api_key}" unless @api_key.nil?
|
68
|
+
|
69
|
+
request.body = payload.to_json
|
70
|
+
|
71
|
+
if server_sent_events_enabled
|
72
|
+
parser = EventStreamParser::Parser.new
|
73
|
+
|
74
|
+
request.options.on_data = proc do |chunk, bytes, env|
|
75
|
+
if env && env.status != 200
|
76
|
+
raise_error = Faraday::Response::RaiseError.new
|
77
|
+
raise_error.on_complete(env.merge(body: chunk))
|
78
|
+
end
|
79
|
+
|
80
|
+
parser.feed(chunk) do |type, data, id, reconnection_time|
|
81
|
+
parsed_data = safe_parse_json(data)
|
82
|
+
|
83
|
+
if parsed_data != '[DONE]'
|
84
|
+
result = {
|
85
|
+
event: safe_parse_json(data),
|
86
|
+
parsed: { type:, data:, id:, reconnection_time: },
|
87
|
+
raw: { chunk:, bytes:, env: }
|
88
|
+
}
|
89
|
+
|
90
|
+
callback.call(result[:event], result[:parsed], result[:raw]) unless callback.nil?
|
91
|
+
|
92
|
+
results << result
|
93
|
+
|
94
|
+
parsed_data['choices'].find do |candidate|
|
95
|
+
!candidate['finish_reason'].nil? && candidate['finish_reason'] != ''
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
return safe_parse_json(response.body) unless server_sent_events_enabled
|
104
|
+
|
105
|
+
results.map { |result| result[:event] }
|
106
|
+
rescue Faraday::ServerError => e
|
107
|
+
raise RequestError.new(e.message, request: e, payload:)
|
108
|
+
end
|
109
|
+
|
110
|
+
def safe_parse_json(raw)
|
111
|
+
raw.start_with?('{', '[') ? JSON.parse(raw) : raw
|
112
|
+
rescue JSON::ParserError
|
113
|
+
raw
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
data/mistral-ai.gemspec
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'static/gem'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = Mistral::GEM[:name]
|
7
|
+
spec.version = Mistral::GEM[:version]
|
8
|
+
spec.authors = [Mistral::GEM[:author]]
|
9
|
+
|
10
|
+
spec.summary = Mistral::GEM[:summary]
|
11
|
+
spec.description = Mistral::GEM[:description]
|
12
|
+
|
13
|
+
spec.homepage = Mistral::GEM[:github]
|
14
|
+
|
15
|
+
spec.license = Mistral::GEM[:license]
|
16
|
+
|
17
|
+
spec.required_ruby_version = Gem::Requirement.new(">= #{Mistral::GEM[:ruby]}")
|
18
|
+
|
19
|
+
spec.metadata['allowed_push_host'] = Mistral::GEM[:gem_server]
|
20
|
+
|
21
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
22
|
+
spec.metadata['source_code_uri'] = Mistral::GEM[:github]
|
23
|
+
|
24
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
25
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
26
|
+
f.match(%r{\A(?:test|spec|features)/})
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
spec.require_paths = ['ports/dsl']
|
31
|
+
|
32
|
+
spec.add_dependency 'event_stream_parser', '~> 1.0'
|
33
|
+
spec.add_dependency 'faraday', '~> 2.8', '>= 2.8.1'
|
34
|
+
|
35
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
36
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../static/gem'
|
4
|
+
require_relative '../../controllers/client'
|
5
|
+
|
6
|
+
module Mistral
|
7
|
+
def self.new(...)
|
8
|
+
Controllers::Client.new(...)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.version
|
12
|
+
Mistral::GEM[:version]
|
13
|
+
end
|
14
|
+
end
|
data/static/gem.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mistral
|
4
|
+
GEM = {
|
5
|
+
name: 'mistral-ai',
|
6
|
+
version: '1.0.0',
|
7
|
+
author: 'gbaptista',
|
8
|
+
summary: 'Interact with Mistral AI.',
|
9
|
+
description: "A Ruby gem for interacting with Mistral AI's large language models.",
|
10
|
+
github: 'https://github.com/gbaptista/mistral-ai',
|
11
|
+
gem_server: 'https://rubygems.org',
|
12
|
+
license: 'MIT',
|
13
|
+
ruby: '3.1.0'
|
14
|
+
}.freeze
|
15
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
(require '[clojure.string :as str])
|
2
|
+
|
3
|
+
(defn slugify [text]
|
4
|
+
(-> text
|
5
|
+
(clojure.string/lower-case)
|
6
|
+
(clojure.string/replace " " "-")
|
7
|
+
(clojure.string/replace #"[^a-z0-9\-_]" "")))
|
8
|
+
|
9
|
+
(defn remove-code-blocks [content]
|
10
|
+
(let [code-block-regex #"(?s)```.*?```"]
|
11
|
+
(clojure.string/replace content code-block-regex "")))
|
12
|
+
|
13
|
+
(defn process-line [line]
|
14
|
+
(when-let [[_ hashes title] (re-find #"^(\#{2,}) (.+)" line)]
|
15
|
+
(let [link (slugify title)]
|
16
|
+
{:level (count hashes) :title title :link link})))
|
17
|
+
|
18
|
+
(defn create-index [content]
|
19
|
+
(let [processed-content (remove-code-blocks content)
|
20
|
+
processed-lines (->> processed-content
|
21
|
+
clojure.string/split-lines
|
22
|
+
(map process-line)
|
23
|
+
(remove nil?))]
|
24
|
+
(->> processed-lines
|
25
|
+
(map (fn [{:keys [level title link]}]
|
26
|
+
(str (apply str (repeat (* 4 (- level 2)) " "))
|
27
|
+
"- ["
|
28
|
+
title
|
29
|
+
"](#"
|
30
|
+
link
|
31
|
+
")")))
|
32
|
+
(clojure.string/join "\n"))))
|
33
|
+
|
34
|
+
|
35
|
+
(let [content (slurp "template.md")
|
36
|
+
index (create-index content)
|
37
|
+
updated-content (clojure.string/replace content "{index}" index)]
|
38
|
+
(spit "README.md" updated-content)
|
39
|
+
(println "README.md successfully generated."))
|