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.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +57 -0
- data/CLAUDE.md +103 -0
- data/LICENSE.txt +21 -0
- data/README.md +495 -0
- data/Rakefile +11 -0
- data/examples/README.md +39 -0
- data/examples/batch.rb +29 -0
- data/examples/chat_completion.rb +24 -0
- data/examples/chat_completion_stop.rb +19 -0
- data/examples/chat_completion_streaming.rb +23 -0
- data/examples/embedding.rb +20 -0
- data/examples/error_handling.rb +27 -0
- data/examples/file_upload.rb +23 -0
- data/examples/mcp_agent.rb +63 -0
- data/examples/mcp_chat_with_tools.rb +103 -0
- data/examples/mcp_resources_and_prompts.rb +89 -0
- data/examples/models_list.rb +16 -0
- data/examples/speech.rb +23 -0
- data/examples/transcription.rb +23 -0
- data/examples/translation.rb +22 -0
- data/lib/groq_ruby/client.rb +69 -0
- data/lib/groq_ruby/configuration.rb +62 -0
- data/lib/groq_ruby/error_mapper.rb +37 -0
- data/lib/groq_ruby/errors/api_connection_error.rb +8 -0
- data/lib/groq_ruby/errors/api_error.rb +14 -0
- data/lib/groq_ruby/errors/api_response_error.rb +5 -0
- data/lib/groq_ruby/errors/api_status_error.rb +23 -0
- data/lib/groq_ruby/errors/api_timeout_error.rb +8 -0
- data/lib/groq_ruby/errors/authentication_error.rb +4 -0
- data/lib/groq_ruby/errors/bad_request_error.rb +4 -0
- data/lib/groq_ruby/errors/configuration_error.rb +4 -0
- data/lib/groq_ruby/errors/conflict_error.rb +4 -0
- data/lib/groq_ruby/errors/error.rb +5 -0
- data/lib/groq_ruby/errors/internal_server_error.rb +4 -0
- data/lib/groq_ruby/errors/not_found_error.rb +4 -0
- data/lib/groq_ruby/errors/parameter_error.rb +13 -0
- data/lib/groq_ruby/errors/permission_denied_error.rb +4 -0
- data/lib/groq_ruby/errors/rate_limit_error.rb +4 -0
- data/lib/groq_ruby/errors/unprocessable_entity_error.rb +4 -0
- data/lib/groq_ruby/mcp/bridge.rb +239 -0
- data/lib/groq_ruby/mcp/claude_desktop_config.rb +79 -0
- data/lib/groq_ruby/mcp/client.rb +171 -0
- data/lib/groq_ruby/mcp/errors/error.rb +7 -0
- data/lib/groq_ruby/mcp/errors/json_rpc_error.rb +21 -0
- data/lib/groq_ruby/mcp/errors/protocol_error.rb +7 -0
- data/lib/groq_ruby/mcp/errors/timeout_error.rb +7 -0
- data/lib/groq_ruby/mcp/errors/transport_error.rb +6 -0
- data/lib/groq_ruby/mcp/errors/unknown_tool_error.rb +7 -0
- data/lib/groq_ruby/mcp/json_rpc.rb +51 -0
- data/lib/groq_ruby/mcp/prompt.rb +21 -0
- data/lib/groq_ruby/mcp/resource.rb +17 -0
- data/lib/groq_ruby/mcp/server_config.rb +22 -0
- data/lib/groq_ruby/mcp/tool.rb +22 -0
- data/lib/groq_ruby/mcp/transport.rb +32 -0
- data/lib/groq_ruby/mcp/transports/stdio.rb +100 -0
- data/lib/groq_ruby/mcp.rb +25 -0
- data/lib/groq_ruby/models/audio/transcription.rb +10 -0
- data/lib/groq_ruby/models/audio/translation.rb +8 -0
- data/lib/groq_ruby/models/batches/batch.rb +16 -0
- data/lib/groq_ruby/models/batches/batch_list.rb +10 -0
- data/lib/groq_ruby/models/batches/batch_request_counts.rb +8 -0
- data/lib/groq_ruby/models/chat/chat_completion.rb +14 -0
- data/lib/groq_ruby/models/chat/chat_completion_choice.rb +10 -0
- data/lib/groq_ruby/models/chat/chat_completion_chunk.rb +13 -0
- data/lib/groq_ruby/models/chat/chat_completion_chunk_choice.rb +10 -0
- data/lib/groq_ruby/models/chat/chat_completion_delta.rb +8 -0
- data/lib/groq_ruby/models/chat/chat_completion_message.rb +10 -0
- data/lib/groq_ruby/models/embeddings/create_embedding_response.rb +11 -0
- data/lib/groq_ruby/models/embeddings/embedding.rb +8 -0
- data/lib/groq_ruby/models/embeddings/embedding_usage.rb +8 -0
- data/lib/groq_ruby/models/files/file_deleted.rb +8 -0
- data/lib/groq_ruby/models/files/file_list.rb +10 -0
- data/lib/groq_ruby/models/files/file_object.rb +8 -0
- data/lib/groq_ruby/models/model.rb +8 -0
- data/lib/groq_ruby/models/model_deleted.rb +8 -0
- data/lib/groq_ruby/models/model_factory.rb +31 -0
- data/lib/groq_ruby/models/model_list.rb +10 -0
- data/lib/groq_ruby/models/usage.rb +11 -0
- data/lib/groq_ruby/multipart.rb +84 -0
- data/lib/groq_ruby/request.rb +13 -0
- data/lib/groq_ruby/resources/audio/speech.rb +32 -0
- data/lib/groq_ruby/resources/audio/transcriptions.rb +48 -0
- data/lib/groq_ruby/resources/audio/translations.rb +45 -0
- data/lib/groq_ruby/resources/audio.rb +26 -0
- data/lib/groq_ruby/resources/base.rb +33 -0
- data/lib/groq_ruby/resources/batches.rb +44 -0
- data/lib/groq_ruby/resources/chat/completions.rb +94 -0
- data/lib/groq_ruby/resources/chat.rb +16 -0
- data/lib/groq_ruby/resources/embeddings.rb +28 -0
- data/lib/groq_ruby/resources/files.rb +55 -0
- data/lib/groq_ruby/resources/models.rb +35 -0
- data/lib/groq_ruby/response.rb +9 -0
- data/lib/groq_ruby/streaming/chunk_stream.rb +58 -0
- data/lib/groq_ruby/streaming/event_parser.rb +23 -0
- data/lib/groq_ruby/transport.rb +169 -0
- data/lib/groq_ruby/version.rb +5 -0
- data/lib/groq_ruby.rb +36 -0
- data/lib/tasks/gem.rake +5 -0
- data/lib/tasks/lint/all.rake +11 -0
- data/lib/tasks/lint/rubocop.rake +15 -0
- data/lib/tasks/security.rake +11 -0
- data/lib/tasks/types.rake +11 -0
- data/sig/groq_ruby.rbs +191 -0
- data/sig/zeitwerk.rbs +13 -0
- data.tar.gz.sig +0 -0
- metadata +237 -0
- metadata.gz.sig +0 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: e37b875bee74a1cd5660dd886ae8aa670b964cd6f75822abf9d66c81060e6b81
|
|
4
|
+
data.tar.gz: a68e3933724ff8972efef22ab22ea3e5ceb1a657e29451008770c2f63f479068
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a97a4e32dae7e9d313da6d6f4dd75e570218f55296e2daf06c071c11230e90e75832ec646f5e6e3f4cdb92e8eaa687235bd9541d20b2e96c7d3a10b6bf9bdea1
|
|
7
|
+
data.tar.gz: a83b33d635b1b06ff1e157a21d3c9c317ca4bbb0d0793eb575277f1cf1b6750f0fdb8e28540d8d961e123995a223e99d380d15020f3d33967ecdfa372cac9cfe
|
checksums.yaml.gz.sig
ADDED
|
Binary file
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file. The
|
|
4
|
+
format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
5
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.1.0]
|
|
10
|
+
|
|
11
|
+
Initial release. Idiomatic Ruby client for the Groq API, mirroring the
|
|
12
|
+
surface of the [`groq-python`](https://github.com/groq/groq-python) SDK.
|
|
13
|
+
|
|
14
|
+
### Added — Groq API
|
|
15
|
+
|
|
16
|
+
- `GroqRuby::Client` exposes `chat`, `embeddings`, `audio`, `models`,
|
|
17
|
+
`files`, and `batches` resources.
|
|
18
|
+
- Chat completions: buffered responses and Server-Sent-Events streaming
|
|
19
|
+
(block form or `Enumerable`-returning `ChunkStream`).
|
|
20
|
+
- Function (tool) calling: pass `tools:` / `tool_choice:` directly to
|
|
21
|
+
`chat.completions.create`.
|
|
22
|
+
- Embeddings: `client.embeddings.create(model:, input:)`.
|
|
23
|
+
- Audio: `speech` (TTS, returns raw bytes), `transcriptions`, and
|
|
24
|
+
`translations` (multipart upload + Whisper).
|
|
25
|
+
- Models: `list`, `retrieve`, `delete`.
|
|
26
|
+
- Files lifecycle: `create`, `list`, `info`, `content` (binary), `delete`.
|
|
27
|
+
- Batches: `create`, `retrieve`, `list`, `cancel`.
|
|
28
|
+
|
|
29
|
+
### Added — MCP (Model Context Protocol)
|
|
30
|
+
|
|
31
|
+
- `GroqRuby::MCP::Client` — stdio MCP client with `initialize`
|
|
32
|
+
handshake, `tools/list`, `tools/call`, `resources/list`,
|
|
33
|
+
`resources/read`, `prompts/list`, `prompts/get`, `supports?`.
|
|
34
|
+
- `GroqRuby::MCP::Bridge` — wires one or more MCP servers into Groq's
|
|
35
|
+
`chat.completions(tools:)` parameter. Surfaces tools (namespaced
|
|
36
|
+
`<server>__<tool>`), resources (via a synthetic
|
|
37
|
+
`<server>__read_resource(uri)` tool), and prompts (`bridge.prompts`,
|
|
38
|
+
`bridge.get_prompt`). Optional capabilities are probed gracefully.
|
|
39
|
+
- `GroqRuby::MCP::ClaudeDesktopConfig` — loads the same
|
|
40
|
+
`claude_desktop_config.json` shape (with `${VAR}` expansion against
|
|
41
|
+
per-server `env` block first, then process `ENV`) and returns
|
|
42
|
+
`Array<ServerConfig>`.
|
|
43
|
+
|
|
44
|
+
### Added — infrastructure
|
|
45
|
+
|
|
46
|
+
- Typed response models (`Data` definitions, frozen) with recursive
|
|
47
|
+
`from_hash` coercion for nested choices/messages/usage/etc.
|
|
48
|
+
- Per-call parameter validation via `dry-schema` raising
|
|
49
|
+
`ParameterError` before any HTTP request fires.
|
|
50
|
+
- Internal transport pipeline uses `dry-monads` `Result` + Do notation
|
|
51
|
+
for short-circuiting on transport failures, mapped to a typed
|
|
52
|
+
exception hierarchy at the public boundary
|
|
53
|
+
(`AuthenticationError`, `RateLimitError`, `BadRequestError`, etc.).
|
|
54
|
+
- RBS signatures for the public API (`sig/groq_ruby.rbs`).
|
|
55
|
+
- `examples/` directory with one runnable script per major endpoint
|
|
56
|
+
plus three MCP variants (minimal, annotated walkthrough, full
|
|
57
|
+
resources+prompts coverage).
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## What this gem is
|
|
6
|
+
|
|
7
|
+
`groq_ruby` is an idiomatic Ruby client for the [Groq](https://groq.com)
|
|
8
|
+
API. It mirrors the surface of the official `groq-python` SDK and is
|
|
9
|
+
**not** an official Groq product — its wire protocol and resource
|
|
10
|
+
layout come from the publicly available python SDK at
|
|
11
|
+
<https://github.com/groq/groq-python>.
|
|
12
|
+
|
|
13
|
+
The `README.md` is the user-facing documentation. This file documents
|
|
14
|
+
the *internal* conventions and constraints — read it before changing
|
|
15
|
+
architecture, dependencies, or naming.
|
|
16
|
+
|
|
17
|
+
> Note: the parent directory `/Users/pablo/code/posiczko/CLAUDE.md`
|
|
18
|
+
> documents a different project (Ougai). It does not apply here.
|
|
19
|
+
|
|
20
|
+
## Architectural constraints
|
|
21
|
+
|
|
22
|
+
These are deliberate, non-negotiable choices. Don't change them without
|
|
23
|
+
explicit discussion:
|
|
24
|
+
|
|
25
|
+
- **No global state, no `GroqRuby.configure`.** Configuration is
|
|
26
|
+
per-`Client` instance. Multi-tenant code holds multiple clients.
|
|
27
|
+
Constructor reads `GROQ_API_KEY` / `GROQ_BASE_URL` from env as a
|
|
28
|
+
default — that's it.
|
|
29
|
+
- **`Net::HTTP` only.** No Faraday/HTTParty/http.rb dependency. Single
|
|
30
|
+
shared `GroqRuby::Transport` instance per client. Resources never
|
|
31
|
+
open their own sockets.
|
|
32
|
+
- **Internal control flow uses `dry-monads` `Result` + Do notation**
|
|
33
|
+
inside `Transport`. The *public* API raises typed exceptions
|
|
34
|
+
(`AuthenticationError`, `RateLimitError`, ...) — `Resources::Base`
|
|
35
|
+
unwraps the `Failure` and re-raises so callers don't see Result.
|
|
36
|
+
- **Per-call parameter validation via `dry-schema`** raises
|
|
37
|
+
`ParameterError` *before* any HTTP request fires. Schemas live next
|
|
38
|
+
to each resource (e.g. `Resources::Chat::Completions::SCHEMA`).
|
|
39
|
+
- **Response models are frozen `Data` classes** under
|
|
40
|
+
`GroqRuby::Models::*`, each with a `from_hash` factory via the
|
|
41
|
+
`ModelFactory` mixin. Coerce nested types via `coerce :field, with:`.
|
|
42
|
+
- **Single class per file** so Zeitwerk autoloads cleanly. The two
|
|
43
|
+
exceptions to "one constant per file" (the error families and the
|
|
44
|
+
per-domain model groups) live in collapsed sub-directories
|
|
45
|
+
(`lib/groq_ruby/errors/`, `lib/groq_ruby/models/<domain>/`); see the
|
|
46
|
+
`loader.collapse` calls in `lib/groq_ruby.rb`.
|
|
47
|
+
- **No `require_relative` in `lib/`.** Zeitwerk autoloads everything.
|
|
48
|
+
The gemspec's `require_relative "lib/groq_ruby/version"` is the only
|
|
49
|
+
exception (gemspec runs before zeitwerk; zeitwerk's `for_gem`
|
|
50
|
+
ignores `version.rb` automatically).
|
|
51
|
+
- **MCP integration is client-side orchestration**, not a Groq API
|
|
52
|
+
feature. The bridge speaks JSON-RPC 2.0 to MCP servers and converts
|
|
53
|
+
their tools to OpenAI-shaped function tools. Don't suggest adding a
|
|
54
|
+
request-side `mcp_servers:` parameter — Groq's API doesn't accept one.
|
|
55
|
+
|
|
56
|
+
## Layout
|
|
57
|
+
|
|
58
|
+
- `lib/groq_ruby.rb` — entry point: zeitwerk setup with inflections
|
|
59
|
+
(acronyms: `MCP`, `APIError` family) and collapsed sub-dirs.
|
|
60
|
+
- `lib/groq_ruby/{client,configuration,transport,request,response,multipart,error_mapper,version}.rb` — top-level plumbing.
|
|
61
|
+
- `lib/groq_ruby/errors/*.rb` — error classes (collapsed → `GroqRuby::*`).
|
|
62
|
+
- `lib/groq_ruby/resources/{chat,audio}/*.rb` and `lib/groq_ruby/resources/*.rb` — one class per API surface.
|
|
63
|
+
- `lib/groq_ruby/models/{chat,audio,embeddings,files,batches}/*.rb` — collapsed → `GroqRuby::Models::*`.
|
|
64
|
+
- `lib/groq_ruby/streaming/*.rb` — SSE adapter (`event_stream_parser`) + `ChunkStream` enumerable.
|
|
65
|
+
- `lib/groq_ruby/mcp/*.rb` — MCP client, bridge, transports, error families (collapsed). `MCP::PROTOCOL_VERSION` lives in `mcp.rb`.
|
|
66
|
+
- `sig/groq_ruby.rbs` — public-API RBS sigs. Keep in sync when adding public API.
|
|
67
|
+
- `test/**/test_*.rb` — Minitest. Real network calls are forbidden — every HTTP test stubs via WebMock; every MCP test uses `test/support/fake_mcp_transport.rb`.
|
|
68
|
+
- `examples/*.rb` — runnable scripts. Each starts with `require "bundler/setup"; require "groq_ruby"`.
|
|
69
|
+
|
|
70
|
+
## Commands
|
|
71
|
+
|
|
72
|
+
- `bin/setup` — `bundle install` wrapper.
|
|
73
|
+
- `bin/console` — IRB with the gem preloaded.
|
|
74
|
+
- `bundle exec rake` — default: `test` + `lint` (rubocop+standard) + `types:validate` (RBS validate). All three must pass.
|
|
75
|
+
- `bundle exec rake test` — Minitest suite.
|
|
76
|
+
- `bundle exec rake test TESTOPTS="--name=/pattern/"` — subset.
|
|
77
|
+
- `bundle exec rake lint` / `lint:all:autocorrect` — rubocop check / autofix.
|
|
78
|
+
- `bundle exec rake types:validate` — `rbs validate` against `sig/`.
|
|
79
|
+
- `bundle exec rake gem:build` / `install` / `release` — gem packaging (release is maintainer-only).
|
|
80
|
+
|
|
81
|
+
## Conventions
|
|
82
|
+
|
|
83
|
+
- Ruby 3.2+ (`.rubocop.yml` inherits from Standard; the gemspec pins `>= 3.2.0`).
|
|
84
|
+
- Style is Standard via Rubocop. Run autocorrect before committing.
|
|
85
|
+
- Prefer `git mv` when reorganising files so history is preserved.
|
|
86
|
+
- Bump `GroqRuby::VERSION` in `lib/groq_ruby/version.rb` for releases; the gemspec reads it via `require_relative`.
|
|
87
|
+
- Steep is intentionally **not** part of the default rake. Steep 2.0 crashes on `Data.define do ... end` blocks (upstream). RBS validate is enough for v1.
|
|
88
|
+
- CI (`.github/workflows/main.yml`) currently has a placeholder branch (`.invalid`) and a non-existent Ruby version (`'4.0.3'`) in the matrix — pre-existing scaffolding issue, fix when wiring CI for real.
|
|
89
|
+
|
|
90
|
+
## When extending the gem
|
|
91
|
+
|
|
92
|
+
- New API endpoint → new `Resources::...` class (sub-class of
|
|
93
|
+
`Resources::Base`) + a `Models::...` Data class for the response.
|
|
94
|
+
Mirror the python SDK's parameter names verbatim.
|
|
95
|
+
- New error condition → add a class under `lib/groq_ruby/errors/`
|
|
96
|
+
matching the file naming (one class per file). Update the README's
|
|
97
|
+
error-hierarchy table.
|
|
98
|
+
- New MCP capability → add a `Client` method first, then expose via
|
|
99
|
+
`Bridge` if the LLM should be able to use it through chat
|
|
100
|
+
completions. Probe optional capabilities gracefully (catch
|
|
101
|
+
`JsonRpcError` / `-32601`).
|
|
102
|
+
- New public method → add a YARD `@param`/`@return`/`@raise` and
|
|
103
|
+
update `sig/groq_ruby.rbs`.
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pawel Osiczko
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
# groq_ruby
|
|
2
|
+
|
|
3
|
+
Idiomatic Ruby client for the [Groq](https://groq.com) API.
|
|
4
|
+
|
|
5
|
+
`groq_ruby` mirrors the surface of the official
|
|
6
|
+
[`groq-python`](https://github.com/groq/groq-python) SDK in a Ruby-native
|
|
7
|
+
shape: typed response objects, single-purpose resource classes, internal
|
|
8
|
+
`dry-monads` `Result` pipelines, and request validation via
|
|
9
|
+
`dry-schema`. Streaming chat completions are supported via Server-Sent
|
|
10
|
+
Events. A built-in [MCP](https://modelcontextprotocol.io) client lets you
|
|
11
|
+
wire one or more MCP servers into a Groq chat completion as tools.
|
|
12
|
+
|
|
13
|
+
This gem is **not** an official Groq product. The wire protocol it
|
|
14
|
+
implements and the API surface it mirrors come from the publicly
|
|
15
|
+
available `groq-python` SDK.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
# Gemfile
|
|
21
|
+
gem "groq_ruby"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
bundle install
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick start
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
require "groq_ruby"
|
|
32
|
+
|
|
33
|
+
client = GroqRuby::Client.new # reads GROQ_API_KEY from the environment
|
|
34
|
+
|
|
35
|
+
response = client.chat.completions.create(
|
|
36
|
+
model: "llama-3.3-70b-versatile",
|
|
37
|
+
messages: [
|
|
38
|
+
{role: "system", content: "You are a helpful assistant."},
|
|
39
|
+
{role: "user", content: "Explain low-latency LLMs in one sentence."}
|
|
40
|
+
]
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
puts response.choices.first.message.content
|
|
44
|
+
puts "tokens: #{response.usage.total_tokens}"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Configuration
|
|
48
|
+
|
|
49
|
+
Configuration is per-client and immutable — no global state, no
|
|
50
|
+
`GroqRuby.configure`. Build one client per tenant or set of credentials.
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
client = GroqRuby::Client.new(
|
|
54
|
+
api_key: ENV["GROQ_API_KEY"], # default: ENV["GROQ_API_KEY"]
|
|
55
|
+
base_url: "https://api.groq.com", # default: ENV["GROQ_BASE_URL"] || "https://api.groq.com"
|
|
56
|
+
open_timeout: 10, # connect-phase timeout, seconds
|
|
57
|
+
read_timeout: 60, # socket-read timeout, seconds
|
|
58
|
+
user_agent: "myapp/1.0" # default: "groq_ruby/<version> (ruby; net-http)"
|
|
59
|
+
)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
A `ConfigurationError` is raised at construction time if no API key can
|
|
63
|
+
be found.
|
|
64
|
+
|
|
65
|
+
## Chat completions
|
|
66
|
+
|
|
67
|
+
### Buffered
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
response = client.chat.completions.create(
|
|
71
|
+
model: "llama-3.3-70b-versatile",
|
|
72
|
+
messages: [{role: "user", content: "Hello"}],
|
|
73
|
+
temperature: 0.5,
|
|
74
|
+
max_completion_tokens: 256
|
|
75
|
+
)
|
|
76
|
+
response.choices.first.message.content
|
|
77
|
+
response.usage.prompt_tokens
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Streaming
|
|
81
|
+
|
|
82
|
+
Pass `stream: true`. With a block, each chunk is yielded as it arrives.
|
|
83
|
+
Without a block, you get a lazy `Enumerable` you can iterate later.
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
client.chat.completions.create(
|
|
87
|
+
model: "llama-3.3-70b-versatile",
|
|
88
|
+
messages: [{role: "user", content: "Write a poem about latency."}],
|
|
89
|
+
stream: true
|
|
90
|
+
) do |chunk|
|
|
91
|
+
print chunk.choices.first.delta.content
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Or:
|
|
95
|
+
stream = client.chat.completions.create(model: "...", messages: [...], stream: true)
|
|
96
|
+
stream.each { |chunk| ... }
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Validation rejects out-of-range values before any HTTP request fires:
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
102
|
+
client.chat.completions.create(
|
|
103
|
+
model: "...", messages: [{role: "user", content: "x"}], temperature: 5.0
|
|
104
|
+
)
|
|
105
|
+
# => GroqRuby::ParameterError: invalid parameters: {:temperature=>["must be less than or equal to 2.0"]}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Function (tool) calling
|
|
109
|
+
|
|
110
|
+
Pass `tools:` (and optionally `tool_choice:`) just like the OpenAI/Groq
|
|
111
|
+
schema — `groq_ruby` doesn't transform them. When the model decides to
|
|
112
|
+
call a tool, it comes back in `response.choices.first.message.tool_calls`.
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
tools = [
|
|
116
|
+
{
|
|
117
|
+
type: "function",
|
|
118
|
+
function: {
|
|
119
|
+
name: "get_weather",
|
|
120
|
+
description: "Look up the current weather for a city",
|
|
121
|
+
parameters: {
|
|
122
|
+
type: "object",
|
|
123
|
+
properties: {city: {type: "string"}},
|
|
124
|
+
required: ["city"]
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
response = client.chat.completions.create(
|
|
131
|
+
model: "llama-3.3-70b-versatile",
|
|
132
|
+
messages: [{role: "user", content: "What's the weather in Berlin?"}],
|
|
133
|
+
tools: tools,
|
|
134
|
+
tool_choice: "auto" # "auto" | "required" | "none" | {type: "function", function: {name: "get_weather"}}
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
call = response.choices.first.message.tool_calls&.first
|
|
138
|
+
if call
|
|
139
|
+
args = JSON.parse(call["function"]["arguments"])
|
|
140
|
+
result = get_weather(args["city"])
|
|
141
|
+
|
|
142
|
+
# Feed the result back as a follow-up turn:
|
|
143
|
+
followup = client.chat.completions.create(
|
|
144
|
+
model: "llama-3.3-70b-versatile",
|
|
145
|
+
messages: [
|
|
146
|
+
*original_messages,
|
|
147
|
+
{role: "assistant", content: nil, tool_calls: response.choices.first.message.tool_calls},
|
|
148
|
+
{role: "tool", tool_call_id: call["id"], content: JSON.generate(result)}
|
|
149
|
+
]
|
|
150
|
+
)
|
|
151
|
+
end
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
For an MCP-driven version of this loop (where the tools come from an
|
|
155
|
+
MCP server instead of being hand-defined), see the [MCP section below](#mcp--model-context-protocol).
|
|
156
|
+
|
|
157
|
+
## Embeddings
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
response = client.embeddings.create(
|
|
161
|
+
model: "nomic-embed-text-v1_5",
|
|
162
|
+
input: "Groq makes inference very fast."
|
|
163
|
+
)
|
|
164
|
+
vector = response.data.first.embedding
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Audio
|
|
168
|
+
|
|
169
|
+
### Text → speech
|
|
170
|
+
|
|
171
|
+
```ruby
|
|
172
|
+
audio_bytes = client.audio.speech.create(
|
|
173
|
+
input: "Hello from Groq.",
|
|
174
|
+
model: "playai-tts",
|
|
175
|
+
voice: "Aaliyah-PlayAI",
|
|
176
|
+
response_format: "wav"
|
|
177
|
+
)
|
|
178
|
+
File.binwrite("speech.wav", audio_bytes)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Speech → text (transcription)
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
response = File.open("audio.mp3", "rb") do |file|
|
|
185
|
+
client.audio.transcriptions.create(
|
|
186
|
+
file: file,
|
|
187
|
+
filename: "audio.mp3",
|
|
188
|
+
model: "whisper-large-v3-turbo"
|
|
189
|
+
)
|
|
190
|
+
end
|
|
191
|
+
puts response.text
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Speech → English text (translation)
|
|
195
|
+
|
|
196
|
+
```ruby
|
|
197
|
+
response = File.open("audio.mp3", "rb") do |file|
|
|
198
|
+
client.audio.translations.create(
|
|
199
|
+
file: file,
|
|
200
|
+
filename: "audio.mp3",
|
|
201
|
+
model: "whisper-large-v3"
|
|
202
|
+
)
|
|
203
|
+
end
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Models
|
|
207
|
+
|
|
208
|
+
```ruby
|
|
209
|
+
client.models.list.data.each { |m| puts m.id }
|
|
210
|
+
client.models.retrieve("llama-3.3-70b-versatile")
|
|
211
|
+
client.models.delete("custom-model-id")
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Files
|
|
215
|
+
|
|
216
|
+
```ruby
|
|
217
|
+
uploaded = File.open("requests.jsonl", "rb") do |file|
|
|
218
|
+
client.files.create(file: file, filename: "requests.jsonl", purpose: "batch")
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
client.files.list.data
|
|
222
|
+
client.files.info(uploaded.id)
|
|
223
|
+
client.files.content(uploaded.id) # raw bytes
|
|
224
|
+
client.files.delete(uploaded.id)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Batches
|
|
228
|
+
|
|
229
|
+
```ruby
|
|
230
|
+
batch = client.batches.create(
|
|
231
|
+
input_file_id: uploaded.id,
|
|
232
|
+
endpoint: "/v1/chat/completions",
|
|
233
|
+
completion_window: "24h"
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
client.batches.retrieve(batch.id)
|
|
237
|
+
client.batches.list
|
|
238
|
+
client.batches.cancel(batch.id)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## MCP — Model Context Protocol
|
|
242
|
+
|
|
243
|
+
`groq_ruby` ships with a stdio MCP client, so a Groq agent can use tools
|
|
244
|
+
exposed by any MCP-compatible server (filesystem, web, custom tooling).
|
|
245
|
+
Coverage matches what host applications like Claude Desktop surface:
|
|
246
|
+
|
|
247
|
+
| MCP capability | Coverage |
|
|
248
|
+
|-------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
249
|
+
| **Tools** | Surfaced as Groq function tools, namespaced `<server>__<tool>` |
|
|
250
|
+
| **Resources** | Surfaced via a synthetic `<server>__read_resource(uri)` tool the LLM can call. `bridge.resources` returns the inventory if you want to advertise specific URIs in your system prompt |
|
|
251
|
+
| **Prompts** | Listed via `bridge.prompts` for *your application* to surface (e.g. as a picker in your UI) and rendered via `bridge.get_prompt(name, args)` |
|
|
252
|
+
| Sampling, notifications | Not supported in v1 |
|
|
253
|
+
|
|
254
|
+
Optional capabilities are probed gracefully — if a server doesn't
|
|
255
|
+
implement `resources/list` or `prompts/list`, those entries are simply
|
|
256
|
+
empty for that server.
|
|
257
|
+
|
|
258
|
+
### Configuring servers
|
|
259
|
+
|
|
260
|
+
Build `ServerConfig` directly, or load from the same JSON shape Claude
|
|
261
|
+
Desktop uses (`mcpServers` block). The Claude Desktop adapter expands
|
|
262
|
+
`${VAR}` references against each server's `env` block first, then the
|
|
263
|
+
process's `ENV`, and raises on unresolved references.
|
|
264
|
+
|
|
265
|
+
```ruby
|
|
266
|
+
# (a) direct
|
|
267
|
+
config = GroqRuby::MCP::ServerConfig.new(
|
|
268
|
+
name: "fs",
|
|
269
|
+
command: "npx",
|
|
270
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/docs"]
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
# (b) load Claude Desktop JSON
|
|
274
|
+
configs = GroqRuby::MCP::ClaudeDesktopConfig.load(
|
|
275
|
+
"~/Library/Application Support/Claude/claude_desktop_config.json"
|
|
276
|
+
)
|
|
277
|
+
bridge = GroqRuby::MCP::Bridge.new(configs)
|
|
278
|
+
|
|
279
|
+
# (c) parse an in-memory hash (e.g. for a private server with a PAT)
|
|
280
|
+
configs = GroqRuby::MCP::ClaudeDesktopConfig.parse({
|
|
281
|
+
"mcpServers" => {
|
|
282
|
+
"spectrum-ferret-staging" => {
|
|
283
|
+
"command" => "npx",
|
|
284
|
+
"args" => [
|
|
285
|
+
"-y", "mcp-remote@latest", "https://mcp-staging.spectrumferret.com",
|
|
286
|
+
"--header", "Authorization: Bearer ${SF_PAT}"
|
|
287
|
+
],
|
|
288
|
+
"env" => {"SF_PAT" => ENV.fetch("SF_PAT")}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
})
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Direct usage
|
|
295
|
+
|
|
296
|
+
```ruby
|
|
297
|
+
config = GroqRuby::MCP::ServerConfig.new(
|
|
298
|
+
name: "fs",
|
|
299
|
+
command: "npx",
|
|
300
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/docs"]
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
mcp = GroqRuby::MCP::Client.connect(config)
|
|
304
|
+
mcp.tools_list # [Tool(name: "read_file", ...), ...]
|
|
305
|
+
mcp.tools_call(name: "read_file", arguments: {path: "/Users/me/docs/foo.txt"})
|
|
306
|
+
mcp.resources_list # [Resource(uri: "fs://...", ...)]
|
|
307
|
+
mcp.resources_read("fs://docs/foo.md")
|
|
308
|
+
mcp.prompts_list # [Prompt(name: "summarize", ...)]
|
|
309
|
+
mcp.prompts_get("summarize", {path: "foo.md"})
|
|
310
|
+
mcp.stop
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Bridge into chat.completions
|
|
314
|
+
|
|
315
|
+
> **Important:** Groq's chat-completions API does not have a request
|
|
316
|
+
> parameter for "MCP server URL." MCP integration here is **client-side
|
|
317
|
+
> orchestration**: this gem talks to the MCP servers, exposes their tools
|
|
318
|
+
> as ordinary OpenAI/Groq function tools through the standard `tools:`
|
|
319
|
+
> parameter, then routes the model's `tool_calls` back to the right
|
|
320
|
+
> server. From Groq's perspective there is no MCP — just function tools.
|
|
321
|
+
|
|
322
|
+
`Bridge` does three things at construction:
|
|
323
|
+
|
|
324
|
+
1. spawns each `ServerConfig`'s child process via stdio,
|
|
325
|
+
2. runs the MCP `initialize` handshake and asks each server for its tool list,
|
|
326
|
+
3. indexes those tools by namespaced name `<server>__<tool>` so collisions across servers are impossible.
|
|
327
|
+
|
|
328
|
+
`bridge.tools` then returns an array shaped exactly like Groq's
|
|
329
|
+
`tools:` parameter:
|
|
330
|
+
|
|
331
|
+
```ruby
|
|
332
|
+
bridge.tools
|
|
333
|
+
# => [
|
|
334
|
+
# {
|
|
335
|
+
# type: "function",
|
|
336
|
+
# function: {
|
|
337
|
+
# name: "fs__read_file",
|
|
338
|
+
# description: "Read the contents of a file at the given path",
|
|
339
|
+
# parameters: { # JSON Schema, straight from the MCP server
|
|
340
|
+
# "type" => "object",
|
|
341
|
+
# "properties" => { "path" => { "type" => "string" } },
|
|
342
|
+
# "required" => ["path"]
|
|
343
|
+
# }
|
|
344
|
+
# }
|
|
345
|
+
# },
|
|
346
|
+
# ...
|
|
347
|
+
# ]
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
A complete agent loop using a real MCP server (`@modelcontextprotocol/server-filesystem`):
|
|
351
|
+
|
|
352
|
+
```ruby
|
|
353
|
+
require "groq_ruby"
|
|
354
|
+
require "json"
|
|
355
|
+
|
|
356
|
+
filesystem = GroqRuby::MCP::ServerConfig.new(
|
|
357
|
+
name: "fs",
|
|
358
|
+
command: "npx",
|
|
359
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/docs"]
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
bridge = GroqRuby::MCP::Bridge.new([filesystem])
|
|
363
|
+
groq = GroqRuby::Client.new
|
|
364
|
+
|
|
365
|
+
messages = [
|
|
366
|
+
{role: "system", content: "You can use filesystem tools. Prefer tools over speculation."},
|
|
367
|
+
{role: "user", content: "Summarise README.md."}
|
|
368
|
+
]
|
|
369
|
+
|
|
370
|
+
begin
|
|
371
|
+
loop do
|
|
372
|
+
response = groq.chat.completions.create(
|
|
373
|
+
model: "llama-3.3-70b-versatile",
|
|
374
|
+
messages: messages,
|
|
375
|
+
tools: bridge.tools # <-- MCP tools surfaced as Groq function tools
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
message = response.choices.first.message
|
|
379
|
+
tool_calls = message.tool_calls
|
|
380
|
+
|
|
381
|
+
if tool_calls.nil? || tool_calls.empty?
|
|
382
|
+
puts message.content
|
|
383
|
+
break
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
messages << {role: "assistant", content: message.content, tool_calls: tool_calls}
|
|
387
|
+
tool_calls.each do |call|
|
|
388
|
+
fn = call["function"]
|
|
389
|
+
# bridge.call accepts either a Hash or the raw JSON string Groq
|
|
390
|
+
# returns in `function.arguments` — it routes to the owning server.
|
|
391
|
+
result = bridge.call(fn["name"], fn["arguments"])
|
|
392
|
+
messages << {role: "tool", tool_call_id: call["id"], content: JSON.generate(result)}
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
ensure
|
|
396
|
+
bridge.stop # kills child processes, closes stdio
|
|
397
|
+
end
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
Runnable variants in `examples/`:
|
|
401
|
+
- [`examples/mcp_agent.rb`](examples/mcp_agent.rb) — minimal version of the loop above.
|
|
402
|
+
- [`examples/mcp_chat_with_tools.rb`](examples/mcp_chat_with_tools.rb) — same loop, heavily annotated step by step.
|
|
403
|
+
- [`examples/mcp_resources_and_prompts.rb`](examples/mcp_resources_and_prompts.rb) — adds resources (catalogued in the system prompt + fetched via the synthetic `read_resource` tool) and prompts.
|
|
404
|
+
|
|
405
|
+
## Error handling
|
|
406
|
+
|
|
407
|
+
Every API failure raises a subclass of `GroqRuby::APIError`. Rescue the
|
|
408
|
+
base class to handle anything; rescue specific subclasses to react to
|
|
409
|
+
particular conditions.
|
|
410
|
+
|
|
411
|
+
```ruby
|
|
412
|
+
begin
|
|
413
|
+
client.chat.completions.create(...)
|
|
414
|
+
rescue GroqRuby::AuthenticationError => e
|
|
415
|
+
warn "auth failed: #{e.message}"
|
|
416
|
+
rescue GroqRuby::RateLimitError => e
|
|
417
|
+
warn "rate limited; retry after backoff"
|
|
418
|
+
rescue GroqRuby::APIStatusError => e
|
|
419
|
+
warn "status #{e.status}: #{e.message}"
|
|
420
|
+
rescue GroqRuby::APIConnectionError => e
|
|
421
|
+
warn "network: #{e.message}"
|
|
422
|
+
end
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
Hierarchy:
|
|
426
|
+
|
|
427
|
+
| Class | When |
|
|
428
|
+
|--------------------------------------|--------------------------------------------------------|
|
|
429
|
+
| `GroqRuby::Error` | Base for everything in the gem |
|
|
430
|
+
| `GroqRuby::ConfigurationError` | Missing or invalid configuration |
|
|
431
|
+
| `GroqRuby::ParameterError` | Request params failed validation |
|
|
432
|
+
| `GroqRuby::APIError` | Base for any failure talking to the API |
|
|
433
|
+
| `GroqRuby::APIConnectionError` | Network failure before a response |
|
|
434
|
+
| `GroqRuby::APITimeoutError` | Connection or read timeout |
|
|
435
|
+
| `GroqRuby::APIStatusError` | 4xx/5xx response (carries `status`, `headers`, `body`) |
|
|
436
|
+
| `GroqRuby::BadRequestError` | 400 |
|
|
437
|
+
| `GroqRuby::AuthenticationError` | 401 |
|
|
438
|
+
| `GroqRuby::PermissionDeniedError` | 403 |
|
|
439
|
+
| `GroqRuby::NotFoundError` | 404 |
|
|
440
|
+
| `GroqRuby::ConflictError` | 409 |
|
|
441
|
+
| `GroqRuby::UnprocessableEntityError` | 422 |
|
|
442
|
+
| `GroqRuby::RateLimitError` | 429 |
|
|
443
|
+
| `GroqRuby::InternalServerError` | 5xx |
|
|
444
|
+
| `GroqRuby::APIResponseError` | API returned an unexpected payload |
|
|
445
|
+
| `GroqRuby::MCP::Error` | Base for any MCP-layer failure |
|
|
446
|
+
| `GroqRuby::MCP::TransportError` | Stdio pipe broke or process exited |
|
|
447
|
+
| `GroqRuby::MCP::TimeoutError` | MCP request timed out |
|
|
448
|
+
| `GroqRuby::MCP::ProtocolError` | Server sent malformed JSON-RPC |
|
|
449
|
+
| `GroqRuby::MCP::JsonRpcError` | Server returned a JSON-RPC `error` |
|
|
450
|
+
| `GroqRuby::MCP::UnknownToolError` | `Bridge#call` couldn't find the tool |
|
|
451
|
+
|
|
452
|
+
## Examples
|
|
453
|
+
|
|
454
|
+
The [`examples/`](examples) directory has one runnable script per major
|
|
455
|
+
endpoint, plus the MCP agent loop. Each reads `GROQ_API_KEY` from the
|
|
456
|
+
environment. See [`examples/README.md`](examples/README.md) for the full
|
|
457
|
+
list.
|
|
458
|
+
|
|
459
|
+
## Compatibility
|
|
460
|
+
|
|
461
|
+
- Ruby 3.2+
|
|
462
|
+
- Net::HTTP (no Faraday/HTTParty dependency)
|
|
463
|
+
|
|
464
|
+
### Not yet supported
|
|
465
|
+
|
|
466
|
+
The python SDK has a few features that aren't in `groq_ruby` v1:
|
|
467
|
+
|
|
468
|
+
- Async client (everything here is synchronous; use threads/fibers if you need concurrency).
|
|
469
|
+
- `with_raw_response` / `with_streaming_response` accessors (responses are always parsed into typed models).
|
|
470
|
+
- Built-in retries / backoff (handle in your own caller).
|
|
471
|
+
- MCP sampling and `notifications/list_changed` (resource and prompt inventories are snapshotted at `Bridge` construction).
|
|
472
|
+
|
|
473
|
+
## Development
|
|
474
|
+
|
|
475
|
+
```sh
|
|
476
|
+
bin/setup # install deps
|
|
477
|
+
bundle exec rake # tests + lint + RBS validate
|
|
478
|
+
bundle exec rake test # tests only
|
|
479
|
+
bundle exec rake test TESTOPTS="--name=/pattern/" # subset
|
|
480
|
+
bin/console # IRB with the gem preloaded
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
Tests use Minitest + WebMock; no test makes real network calls.
|
|
484
|
+
|
|
485
|
+
## Attribution
|
|
486
|
+
|
|
487
|
+
The API surface, parameter names, and resource layout follow the
|
|
488
|
+
official Groq Python SDK at
|
|
489
|
+
<https://github.com/groq/groq-python>, distributed under the Apache 2.0
|
|
490
|
+
licence. This gem is an independent Ruby implementation and is not
|
|
491
|
+
affiliated with or endorsed by Groq.
|
|
492
|
+
|
|
493
|
+
## License
|
|
494
|
+
|
|
495
|
+
MIT — see [`LICENSE.txt`](LICENSE.txt).
|