groq_ruby 0.1.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.
Files changed (109) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +57 -0
  4. data/CLAUDE.md +103 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +495 -0
  7. data/Rakefile +11 -0
  8. data/examples/README.md +39 -0
  9. data/examples/batch.rb +29 -0
  10. data/examples/chat_completion.rb +24 -0
  11. data/examples/chat_completion_stop.rb +19 -0
  12. data/examples/chat_completion_streaming.rb +23 -0
  13. data/examples/embedding.rb +20 -0
  14. data/examples/error_handling.rb +27 -0
  15. data/examples/file_upload.rb +23 -0
  16. data/examples/mcp_agent.rb +63 -0
  17. data/examples/mcp_chat_with_tools.rb +103 -0
  18. data/examples/mcp_resources_and_prompts.rb +89 -0
  19. data/examples/models_list.rb +16 -0
  20. data/examples/speech.rb +23 -0
  21. data/examples/transcription.rb +23 -0
  22. data/examples/translation.rb +22 -0
  23. data/lib/groq_ruby/client.rb +69 -0
  24. data/lib/groq_ruby/configuration.rb +62 -0
  25. data/lib/groq_ruby/error_mapper.rb +37 -0
  26. data/lib/groq_ruby/errors/api_connection_error.rb +8 -0
  27. data/lib/groq_ruby/errors/api_error.rb +14 -0
  28. data/lib/groq_ruby/errors/api_response_error.rb +5 -0
  29. data/lib/groq_ruby/errors/api_status_error.rb +23 -0
  30. data/lib/groq_ruby/errors/api_timeout_error.rb +8 -0
  31. data/lib/groq_ruby/errors/authentication_error.rb +4 -0
  32. data/lib/groq_ruby/errors/bad_request_error.rb +4 -0
  33. data/lib/groq_ruby/errors/configuration_error.rb +4 -0
  34. data/lib/groq_ruby/errors/conflict_error.rb +4 -0
  35. data/lib/groq_ruby/errors/error.rb +5 -0
  36. data/lib/groq_ruby/errors/internal_server_error.rb +4 -0
  37. data/lib/groq_ruby/errors/not_found_error.rb +4 -0
  38. data/lib/groq_ruby/errors/parameter_error.rb +13 -0
  39. data/lib/groq_ruby/errors/permission_denied_error.rb +4 -0
  40. data/lib/groq_ruby/errors/rate_limit_error.rb +4 -0
  41. data/lib/groq_ruby/errors/unprocessable_entity_error.rb +4 -0
  42. data/lib/groq_ruby/mcp/bridge.rb +239 -0
  43. data/lib/groq_ruby/mcp/claude_desktop_config.rb +79 -0
  44. data/lib/groq_ruby/mcp/client.rb +171 -0
  45. data/lib/groq_ruby/mcp/errors/error.rb +7 -0
  46. data/lib/groq_ruby/mcp/errors/json_rpc_error.rb +21 -0
  47. data/lib/groq_ruby/mcp/errors/protocol_error.rb +7 -0
  48. data/lib/groq_ruby/mcp/errors/timeout_error.rb +7 -0
  49. data/lib/groq_ruby/mcp/errors/transport_error.rb +6 -0
  50. data/lib/groq_ruby/mcp/errors/unknown_tool_error.rb +7 -0
  51. data/lib/groq_ruby/mcp/json_rpc.rb +51 -0
  52. data/lib/groq_ruby/mcp/prompt.rb +21 -0
  53. data/lib/groq_ruby/mcp/resource.rb +17 -0
  54. data/lib/groq_ruby/mcp/server_config.rb +22 -0
  55. data/lib/groq_ruby/mcp/tool.rb +22 -0
  56. data/lib/groq_ruby/mcp/transport.rb +32 -0
  57. data/lib/groq_ruby/mcp/transports/stdio.rb +100 -0
  58. data/lib/groq_ruby/mcp.rb +25 -0
  59. data/lib/groq_ruby/models/audio/transcription.rb +10 -0
  60. data/lib/groq_ruby/models/audio/translation.rb +8 -0
  61. data/lib/groq_ruby/models/batches/batch.rb +16 -0
  62. data/lib/groq_ruby/models/batches/batch_list.rb +10 -0
  63. data/lib/groq_ruby/models/batches/batch_request_counts.rb +8 -0
  64. data/lib/groq_ruby/models/chat/chat_completion.rb +14 -0
  65. data/lib/groq_ruby/models/chat/chat_completion_choice.rb +10 -0
  66. data/lib/groq_ruby/models/chat/chat_completion_chunk.rb +13 -0
  67. data/lib/groq_ruby/models/chat/chat_completion_chunk_choice.rb +10 -0
  68. data/lib/groq_ruby/models/chat/chat_completion_delta.rb +8 -0
  69. data/lib/groq_ruby/models/chat/chat_completion_message.rb +10 -0
  70. data/lib/groq_ruby/models/embeddings/create_embedding_response.rb +11 -0
  71. data/lib/groq_ruby/models/embeddings/embedding.rb +8 -0
  72. data/lib/groq_ruby/models/embeddings/embedding_usage.rb +8 -0
  73. data/lib/groq_ruby/models/files/file_deleted.rb +8 -0
  74. data/lib/groq_ruby/models/files/file_list.rb +10 -0
  75. data/lib/groq_ruby/models/files/file_object.rb +8 -0
  76. data/lib/groq_ruby/models/model.rb +8 -0
  77. data/lib/groq_ruby/models/model_deleted.rb +8 -0
  78. data/lib/groq_ruby/models/model_factory.rb +31 -0
  79. data/lib/groq_ruby/models/model_list.rb +10 -0
  80. data/lib/groq_ruby/models/usage.rb +11 -0
  81. data/lib/groq_ruby/multipart.rb +84 -0
  82. data/lib/groq_ruby/request.rb +13 -0
  83. data/lib/groq_ruby/resources/audio/speech.rb +32 -0
  84. data/lib/groq_ruby/resources/audio/transcriptions.rb +48 -0
  85. data/lib/groq_ruby/resources/audio/translations.rb +45 -0
  86. data/lib/groq_ruby/resources/audio.rb +26 -0
  87. data/lib/groq_ruby/resources/base.rb +33 -0
  88. data/lib/groq_ruby/resources/batches.rb +44 -0
  89. data/lib/groq_ruby/resources/chat/completions.rb +94 -0
  90. data/lib/groq_ruby/resources/chat.rb +16 -0
  91. data/lib/groq_ruby/resources/embeddings.rb +28 -0
  92. data/lib/groq_ruby/resources/files.rb +55 -0
  93. data/lib/groq_ruby/resources/models.rb +35 -0
  94. data/lib/groq_ruby/response.rb +9 -0
  95. data/lib/groq_ruby/streaming/chunk_stream.rb +58 -0
  96. data/lib/groq_ruby/streaming/event_parser.rb +23 -0
  97. data/lib/groq_ruby/transport.rb +169 -0
  98. data/lib/groq_ruby/version.rb +5 -0
  99. data/lib/groq_ruby.rb +36 -0
  100. data/lib/tasks/gem.rake +5 -0
  101. data/lib/tasks/lint/all.rake +11 -0
  102. data/lib/tasks/lint/rubocop.rake +15 -0
  103. data/lib/tasks/security.rake +11 -0
  104. data/lib/tasks/types.rake +11 -0
  105. data/sig/groq_ruby.rbs +191 -0
  106. data/sig/zeitwerk.rbs +13 -0
  107. data.tar.gz.sig +0 -0
  108. metadata +237 -0
  109. metadata.gz.sig +0 -0
