diff --git a/plugins/groq/LICENSE b/plugins/groq/LICENSE new file mode 100644 index 000000000..a48ac85f9 --- /dev/null +++ b/plugins/groq/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2023] [SuperDuperDB, Inc.] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/plugins/groq/README.md b/plugins/groq/README.md new file mode 100644 index 000000000..1dbc23921 --- /dev/null +++ b/plugins/groq/README.md @@ -0,0 +1,63 @@ + +# superduper-groq + +SuperDuperDB integration with Groq API + +## Installation + +```bash +pip install superduper-groq +``` + +## API + + +- [Code](https://github.com/superduper-io/superduper/tree/main/plugins/groq) +- [API-docs](/docs/api/plugins/superduper-groq) + +| Class | Description | +|---|---| +| `superduper_groq.model.GroqAPIModel` | Base Class for Groq API Models | +| `superduper_groq.model.GroqChatCompletions` | Chat Completions Model for Groq API | + + + + + + + +## Create a Chat Model with All Attributes + +```python +model = GroqChatCompletions( + identifier="llama3-8b-8192", + GROQ_API_KEY=os.getenv("GROQ_API_KEY"), + temperature=0.7, + system_message="You are a helpful assistant.", + tool_choice="auto", + tools=[], + include_reasoning=True, + browser_search=False +) +``` + +## Single Prediction + +```python +response = model.predict("Tell me about AI") +print(response) +``` + +## Prediction with Context +```python +response = model.predict("Tell me about ", context=["Artificial Intelligence"]) +print(response) +``` + +## Batch Predictions +```python +prompts = ["Hello world", "Explain machine learning"] +responses = model.predict_batches(prompts) +for i, resp in enumerate(responses, 1): + print(f"Response {i}: {resp}") +``` \ No newline at end of file diff --git a/plugins/groq/plugin_test/cassettes/test_chat_predict.yaml b/plugins/groq/plugin_test/cassettes/test_chat_predict.yaml new file mode 100644 index 000000000..0bde1c266 --- /dev/null +++ b/plugins/groq/plugin_test/cassettes/test_chat_predict.yaml @@ -0,0 +1,100 @@ +interactions: +- request: + body: '{"messages":[{"role":"system","content":"You are a helpful assistant."},{"role":"user","content":"Hello + world"}],"model":"openai/gpt-oss-20b","temperature":null,"tool_choice":"auto","tools":null}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '195' + content-type: + - application/json + host: + - api.groq.com + user-agent: + - Groq/Python 0.31.1 + x-stainless-arch: + - other:amd64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - Windows + x-stainless-package-version: + - 0.31.1 + x-stainless-read-timeout: + - '60' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.3 + method: POST + uri: https://api.groq.com/openai/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAAwAAAP//VJJBj5swEIX/ijtnSA0YCL70ur1VqnrqVsjgAbwxHsd2lEVR/nuV3SRq + r0/vk96bNxcwGiSMi0rj6m3Op0F1ohrzpqq6XHRFlyvNdS6qqen0sG8GMUIGNLzhmO7gbqTVW0yG + HGQwBlQJNciirdui5m3dZbCSRgsSyKNT5uvsU04x5iUfbsRCZsQI8vcFjNP4DpJnsGKMakaQFwhk + ESSoGE1MyqUbQy6huyV4QWvpC3uhMxuVY9/ZgtazjU4skVbbN8ggoIrkjJtBwq+IgUW1Rfb6ibIz + BatfYcd+BBrUYDeWFtzYWbnEFJsDYjJuZgGjJxdxx34udLL6LmjmyZqEdtvBNQNLsw80RJDuZG0G + k3EmLv1nApAQE3m4/sng9Ch3POEJ+2RWBMl3XHRlW5f7MgMfaPWpT3RAF0H+Kz3MvK54V7b72z0e + EzyBqvlffkBVyXnXNnUGiZKyT39R7J/S09tWRcdFdb0H7oeA6qDp7B4F4xYTrv1k3IzBB/OxyeR7 + MQqlGiWUgAze+znQ8db249sCHnteHMRUL29xxFl03m8aD39lZlZRkZcLCsbi1KKyzGRQsKQWgZJN + XnxKam5iXopSLRcAAAD//wMAJtkUjbICAAA= + headers: + CF-RAY: + - 97acec180cce58c9-MAA + Cache-Control: + - private, max-age=0, no-store, no-cache, must-revalidate + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Sat, 06 Sep 2025 09:25:59 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=30T388QvCy6EwN0IZNXyvRi7t0XswiQwTG9tx_vITJc-1757150759-1.0.1.1-.SD7Ew6AHyJ.aVqycWJ7KK5pHDWiK5mADPX8jFV0vGWfh6VM6n3YIIGWNpgTA_AiyLr8aeC6EzXDD_zyfugkdYprxTwShgzX_owurp176kM; + path=/; expires=Sat, 06-Sep-25 09:55:59 GMT; domain=.groq.com; HttpOnly; Secure; + SameSite=None + Transfer-Encoding: + - chunked + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + vary: + - Origin + via: + - 1.1 google + x-groq-region: + - bom + x-ratelimit-limit-requests: + - '1000' + x-ratelimit-limit-tokens: + - '8000' + x-ratelimit-remaining-requests: + - '999' + x-ratelimit-remaining-tokens: + - '7917' + x-ratelimit-reset-requests: + - 1m26.4s + x-ratelimit-reset-tokens: + - 622.5ms + x-request-id: + - req_01k4f5hjsceg49ppydek66xxnm + status: + code: 200 + message: OK +version: 1 diff --git a/plugins/groq/plugin_test/cassettes/test_chat_predict_batches.yaml b/plugins/groq/plugin_test/cassettes/test_chat_predict_batches.yaml new file mode 100644 index 000000000..377c9e61d --- /dev/null +++ b/plugins/groq/plugin_test/cassettes/test_chat_predict_batches.yaml @@ -0,0 +1,200 @@ +interactions: +- request: + body: '{"messages":[{"role":"system","content":"You are a helpful assistant."},{"role":"user","content":"Hello"}],"model":"openai/gpt-oss-20b","temperature":null,"tool_choice":"auto","tools":null}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '189' + content-type: + - application/json + host: + - api.groq.com + user-agent: + - Groq/Python 0.31.1 + x-stainless-arch: + - other:amd64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - Windows + x-stainless-package-version: + - 0.31.1 + x-stainless-read-timeout: + - '60' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.3 + method: POST + uri: https://api.groq.com/openai/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAA1SSzW7bMBCE730Kds+SS9r65aXX9AGKHppCoMSVxJri0iTVxDD87oWSyEWvg/kW + M4O9gdEgYZhVGhZv834oK9U2x7w/jWNe8LLNm6Ye84qLutd9w0XJIQPqf+OQPsDDQIu3mAw5yGAI + qBJqkKIua1HyuhIZLKTRggTy6JT5MvmUU4z5kfcbMZMZMIL8eQPjNL6C5BksGKOaEOQNAlkECSpG + E5NyaWPIJXRbgie0lj6zJ3phg3LsG5vRenallSXS6voVMgioIjnjJpDwPWJgUV0je35Hn+HAfiCL + M61Ws4DRk9PMkzUJ7fUA9wwsTT5QH0G61doMRuNMnLv3syAhJvJw/5XBuie+rLhil8yCIPmBF1Vd + iLZoM/CBFp+6RGd0EWQj/km7mVecl7wQW8l91wdwbP+Xd+hYNnXT1GUGiZKyD78Q/CHt3pNomlMr + qvtH4K4PqM6aXtxeMF5jwqUbjZsw+GDehh59p8aiL4q6HwfI4LWbAl22tm8vFPDScXEuxnJeVMCp + SOXlfE5lFFyHaZsxYvhjhm0WDNsvuE7jopyG+6e/AAAA//8DAON8xCOHAgAA + headers: + CF-RAY: + - 97acec21e97d380b-MAA + Cache-Control: + - private, max-age=0, no-store, no-cache, must-revalidate + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Sat, 06 Sep 2025 09:26:01 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=kQ5ckCGWP5WN31b4mSUSpqe.psOWJPVMlEsdVfhpoo0-1757150761-1.0.1.1-gEfCQ64QX3v8RxTCj2iKHtL4BANbCtO0IR4P5XpezI0dXdeLUag7oQoegwVTsfJMUf_a8vQFvGHCrLnBW5YePcuKA57NTVM_pqIYhJB0ig8; + path=/; expires=Sat, 06-Sep-25 09:56:01 GMT; domain=.groq.com; HttpOnly; Secure; + SameSite=None + Transfer-Encoding: + - chunked + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + vary: + - Origin + via: + - 1.1 google + x-groq-region: + - bom + x-ratelimit-limit-requests: + - '1000' + x-ratelimit-limit-tokens: + - '8000' + x-ratelimit-remaining-requests: + - '997' + x-ratelimit-remaining-tokens: + - '7849' + x-ratelimit-reset-requests: + - 4m18.417999999s + x-ratelimit-reset-tokens: + - 1.132499999s + x-request-id: + - req_01k4f5hmareg4t5qkkt5s10drg + status: + code: 200 + message: OK +- request: + body: '{"messages":[{"role":"system","content":"You are a helpful assistant."},{"role":"user","content":"Summarize + AI history in 2 sentences"}],"model":"openai/gpt-oss-20b","temperature":null,"tool_choice":"auto","tools":null}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '219' + content-type: + - application/json + cookie: + - __cf_bm=kQ5ckCGWP5WN31b4mSUSpqe.psOWJPVMlEsdVfhpoo0-1757150761-1.0.1.1-gEfCQ64QX3v8RxTCj2iKHtL4BANbCtO0IR4P5XpezI0dXdeLUag7oQoegwVTsfJMUf_a8vQFvGHCrLnBW5YePcuKA57NTVM_pqIYhJB0ig8 + host: + - api.groq.com + user-agent: + - Groq/Python 0.31.1 + x-stainless-arch: + - other:amd64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - Windows + x-stainless-package-version: + - 0.31.1 + x-stainless-read-timeout: + - '60' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.3 + method: POST + uri: https://api.groq.com/openai/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAA2RUy27rNhDd9ysGs6ZdSbYsW7usLoJuAtx01VsYFDmS2FAkQ1JXcYIA/oUsC7Q/ + 5y8pKD/S2+6E0ZyZc87M8A2VxBpFz6MYnF5sebMhnreLbVZsFmvZNIum4dmCS5FzucqrQmyRoW3+ + IBEvwKWwg9MUlTXIUHjikSTWeVVWeZlVm5zhYCVprNE6Mlz93Lm4sCEsiqxJiN4qQQHr395QGUkv + WGcMBwqBd4T1G3qrCWvkIagQuYkJY00kkxjc+ahaJRTXoEwkrVVHRhA01HEDk4o9EPf6AOEwNFYr + Adw5b7noKQA3EmJP8Dh6ZTqIFCIoM4cGJU/HjyKLPQgycfQHBpq4nPMs0IsjHyEcQqThs1CcLJyO + f93dw5TY+HA6/g22nf/luyo7Z+a7bRYYTL3SBJ4CcS/61NjQ6LkGQ3Gy/umcPHDRK0OpuTepe9B2 + 0gfouDIkIXoukvVLuD8TL7I8FW888afYezt2fUi1JZG7FWHw5eFX4EKQJs8Tns3NNPcdnY4fQXBN + oLnpRt4RzPMLEEbRAw/w5eHxdPxYzYjz9xqct460Jgl392kSFiYlKThPXIKbSQquk/laibnjWZ4n + QxNJsC6qQYUBeGPHOCtpFWl5Ov4ZoB3j6GmJDD3xYJMCrPGxJxgD+Rq+4ddxGLhXr5Ta9ypE6w9J + dQGB0qoICt9wCV97O+rEx8pREAhrhAoEYUYf0qR+hKd53gos4ZfkoYpX3K3eQCYpumza3T27bBT7 + 3LoU/FwL9q9RB/b/2Twu4cHb70rSfyjgO0NtO+dtE7A2o9YMW2VU6Pdna7DGEK3D998ZjtcLeh5p + pH1UA2GdLbP1pizW2S5n6LwdXNxH+0QmYL3dfYauydmmWpVltktHd73zGyDflD/GL6i8zMpdtctz + htFGrm+AolzfQrfcarVarYv3C+H9vLrSTuYq8Hxl+1aZjrzzaj781u3LXVtVLZFskeHLvvP2Oamd + nzRPz/ssf1q3ZT+0PXXrWE7t1rUuvr5Gk2wM5L8rkWwhn94ms5c0cCPx/ad/AAAA//8DACpx+A0X + BQAA + headers: + CF-RAY: + - 97acec22eb9d380b-MAA + Cache-Control: + - private, max-age=0, no-store, no-cache, must-revalidate + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Sat, 06 Sep 2025 09:26:01 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + vary: + - Origin + via: + - 1.1 google + x-groq-region: + - bom + x-ratelimit-limit-requests: + - '1000' + x-ratelimit-limit-tokens: + - '8000' + x-ratelimit-remaining-requests: + - '996' + x-ratelimit-remaining-tokens: + - '7750' + x-ratelimit-reset-requests: + - 5m45.446999999s + x-ratelimit-reset-tokens: + - 1.868499999s + x-request-id: + - req_01k4f5hmfheg4t5wf8pfptzztn + status: + code: 200 + message: OK +version: 1 diff --git a/plugins/groq/plugin_test/cassettes/test_chat_predict_with_context.yaml b/plugins/groq/plugin_test/cassettes/test_chat_predict_with_context.yaml new file mode 100644 index 000000000..1d3b3319a --- /dev/null +++ b/plugins/groq/plugin_test/cassettes/test_chat_predict_with_context.yaml @@ -0,0 +1,102 @@ +interactions: +- request: + body: '{"messages":[{"role":"system","content":"You are a helpful assistant."},{"role":"user","content":"Tell + me about"}],"model":"openai/gpt-oss-20b","temperature":null,"tool_choice":"auto","tools":null}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '197' + content-type: + - application/json + host: + - api.groq.com + user-agent: + - Groq/Python 0.31.1 + x-stainless-arch: + - other:amd64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - Windows + x-stainless-package-version: + - 0.31.1 + x-stainless-read-timeout: + - '60' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.3 + method: POST + uri: https://api.groq.com/openai/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAA1STQW/bMAyF7/sVHC+9OJmd2LHjyw4DBgzYscMO62DIMh2rlkVFkpd6Rf77oDRJ + sSv1PuLxkXpF1WGNchBBTlav+rYoqGv7VZZVYpWnebpqN22+2lWbsi2olVXeY4LcPpMMV3AtebKa + gmKDCUpHIlCHdVYWZVak5S5NcOKONNbIloxQnw42rNj71SZtIzGwkuSx/vWKynT0gnVEyHtxIKxf + 0bEmrFF4r3wQJkSGTSATHXzhWXew8AyaAkwEo+ETnAYRYvGhA61GgsCgSTgDouU5fIavRBp6R5cX + b0mqfgEBga2SCVhynk0Cko0kGxJgB8IsYVDmAKQ9XVo7Ajk7xbN/6/oRE3QkPBtlDljjD08OvFg8 + POEjaR3NXYRPuIZv4cGDMtfoaA3f1Uh6gTDQAidhAkzsCJTpGdo5gOFw9amoW8NPAkPURffCjyC1 + cKpfor3jTD6uYg2PA8E9M/DDJShH3rLpIhXVPbsrrKS4YHhOUPPBOm491mbWOsFeGeWH5m04rNEH + tnj+neB8W9FxppmaoCbCOl2neZmXWbpP0DqebGgCj2Q81tX2vXTTpkW+32abuNPbGd315e7/8o3Z + lVVRlWWRYOAg9F2fFft76aYtt9siq/bF+Wq3aR2JseOTuY3nFx9oanplDuSsU5e76m0j+rzN87Lt + JSb40hwcH+Oslx/j6Nik2Zj3xTA+7/q/fp8+V9ae5Bi6l20M0ZP7o2QMhVw8fdN0NAnT4fnDPwAA + AP//AwAp8CaWdgMAAA== + headers: + CF-RAY: + - 97acec1cfb85ff18-MAA + Cache-Control: + - private, max-age=0, no-store, no-cache, must-revalidate + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Sat, 06 Sep 2025 09:26:00 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=n0HkzDzOAP36bAIDruAQxVFVV.oGYFmIDkwVuPia2Y4-1757150760-1.0.1.1-SIrVHe5Io5mFBAYIhGzx7YZ4SpYUkMOEt0PvXicxKRsLZDnuq.e_kmKjDCRBXy1e.HgCUKTeLOenbZ.FwGzTlsmvyBZLxctCwRtXPIx29is; + path=/; expires=Sat, 06-Sep-25 09:56:00 GMT; domain=.groq.com; HttpOnly; Secure; + SameSite=None + Transfer-Encoding: + - chunked + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + vary: + - Origin + via: + - 1.1 google + x-groq-region: + - bom + x-ratelimit-limit-requests: + - '1000' + x-ratelimit-limit-tokens: + - '8000' + x-ratelimit-remaining-requests: + - '998' + x-ratelimit-remaining-tokens: + - '7902' + x-ratelimit-reset-requests: + - 2m52.01s + x-ratelimit-reset-tokens: + - 730.499999ms + x-request-id: + - req_01k4f5hkj6fzs90j8ppwcktdx3 + status: + code: 200 + message: OK +version: 1 diff --git a/plugins/groq/plugin_test/test_model_groq.py b/plugins/groq/plugin_test/test_model_groq.py new file mode 100644 index 000000000..e74dfd1b0 --- /dev/null +++ b/plugins/groq/plugin_test/test_model_groq.py @@ -0,0 +1,55 @@ +import os + +import pytest +import vcr +from dotenv import load_dotenv +from superduper_groq import GroqChatCompletions + +load_dotenv() +CASSETTE_DIR = os.path.join(os.path.dirname(__file__), 'cassettes') + +# Ensure API key is set for testing +if os.getenv('GROQ_API_KEY') is None: + mp = pytest.MonkeyPatch() + mp.setenv('GROQ_API_KEY', 'sk-TopSecret') + + +@vcr.use_cassette( + f"{CASSETTE_DIR}/test_chat_predict.yaml", + filter_headers=["authorization"], +) +def test_chat_predict(): + """Test that GroqChatCompletions predicts a single string response.""" + model = GroqChatCompletions( + identifier="llama-3.1-8b-instant", system_message="You are a helpful assistant." + ) + resp = model.predict("Hello world") + assert isinstance(resp, str) + + +@vcr.use_cassette( + f"{CASSETTE_DIR}/test_chat_predict_with_context.yaml", + filter_headers=["authorization"], +) +def test_chat_predict_with_context(): + """Test that GroqChatCompletions predicts a single string response with context.""" + model = GroqChatCompletions( + identifier="llama-3.1-8b-instant", system_message="You are a helpful assistant." + ) + resp = model.predict("Tell me about", context=["AI"]) + assert isinstance(resp, str) + + +@vcr.use_cassette( + f"{CASSETTE_DIR}/test_chat_predict_batches.yaml", + filter_headers=["authorization"], +) +def test_chat_predict_batches(): + """Test that GroqChatCompletions predicts a batch of responses.""" + model = GroqChatCompletions( + identifier="llama-3.1-8b-instant", system_message="You are a helpful assistant." + ) + dataset = ["Hello", "Summarize AI history in 2 sentences"] + resp = model.predict_batches(dataset) + assert isinstance(resp, list) + assert all(isinstance(r, str) for r in resp) diff --git a/plugins/groq/pyproject.toml b/plugins/groq/pyproject.toml new file mode 100644 index 000000000..229e9eb65 --- /dev/null +++ b/plugins/groq/pyproject.toml @@ -0,0 +1,37 @@ +[build-system] +requires = ["setuptools>=42"] +build-backend = "setuptools.build_meta" + +[project] +name = "superduper-groq" +version = "0.1.0" +description = "SuperDuperDB integration with Groq API" +authors = [ + {name = "Geoffrey Robinson", email = "jeffrobin132004@gmail.com"}, +] +dependencies = [ + "superduper-framework>=0.1.0", + "groq>=0.4.0", +] +requires-python = ">=3.8" + +[project.optional-dependencies] +test = [ + "pytest>=7.0.0", + "pytest-cov>=4.0.0", + "pytest-mock>=3.10.0", + "vcrpy>=5.1.0", +] + +[tool.setuptools.packages.find] +include = ["superduper_groq*"] +exclude = ["plugin_test*"] + +[tool.pytest.ini_options] +testpaths = ["tests"] +python_files = ["test_*.py"] +python_functions = ["test_*"] +addopts = "-v --cov=superduper_groq --cov-report=term-missing" + +[project.entry-points."superduper.plugin"] +groq = "superduper_groq" \ No newline at end of file diff --git a/plugins/groq/superduper_groq/__init__.py b/plugins/groq/superduper_groq/__init__.py new file mode 100644 index 000000000..6badaa326 --- /dev/null +++ b/plugins/groq/superduper_groq/__init__.py @@ -0,0 +1,4 @@ +from .model import GroqChatCompletions + +__version__ = "0.7.0" +__all__ = ['GroqChatCompletions'] diff --git a/plugins/groq/superduper_groq/model.py b/plugins/groq/superduper_groq/model.py new file mode 100644 index 000000000..87d757c90 --- /dev/null +++ b/plugins/groq/superduper_groq/model.py @@ -0,0 +1,88 @@ +import dataclasses as dc +import os +from typing import Any, Dict, List, Optional, Union + +import groq +from groq import APIConnectionError, APIError, APIStatusError, APITimeoutError + +from superduper.base.query_dataset import QueryDataset +from superduper.components.model import APIBaseModel +from superduper.misc.retry import Retry +from superduper.misc.utils import format_prompt + +retry = Retry( + exception_types=(APIConnectionError, APIError, APIStatusError, APITimeoutError) +) + + +class GroqAPIModel(APIBaseModel): + """Base Class for Groq API Models. + + :param groq_api_key: The API key for authenticating with the Groq API. + """ + + temperature: Optional[float] = None + system_message: Optional[str] = None + tool_choice: Optional[Union[str, Dict]] = "auto" + tools: Optional[list] = dc.field(default_factory=list) + include_reasoning: Optional[bool] = None + + def postinit(self): + """Post-initialization method.""" + self.model = self.model or self.identifier + super().postinit() + + def setup(self, db=None): + """Initialize the model. + + :param db: The database connection to use. + """ + self.client = groq.Groq(api_key=os.getenv("GROQ_API_KEY")) + self.async_client = groq.AsyncGroq() + super().setup() + + +class GroqChatCompletions(GroqAPIModel): + """Chat Completions Model for Groq API.""" + + prompt: str = '' + signature: str = '*args,**kwargs' + takes_context: bool = True + browser_search: Optional[bool] = False + browser_tool = { + "type": "browser_search", + "description": "Useful for when you need to answer questions about current events.", + } + + @retry + def predict( + self, X: Union[str, list[dict]], context: Optional[List[str]] = None + ) -> Any: + messages = [] + if isinstance(X, str): + if context is not None: + X = format_prompt(X, self.prompt, context=context) + if self.system_message: + messages.append({'role': 'system', 'content': self.system_message}) + messages.append({'role': 'user', 'content': X}) + elif isinstance(X, list) and all(isinstance(p, dict) for p in X): + if self.system_message: + messages.append({'role': 'system', 'content': self.system_message}) + messages.append({'role': 'user', 'content': X}) + if not self.browser_search and not self.tools: + tools = None + elif self.browser_search: + tools = self.tools + [self.browser_tool] + else: + tools = self.tools + response = self.client.chat.completions.create( + messages=messages, + model=self.identifier, + temperature=self.temperature, + tool_choice=self.tool_choice or "none", + tools=tools, + ) + return response.choices[0].message.content + + def predict_batches(self, dataset: Union[List, QueryDataset]) -> List: + return [self.predict(dataset[i]) for i in range(len(dataset))]