@@ -0,0 +1,11 @@
1
+ module GroqRuby
2
+ module Models
3
+ # Full response from `client.embeddings.create`.
4
+ class CreateEmbeddingResponse < Data.define(:data, :model, :object, :usage)
5
+ extend ModelFactory
6
+
7
+ coerce :data, with: ->(arr) { Array(arr).map { |h| Embedding.from_hash(h) } }
8
+ coerce :usage, with: ->(h) { EmbeddingUsage.from_hash(h) }
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ module GroqRuby
2
+ module Models
3
+ # A single embedding vector with its position in the request.
4
+ class Embedding < Data.define(:embedding, :index, :object)
5
+ extend ModelFactory
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module GroqRuby
2
+ module Models
3
+ # Token usage returned with an embedding response.
4
+ class EmbeddingUsage < Data.define(:prompt_tokens, :total_tokens)
5
+ extend ModelFactory
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module GroqRuby
2
+ module Models
3
+ # Acknowledgement for a successful file deletion.
4
+ class FileDeleted < Data.define(:id, :object, :deleted)
5
+ extend ModelFactory
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,10 @@
1
+ module GroqRuby
2
+ module Models
3
+ # Wrapper for a `files.list` response.
4
+ class FileList < Data.define(:data, :object)
5
+ extend ModelFactory
6
+
7
+ coerce :data, with: ->(arr) { Array(arr).map { |h| FileObject.from_hash(h) } }
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,8 @@
1
+ module GroqRuby
2
+ module Models
3
+ # Metadata for a single file uploaded to Groq.
4
+ class FileObject < Data.define(:id, :object, :bytes, :created_at, :filename, :purpose)
5
+ extend ModelFactory
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module GroqRuby
2
+ module Models
3
+ # Metadata for a single Groq model.
4
+ class Model < Data.define(:id, :object, :created, :owned_by, :active, :context_window, :public_apps, :max_completion_tokens)
5
+ extend ModelFactory
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module GroqRuby
2
+ module Models
3
+ # Acknowledgement for a successful model deletion.
4
+ class ModelDeleted < Data.define(:id, :object, :deleted)
5
+ extend ModelFactory
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,31 @@
1
+ module GroqRuby
2
+ module Models
3
+ # Tiny mixin that gives a Data subclass a `from_hash` constructor.
4
+ # Walks the Data class's `members` and pulls each key out of the
5
+ # incoming Hash (string-keyed). Unknown keys are ignored; missing keys
6
+ # default to `nil`. Per-attribute coercions are declared on the class:
7
+ #
8
+ # class MyModel < Data.define(:choices)
9
+ # extend ModelFactory
10
+ # coerce :choices, with: ->(arr) { arr.map { |h| Choice.from_hash(h) } }
11
+ # end
12
+ module ModelFactory
13
+ def coerce(attr, with:)
14
+ coercions[attr] = with
15
+ end
16
+
17
+ def coercions
18
+ @coercions ||= {}
19
+ end
20
+
21
+ def from_hash(hash)
22
+ return nil if hash.nil?
23
+ attrs = members.each_with_object({}) do |name, acc|
24
+ raw = hash[name.to_s] || hash[name]
25
+ acc[name] = coercions.key?(name) ? coercions[name].call(raw) : raw
26
+ end
27
+ new(**attrs)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,10 @@
1
+ module GroqRuby
2
+ module Models
3
+ # Wrapper for a `models.list` response.
4
+ class ModelList < Data.define(:data, :object)
5
+ extend ModelFactory
6
+
7
+ coerce :data, with: ->(arr) { Array(arr).map { |h| Model.from_hash(h) } }
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ module GroqRuby
2
+ module Models
3
+ # Token + timing usage breakdown returned with chat completions.
4
+ class Usage < Data.define(
5
+ :prompt_tokens, :completion_tokens, :total_tokens,
6
+ :prompt_time, :completion_time, :total_time, :queue_time
7
+ )
8
+ extend ModelFactory
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,84 @@
1
+ require "securerandom"
2
+
3
+ module GroqRuby
4
+ # Encodes a list of fields into a multipart/form-data body. Used by the
5
+ # file upload endpoints (audio transcription/translation, files.create).
6
+ # Single-purpose — builds the body and content-type header. Does no IO.
7
+ #
8
+ # @example
9
+ # parts = [
10
+ # Multipart::Part.new("model", "whisper-large-v3"),
11
+ # Multipart::Part.file("file", io: file_io, filename: "audio.mp3", content_type: "audio/mpeg")
12
+ # ]
13
+ # multipart = Multipart.new(parts)
14
+ # request_body = multipart.body
15
+ # header_value = multipart.content_type
16
+ class Multipart
17
+ # A single field inside a multipart payload.
18
+ Part = Data.define(:name, :value, :filename, :content_type) do
19
+ # Build a plain text field.
20
+ # @param name [String]
21
+ # @param value [#to_s]
22
+ # @return [Part]
23
+ def self.text(name, value)
24
+ new(name: name, value: value.to_s, filename: nil, content_type: nil)
25
+ end
26
+
27
+ # Build a file upload field.
28
+ # @param name [String]
29
+ # @param io [#read] readable IO whose `#read` returns the bytes.
30
+ # @param filename [String]
31
+ # @param content_type [String]
32
+ # @return [Part]
33
+ def self.file(name, io:, filename:, content_type: "application/octet-stream")
34
+ new(name: name, value: io, filename: filename, content_type: content_type)
35
+ end
36
+
37
+ # @return [Boolean] true if this part is a file upload (has a filename)
38
+ def file?
39
+ !filename.nil?
40
+ end
41
+ end
42
+
43
+ # @return [String] the random boundary string used to separate parts
44
+ attr_reader :boundary
45
+
46
+ # @param parts [Array<Part>] the fields to encode
47
+ # @param boundary [String] override the auto-generated boundary
48
+ def initialize(parts, boundary: SecureRandom.hex(16))
49
+ @parts = parts
50
+ @boundary = boundary
51
+ end
52
+
53
+ # @return [String] the encoded multipart body, suitable for `Net::HTTP::Post#body=`
54
+ def body
55
+ buffer = +""
56
+ @parts.each { |p| buffer << encode_part(p) }
57
+ buffer << "--#{@boundary}--\r\n"
58
+ buffer
59
+ end
60
+
61
+ # @return [String] the value to use for the `Content-Type` header
62
+ def content_type
63
+ "multipart/form-data; boundary=#{@boundary}"
64
+ end
65
+
66
+ private
67
+
68
+ def encode_part(part)
69
+ header = "--#{@boundary}\r\n"
70
+ header << if part.file?
71
+ %(Content-Disposition: form-data; name="#{part.name}"; filename="#{part.filename}"\r\n) +
72
+ "Content-Type: #{part.content_type}\r\n\r\n"
73
+ else
74
+ %(Content-Disposition: form-data; name="#{part.name}"\r\n\r\n)
75
+ end
76
+ header << read_value(part.value)
77
+ header << "\r\n"
78
+ end
79
+
80
+ def read_value(value)
81
+ value.respond_to?(:read) ? value.read : value.to_s
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,13 @@
1
+ module GroqRuby
2
+ # Value object describing a single HTTP request. Built by resources,
3
+ # consumed by {Transport}. Immutable.
4
+ Request = Data.define(:method, :path, :query, :body, :headers, :content_type) do
5
+ def initialize(method:, path:, query: nil, body: nil, headers: {}, content_type: nil)
6
+ super
7
+ end
8
+
9
+ def with_headers(extra)
10
+ with(headers: headers.merge(extra))
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,32 @@
1
+ require "dry/schema"
2
+
3
+ module GroqRuby
4
+ module Resources
5
+ class Audio
6
+ # `client.audio.speech.create(...)` — text-to-speech. Returns the
7
+ # raw audio bytes (caller decides where to write them).
8
+ class Speech < Base
9
+ PATH = "/openai/v1/audio/speech".freeze
10
+
11
+ SCHEMA = Dry::Schema.define do
12
+ required(:input).filled(:string)
13
+ required(:model).filled(:string)
14
+ required(:voice).filled(:string)
15
+ optional(:response_format).filled(:string, included_in?: %w[flac mp3 mulaw ogg wav])
16
+ optional(:sample_rate).filled(:integer, included_in?: [8000, 16000, 22050, 24000, 32000, 44100, 48000])
17
+ optional(:speed).filled(:float)
18
+ end
19
+
20
+ # @param params [Hash] required: `:input` (text), `:model`, `:voice`.
21
+ # Optional: `:response_format` (`"flac"` | `"mp3"` | `"mulaw"` |
22
+ # `"ogg"` | `"wav"`), `:sample_rate`, `:speed`.
23
+ # @return [String] raw audio bytes — write with `File.binwrite`.
24
+ # @raise [ParameterError] on invalid params.
25
+ def create(**params)
26
+ body = validate!(SCHEMA, params)
27
+ perform(Request.new(method: :post, path: PATH, body: body), accept: :binary)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,48 @@
1
+ require "dry/schema"
2
+
3
+ module GroqRuby
4
+ module Resources
5
+ class Audio
6
+ # `client.audio.transcriptions.create(...)` — speech to text. Either
7
+ # an in-memory `file:` IO or a remote `url:` must be supplied.
8
+ class Transcriptions < Base
9
+ PATH = "/openai/v1/audio/transcriptions".freeze
10
+
11
+ SCHEMA = Dry::Schema.define do
12
+ required(:model).filled(:string)
13
+ optional(:language).filled(:string)
14
+ optional(:prompt).filled(:string)
15
+ optional(:response_format).filled(:string, included_in?: %w[json text srt verbose_json vtt])
16
+ optional(:temperature).filled(:float, gteq?: 0.0, lteq?: 1.0)
17
+ optional(:timestamp_granularities).value(:array)
18
+ end
19
+
20
+ # @param file [IO, nil] readable IO or nil if `url:` is given.
21
+ # @param filename [String, nil] filename for the multipart upload.
22
+ # @param url [String, nil] URL the API can fetch instead of an upload.
23
+ # @param params [Hash] required: `:model`. Optional: `:language`,
24
+ # `:prompt`, `:response_format`, `:temperature`,
25
+ # `:timestamp_granularities`.
26
+ # @return [GroqRuby::Models::Transcription]
27
+ # @raise [ParameterError] when neither `file:` nor `url:` is given,
28
+ # or when other params fail validation.
29
+ def create(file: nil, filename: nil, url: nil, **params)
30
+ raise ParameterError, {file: ["either file or url is required"]} if file.nil? && url.nil?
31
+ body = validate!(SCHEMA, params)
32
+ multipart = build_multipart(file: file, filename: filename, url: url, body: body)
33
+ request = Request.new(method: :post, path: PATH, body: multipart.body, content_type: multipart.content_type)
34
+ GroqRuby::Models::Transcription.from_hash(perform(request))
35
+ end
36
+
37
+ private
38
+
39
+ def build_multipart(file:, filename:, url:, body:)
40
+ parts = body.map { |k, v| Multipart::Part.text(k.to_s, v) }
41
+ parts << Multipart::Part.text("url", url) if url
42
+ parts << Multipart::Part.file("file", io: file, filename: filename || "audio", content_type: "application/octet-stream") if file
43
+ Multipart.new(parts)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,45 @@
1
+ require "dry/schema"
2
+
3
+ module GroqRuby
4
+ module Resources
5
+ class Audio
6
+ # `client.audio.translations.create(...)` — translates speech in any
7
+ # supported language into English text.
8
+ class Translations < Base
9
+ PATH = "/openai/v1/audio/translations".freeze
10
+
11
+ SCHEMA = Dry::Schema.define do
12
+ required(:model).filled(:string)
13
+ optional(:prompt).filled(:string)
14
+ optional(:response_format).filled(:string, included_in?: %w[json text srt verbose_json vtt])
15
+ optional(:temperature).filled(:float, gteq?: 0.0, lteq?: 1.0)
16
+ end
17
+
18
+ # @param file [IO, nil] readable IO or nil if `url:` is given.
19
+ # @param filename [String, nil] filename for the multipart upload.
20
+ # @param url [String, nil] URL the API can fetch instead of an upload.
21
+ # @param params [Hash] required: `:model`. Optional: `:prompt`,
22
+ # `:response_format`, `:temperature`.
23
+ # @return [GroqRuby::Models::Translation]
24
+ # @raise [ParameterError] when neither `file:` nor `url:` is given,
25
+ # or when other params fail validation.
26
+ def create(file: nil, filename: nil, url: nil, **params)
27
+ raise ParameterError, {file: ["either file or url is required"]} if file.nil? && url.nil?
28
+ body = validate!(SCHEMA, params)
29
+ multipart = build_multipart(file: file, filename: filename, url: url, body: body)
30
+ request = Request.new(method: :post, path: PATH, body: multipart.body, content_type: multipart.content_type)
31
+ GroqRuby::Models::Translation.from_hash(perform(request))
32
+ end
33
+
34
+ private
35
+
36
+ def build_multipart(file:, filename:, url:, body:)
37
+ parts = body.map { |k, v| Multipart::Part.text(k.to_s, v) }
38
+ parts << Multipart::Part.text("url", url) if url
39
+ parts << Multipart::Part.file("file", io: file, filename: filename || "audio", content_type: "application/octet-stream") if file
40
+ Multipart.new(parts)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,26 @@
1
+ module GroqRuby
2
+ module Resources
3
+ # Namespace for the three audio sub-resources: speech, transcriptions,
4
+ # and translations.
5
+ class Audio
6
+ def initialize(transport)
7
+ @transport = transport
8
+ end
9
+
10
+ # @return [Audio::Speech]
11
+ def speech
12
+ @speech ||= Audio::Speech.new(@transport)
13
+ end
14
+
15
+ # @return [Audio::Transcriptions]
16
+ def transcriptions
17
+ @transcriptions ||= Audio::Transcriptions.new(@transport)
18
+ end
19
+
20
+ # @return [Audio::Translations]
21
+ def translations
22
+ @translations ||= Audio::Translations.new(@transport)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,33 @@
1
+ module GroqRuby
2
+ module Resources
3
+ # Common scaffolding shared by every resource: holds the transport and
4
+ # exposes a small set of helpers for building requests and surfacing
5
+ # validation/transport errors.
6
+ class Base
7
+ def initialize(transport)
8
+ @transport = transport
9
+ end
10
+
11
+ private
12
+
13
+ attr_reader :transport
14
+
15
+ # Run a dry-schema against caller params; raise {ParameterError} on
16
+ # failure so callers get a single, well-typed error to rescue.
17
+ def validate!(schema, params)
18
+ result = schema.call(params)
19
+ raise ParameterError, result.errors.to_h if result.failure?
20
+ result.to_h
21
+ end
22
+
23
+ # Send a buffered request and surface either the parsed body Hash or
24
+ # the raw String (for binary endpoints). Re-raises the {APIError}
25
+ # carried by a Failure result so resources stay raise-on-error.
26
+ def perform(request, accept: :json)
27
+ result = @transport.call(request, accept: accept)
28
+ raise result.failure if result.failure?
29
+ result.value!.body
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,44 @@
1
+ require "dry/schema"
2
+
3
+ module GroqRuby
4
+ module Resources
5
+ # `client.batches.*` — async batch processing of chat completions or
6
+ # embeddings against an uploaded JSONL file.
7
+ class Batches < Base
8
+ BASE_PATH = "/openai/v1/batches".freeze
9
+
10
+ CREATE_SCHEMA = Dry::Schema.define do
11
+ required(:input_file_id).filled(:string)
12
+ required(:endpoint).filled(:string)
13
+ required(:completion_window).filled(:string)
14
+ optional(:metadata).value(:hash)
15
+ end
16
+
17
+ # @param params [Hash] required: `:input_file_id`, `:endpoint`,
18
+ # `:completion_window` (e.g. `"24h"`). Optional: `:metadata`.
19
+ # @return [GroqRuby::Models::Batch]
20
+ # @raise [ParameterError] on invalid params.
21
+ def create(**params)
22
+ body = validate!(CREATE_SCHEMA, params)
23
+ GroqRuby::Models::Batch.from_hash(perform(Request.new(method: :post, path: BASE_PATH, body: body)))
24
+ end
25
+
26
+ # @param batch_id [String]
27
+ # @return [GroqRuby::Models::Batch]
28
+ def retrieve(batch_id)
29
+ GroqRuby::Models::Batch.from_hash(perform(Request.new(method: :get, path: "#{BASE_PATH}/#{batch_id}")))
30
+ end
31
+
32
+ # @return [GroqRuby::Models::BatchList]
33
+ def list
34
+ GroqRuby::Models::BatchList.from_hash(perform(Request.new(method: :get, path: BASE_PATH)))
35
+ end
36
+
37
+ # @param batch_id [String]
38
+ # @return [GroqRuby::Models::Batch] with `status` advanced toward `cancelled`
39
+ def cancel(batch_id)
40
+ GroqRuby::Models::Batch.from_hash(perform(Request.new(method: :post, path: "#{BASE_PATH}/#{batch_id}/cancel")))
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,94 @@
1
+ require "dry/schema"
2
+
3
+ module GroqRuby
4
+ module Resources
5
+ class Chat
6
+ # `client.chat.completions.create(...)` — the workhorse of the API.
7
+ # Supports both buffered and Server-Sent-Events streaming responses.
8
+ #
9
+ # @example Buffered call
10
+ # resp = client.chat.completions.create(
11
+ # model: "llama-3.3-70b-versatile",
12
+ # messages: [{role: "user", content: "Hello"}]
13
+ # )
14
+ # resp.choices.first.message.content
15
+ #
16
+ # @example Streaming call (block form)
17
+ # client.chat.completions.create(
18
+ # model: "llama-3.3-70b-versatile",
19
+ # messages: [{role: "user", content: "Hello"}],
20
+ # stream: true
21
+ # ) { |chunk| print chunk.choices.first.delta.content }
22
+ class Completions < Base
23
+ PATH = "/openai/v1/chat/completions".freeze
24
+
25
+ SCHEMA = Dry::Schema.define do
26
+ required(:model).filled(:string)
27
+ required(:messages).value(:array, min_size?: 1)
28
+ optional(:temperature).filled(:float, gteq?: 0.0, lteq?: 2.0)
29
+ optional(:top_p).filled(:float, gteq?: 0.0, lteq?: 1.0)
30
+ optional(:n).filled(:integer, gt?: 0)
31
+ optional(:max_tokens).filled(:integer, gt?: 0)
32
+ optional(:max_completion_tokens).filled(:integer, gt?: 0)
33
+ optional(:presence_penalty).filled(:float, gteq?: -2.0, lteq?: 2.0)
34
+ optional(:frequency_penalty).filled(:float, gteq?: -2.0, lteq?: 2.0)
35
+ optional(:seed).filled(:integer)
36
+ optional(:stop)
37
+ optional(:stream).filled(:bool)
38
+ optional(:user).maybe(:string)
39
+ optional(:tools).value(:array)
40
+ optional(:tool_choice)
41
+ optional(:response_format).value(:hash)
42
+ optional(:logprobs).filled(:bool)
43
+ optional(:top_logprobs).filled(:integer, gteq?: 0, lteq?: 20)
44
+ optional(:reasoning_effort).filled(:string, included_in?: %w[none default low medium high])
45
+ optional(:reasoning_format).filled(:string, included_in?: %w[hidden raw parsed])
46
+ optional(:service_tier).filled(:string, included_in?: %w[auto on_demand flex performance])
47
+ optional(:parallel_tool_calls).filled(:bool)
48
+ optional(:logit_bias).value(:hash)
49
+ optional(:metadata).value(:hash)
50
+ end
51
+
52
+ # @param stream [Boolean] if true, response is streamed via SSE.
53
+ # @param params [Hash] all chat completion parameters supported by
54
+ # the Groq API. Required: `:model`, `:messages`. Many optional:
55
+ # `:temperature`, `:top_p`, `:n`, `:max_completion_tokens`, `:stop`,
56
+ # `:tools`, `:tool_choice`, `:response_format`, `:reasoning_effort`,
57
+ # `:reasoning_format`, `:service_tier`, `:seed`, `:logit_bias`,
58
+ # `:metadata`, etc.
59
+ # @yieldparam chunk [GroqRuby::Models::ChatCompletionChunk]
60
+ # yielded for each event when `stream: true`.
61
+ # @return [GroqRuby::Models::ChatCompletion] when buffered.
62
+ # @return [GroqRuby::Streaming::ChunkStream] when `stream: true`
63
+ # and no block given. Returns nil when streaming with a block.
64
+ # @raise [ParameterError] when params fail dry-schema validation.
65
+ # @raise [APIStatusError] subclass on 4xx/5xx response.
66
+ # @raise [APIConnectionError] on network failure.
67
+ def create(stream: false, **params, &block)
68
+ body = validate!(SCHEMA, params.merge(stream: stream))
69
+ if stream
70
+ stream_response(body, &block)
71
+ else
72
+ buffered_response(body)
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def buffered_response(body)
79
+ payload = perform(Request.new(method: :post, path: PATH, body: body))
80
+ GroqRuby::Models::ChatCompletion.from_hash(payload)
81
+ end
82
+
83
+ def stream_response(body, &block)
84
+ chunks = Streaming::ChunkStream.new(
85
+ transport: transport,
86
+ request: Request.new(method: :post, path: PATH, body: body, headers: {"Accept" => "text/event-stream"}),
87
+ chunk_model: GroqRuby::Models::ChatCompletionChunk
88
+ )
89
+ block ? chunks.each(&block) : chunks
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,16 @@
1
+ module GroqRuby
2
+ module Resources
3
+ # Namespace for the chat sub-resources (only `completions` for now).
4
+ # Mirrors `client.chat.completions.create(...)` from the python SDK.
5
+ class Chat
6
+ def initialize(transport)
7
+ @transport = transport
8
+ end
9
+
10
+ # @return [Chat::Completions]
11
+ def completions
12
+ @completions ||= Chat::Completions.new(@transport)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,28 @@
1
+ require "dry/schema"
2
+
3
+ module GroqRuby
4
+ module Resources
5
+ # `client.embeddings.create(...)` — turns text into vectors.
6
+ class Embeddings < Base
7
+ PATH = "/openai/v1/embeddings".freeze
8
+
9
+ SCHEMA = Dry::Schema.define do
10
+ required(:model).filled(:string)
11
+ required(:input)
12
+ optional(:encoding_format).filled(:string, included_in?: %w[float base64])
13
+ optional(:user).maybe(:string)
14
+ end
15
+
16
+ # @param params [Hash] required: `:model`, `:input` (String or Array of Strings).
17
+ # Optional: `:encoding_format` (`"float"` | `"base64"`), `:user`.
18
+ # @return [GroqRuby::Models::CreateEmbeddingResponse]
19
+ # @raise [ParameterError] when params fail validation.
20
+ # @raise [APIStatusError] subclass on 4xx/5xx response.
21
+ def create(**params)
22
+ body = validate!(SCHEMA, params)
23
+ payload = perform(Request.new(method: :post, path: PATH, body: body))
24
+ GroqRuby::Models::CreateEmbeddingResponse.from_hash(payload)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,55 @@
1
+ require "dry/schema"
2
+
3
+ module GroqRuby
4
+ module Resources
5
+ # `client.files.*` — upload, list, fetch, and delete files used by the
6
+ # batch endpoints.
7
+ class Files < Base
8
+ BASE_PATH = "/openai/v1/files".freeze
9
+
10
+ CREATE_SCHEMA = Dry::Schema.define do
11
+ required(:purpose).filled(:string, included_in?: %w[batch])
12
+ end
13
+
14
+ # Upload a JSONL file to be referenced from a batch job.
15
+ #
16
+ # @param file [IO, File] readable IO whose `#read` returns the bytes.
17
+ # @param filename [String] name to send with the multipart upload.
18
+ # @param purpose [String] currently always `"batch"`.
19
+ # @return [GroqRuby::Models::FileObject]
20
+ # @raise [ParameterError] when `purpose` is not `"batch"`.
21
+ def create(file:, filename:, purpose:)
22
+ validate!(CREATE_SCHEMA, {purpose: purpose})
23
+ multipart = Multipart.new([
24
+ Multipart::Part.text("purpose", purpose),
25
+ Multipart::Part.file("file", io: file, filename: filename, content_type: "application/jsonl")
26
+ ])
27
+ request = Request.new(method: :post, path: BASE_PATH, body: multipart.body, content_type: multipart.content_type)
28
+ GroqRuby::Models::FileObject.from_hash(perform(request))
29
+ end
30
+
31
+ # @return [GroqRuby::Models::FileList]
32
+ def list
33
+ GroqRuby::Models::FileList.from_hash(perform(Request.new(method: :get, path: BASE_PATH)))
34
+ end
35
+
36
+ # @param file_id [String]
37
+ # @return [GroqRuby::Models::FileObject]
38
+ def info(file_id)
39
+ GroqRuby::Models::FileObject.from_hash(perform(Request.new(method: :get, path: "#{BASE_PATH}/#{file_id}")))
40
+ end
41
+
42
+ # @param file_id [String]
43
+ # @return [String] the raw bytes of the file
44
+ def content(file_id)
45
+ perform(Request.new(method: :get, path: "#{BASE_PATH}/#{file_id}/content"), accept: :binary)
46
+ end
47
+
48
+ # @param file_id [String]
49
+ # @return [GroqRuby::Models::FileDeleted]
50
+ def delete(file_id)
51
+ GroqRuby::Models::FileDeleted.from_hash(perform(Request.new(method: :delete, path: "#{BASE_PATH}/#{file_id}")))
52
+ end
53
+ end
54
+ end
55
+ end