2026-09-08 15:12:35 +03:00
# groqqer
2026-09-08 16:59:17 +03:00
`groqqer` is a lightweight, zero-dependency Go OpenAI-compatible API gateway and proxy for the Groq Streamlit space (`https://dromerosm-groq-chatbot.hf.space` ).
2026-09-08 15:12:35 +03:00
2026-09-08 16:59:17 +03:00
It connects directly to Streamlit's binary WebSocket engine (`/_stcore/stream` ) using a pure Go Protobuf wire-format encoder/decoder. It requires **no browser** , **no Chromium** , and **no Xvfb** , allowing it to run smoothly on minimal headless servers and low-resource containers.
2026-09-08 15:12:35 +03:00
2026-09-08 17:35:41 +03:00
## Key features
2026-09-08 15:12:35 +03:00
2026-09-08 17:35:41 +03:00
- **Direct WebSocket Protobuf protocol**: communicates directly with Streamlit's internal engine over RFC 6455 WebSockets and Protocol Buffers wire format.
- **Completely headless and zero dependencies**: written entirely in pure Go using only the standard library (`net/http` , `crypto/tls` , `encoding/binary` , `encoding/json` , etc.). No Chromium, Chrome, Xvfb, Puppeteer, or external Go modules required.
- **Sub-second latency**: bypasses browser rendering and DOM parsing entirely, delivering responses with minimal overhead.
- **OpenAI-compatible API**: exposes standard `/v1/models` and `/v1/chat/completions` endpoints. Drop-in replacement for OpenAI SDKs, LiteLLM, Open-WebUI, LibreChat, and LangChain.
- **System prompt support**: formats developer/system instructions cleanly for the target model.
- **Streaming and non-streaming**: supports Server-Sent Events (`stream: true` ) with real-time token streaming and synchronous JSON responses.
- **Tool and function calling**: fully supports OpenAI `tools` , `tool_choice` , and multi-turn execution (`role: "tool"` ). Automatically injects schemas and parses `<tool_call>` outputs into standard OpenAI `tool_calls` payloads with `finish_reason: "tool_calls"` .
- **Reasoning content**: extracts `<think>` and `</think>` tags from reasoning models (e.g., Qwen 3.6/3.8) and streams or populates `reasoning_content` following OpenAI O-series conventions.
- **Dynamic model discovery and switching**: discovers active models directly from the Streamlit space on startup and allows seamless model switching between requests (`qwen/qwen3.6-27b` , `openai/gpt-oss-120b` , `openai/gpt-oss-20b` , `groq/compound` , etc.).
- **SOCKS5 proxy support**: native pure Go SOCKS5 proxy client supporting authentication (`-socks` flag or `ALL_PROXY` / `SOCKS5_PROXY` environment variables).
2026-09-08 15:12:35 +03:00
## Architecture
```
+---------------------------+
| OpenAI Client / SDK / App |
+---------------------------+
| HTTP POST /v1/chat/completions
v
+---------------------------+
| groqqer |
| (Pure Go Standard Lib) |
2026-09-08 16:59:17 +03:00
| RFC 6455 WS + Protobuf |
2026-09-08 15:12:35 +03:00
+---------------------------+
2026-09-08 16:59:17 +03:00
| WSS (TLS WebSocket)
2026-09-08 15:12:35 +03:00
v
+---------------------------+
| Groq Streamlit Space |
| (dromerosm-groq-chatbot) |
+---------------------------+
```
## Requirements
2026-09-08 17:35:41 +03:00
- Linux, macOS, or Windows
- Go 1.20+ (for building from source)
- No browser or graphics packages needed
2026-09-08 15:12:35 +03:00
2026-09-08 17:35:41 +03:00
## Installation and build
2026-09-08 15:12:35 +03:00
2026-09-08 17:54:51 +03:00
### Using `go install`
Install the binary directly to `$GOPATH/bin` (or `~/go/bin` ):
```bash
go install code.luxferre.top/luxferre/groqqer@latest
```
Make sure `$GOPATH/bin` is in your `PATH` .
### Building from source
Clone or navigate to the repository, then compile the binary:
2026-09-08 15:12:35 +03:00
```bash
make
```
2026-09-08 17:54:51 +03:00
The optimized binary will be created at `bin/groqqer` . Alternatively, install into `$GOPATH/bin` from the local directory:
```bash
go install .
```
2026-09-08 15:12:35 +03:00
2026-09-08 17:35:41 +03:00
## Running the server
2026-09-08 15:12:35 +03:00
Start the gateway with default settings:
```bash
./bin/groqqer -port 8080
```
2026-09-08 17:35:41 +03:00
### CLI flags
2026-09-08 15:12:35 +03:00
| Flag | Default | Description |
|------|---------|-------------|
| `-port` | `8080` | HTTP server listening port |
| `-target` | `https://dromerosm-groq-chatbot.hf.space` | Target Groq Streamlit space URL |
2026-09-08 16:59:17 +03:00
| `-default-model` | `qwen/qwen3.6-27b` | Fallback model if request does not specify one |
2026-09-08 15:12:35 +03:00
| `-socks` | `""` | SOCKS5 proxy URL (e.g. `socks5://127.0.0.1:1080` ) |
2026-09-08 16:59:17 +03:00
| `-user-agent` | *(Chrome UA)* | Custom User-Agent string |
*(Note: legacy flags `-browser`, `-xvfb`, and `-headless` are retained for backward compatibility but are ignored, as groqqer operates completely headless via direct WebSocket).*
2026-09-08 15:12:35 +03:00
2026-09-08 17:35:41 +03:00
## API usage examples
2026-09-08 15:12:35 +03:00
2026-09-08 17:35:41 +03:00
### 1. List available models
2026-09-08 15:12:35 +03:00
```bash
curl -s http://127.0.0.1:8080/v1/models
```
**Response:**
```json
{
"object" : "list" ,
"data" : [
2026-09-08 16:59:17 +03:00
{ "id" : "qwen/qwen3.6-27b" , "object" : "model" , "created" : 1788870683 , "owned_by" : "groq" },
{ "id" : "qwen/qwen3.8-27b" , "object" : "model" , "created" : 1788870683 , "owned_by" : "groq" },
{ "id" : "openai/gpt-oss-120b" , "object" : "model" , "created" : 1788870683 , "owned_by" : "groq" },
{ "id" : "openai/gpt-oss-20b" , "object" : "model" , "created" : 1788870683 , "owned_by" : "groq" },
{ "id" : "groq/compound" , "object" : "model" , "created" : 1788870683 , "owned_by" : "groq" },
{ "id" : "groq/compound-mini" , "object" : "model" , "created" : 1788870683 , "owned_by" : "groq" },
{ "id" : "allam-2-7b" , "object" : "model" , "created" : 1788870683 , "owned_by" : "groq" }
2026-09-08 15:12:35 +03:00
]
}
```
---
2026-09-08 17:35:41 +03:00
### 2. Chat completion (non-streaming)
2026-09-08 15:12:35 +03:00
```bash
curl -s -X POST http://127.0.0.1:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
2026-09-08 16:59:17 +03:00
"model": "openai/gpt-oss-20b",
2026-09-08 15:12:35 +03:00
"messages": [
{"role": "system", "content": "You are a concise assistant. Reply in one sentence."},
{"role": "user", "content": "What is the capital of France?"}
]
}'
```
**Response:**
```json
{
2026-09-08 16:59:17 +03:00
"id" : "chatcmpl-893498c9-2808-4bec-8c6a-2a0352ed69e9" ,
2026-09-08 15:12:35 +03:00
"object" : "chat.completion" ,
2026-09-08 16:59:17 +03:00
"created" : 1788870700 ,
"model" : "openai/gpt-oss-20b" ,
2026-09-08 15:12:35 +03:00
"choices" : [
{
"index" : 0 ,
"message" : {
"role" : "assistant" ,
2026-09-08 16:59:17 +03:00
"content" : "The capital of France is Paris."
2026-09-08 15:12:35 +03:00
},
"finish_reason" : "stop"
}
],
"usage" : {
"prompt_tokens" : 0 ,
"completion_tokens" : 0 ,
"total_tokens" : 0
}
}
```
---
2026-09-08 17:35:41 +03:00
### 3. Streaming chat completion (SSE)
2026-09-08 15:12:35 +03:00
```bash
curl -N -s -X POST http://127.0.0.1:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "qwen/qwen3.6-27b",
"messages": [
2026-09-08 16:59:17 +03:00
{"role": "user", "content": "Count from 1 to 4 separated by commas."}
2026-09-08 15:12:35 +03:00
],
"stream": true
}'
```
**Output:**
```
2026-09-08 16:59:17 +03:00
data: {"id":"chatcmpl-7a06d0fb","object":"chat.completion.chunk","created":1788870705,"model":"qwen/qwen3.6-27b","choices":[{"index":0,"delta":{"role":"assistant"}}]}
2026-09-08 15:12:35 +03:00
2026-09-08 16:59:17 +03:00
data: {"id":"chatcmpl-7a06d0fb","object":"chat.completion.chunk","created":1788870705,"model":"qwen/qwen3.6-27b","choices":[{"index":0,"delta":{"reasoning_content":"Thinking process..."}}]}
2026-09-08 15:12:35 +03:00
2026-09-08 16:59:17 +03:00
data: {"id":"chatcmpl-7a06d0fb","object":"chat.completion.chunk","created":1788870705,"model":"qwen/qwen3.6-27b","choices":[{"index":0,"delta":{"content":"1,"}}]}
2026-09-08 15:12:35 +03:00
2026-09-08 16:59:17 +03:00
data: {"id":"chatcmpl-7a06d0fb","object":"chat.completion.chunk","created":1788870705,"model":"qwen/qwen3.6-27b","choices":[{"index":0,"delta":{"content":" 2,"}}]}
2026-09-08 15:12:35 +03:00
2026-09-08 16:59:17 +03:00
data: {"id":"chatcmpl-7a06d0fb","object":"chat.completion.chunk","created":1788870705,"model":"qwen/qwen3.6-27b","choices":[{"index":0,"delta":{"content":" 3,"}}]}
data: {"id":"chatcmpl-7a06d0fb","object":"chat.completion.chunk","created":1788870705,"model":"qwen/qwen3.6-27b","choices":[{"index":0,"delta":{"content":" 4"}}]}
data: {"id":"chatcmpl-7a06d0fb","object":"chat.completion.chunk","created":1788870705,"model":"qwen/qwen3.6-27b","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
2026-09-08 15:12:35 +03:00
data: [DONE]
```
---
2026-09-08 17:35:41 +03:00
### 4. Tool and function calling
2026-09-08 15:12:35 +03:00
```bash
curl -s -X POST http://127.0.0.1:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "qwen/qwen3.6-27b",
"messages": [
2026-09-08 16:59:17 +03:00
{"role": "user", "content": "What is the weather in Tokyo right now?"}
2026-09-08 15:12:35 +03:00
],
"tools": [
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "Get current weather for a city",
"parameters": {
"type": "object",
"properties": {
2026-09-08 16:59:17 +03:00
"location": {"type": "string", "description": "City and country"}
2026-09-08 15:12:35 +03:00
},
"required": ["location"]
}
}
}
],
2026-09-08 16:59:17 +03:00
"tool_choice": "required"
2026-09-08 15:12:35 +03:00
}'
```
**Response:**
```json
{
2026-09-08 16:59:17 +03:00
"id" : "chatcmpl-29f63b3b-7ab6-444c-8579-49f906007d43" ,
2026-09-08 15:12:35 +03:00
"object" : "chat.completion" ,
2026-09-08 16:59:17 +03:00
"created" : 1788870711 ,
2026-09-08 15:12:35 +03:00
"model" : "qwen/qwen3.6-27b" ,
"choices" : [
{
"index" : 0 ,
"message" : {
"role" : "assistant" ,
"content" : null ,
2026-09-08 16:59:17 +03:00
"reasoning_content" : "The user is asking for the current weather in Tokyo..." ,
2026-09-08 15:12:35 +03:00
"tool_calls" : [
{
2026-09-08 16:59:17 +03:00
"id" : "call_163e8f97" ,
2026-09-08 15:12:35 +03:00
"type" : "function" ,
"function" : {
"name" : "get_current_weather" ,
2026-09-08 16:59:17 +03:00
"arguments" : "{\"location\":\"Tokyo\"}"
2026-09-08 15:12:35 +03:00
}
}
]
},
"finish_reason" : "tool_calls"
}
]
}
```
---
2026-09-08 17:35:41 +03:00
### 5. Multi-turn tool response execution
2026-09-08 15:12:35 +03:00
2026-09-08 16:59:17 +03:00
Send back the tool execution results using `role: "tool"` :
2026-09-08 15:12:35 +03:00
```bash
curl -s -X POST http://127.0.0.1:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "qwen/qwen3.6-27b",
"messages": [
2026-09-08 16:59:17 +03:00
{"role": "user", "content": "What is the weather in Tokyo right now?"},
2026-09-08 15:12:35 +03:00
{
"role": "assistant",
"tool_calls": [
{
2026-09-08 16:59:17 +03:00
"id": "call_163e8f97",
2026-09-08 15:12:35 +03:00
"type": "function",
"function": {
"name": "get_current_weather",
2026-09-08 16:59:17 +03:00
"arguments": "{\"location\":\"Tokyo\"}"
2026-09-08 15:12:35 +03:00
}
}
]
},
{
"role": "tool",
2026-09-08 16:59:17 +03:00
"tool_call_id": "call_163e8f97",
2026-09-08 15:12:35 +03:00
"name": "get_current_weather",
2026-09-08 16:59:17 +03:00
"content": "{\"temperature\": \"19°C\", \"weather\": \"Sunny with clear skies\"}"
2026-09-08 15:12:35 +03:00
}
]
}'
```
**Response:**
```json
{
2026-09-08 16:59:17 +03:00
"id" : "chatcmpl-23354bba-33c7-45be-8807-9c84243037b8" ,
2026-09-08 15:12:35 +03:00
"object" : "chat.completion" ,
2026-09-08 16:59:17 +03:00
"created" : 1788870718 ,
2026-09-08 15:12:35 +03:00
"model" : "qwen/qwen3.6-27b" ,
"choices" : [
{
"index" : 0 ,
"message" : {
"role" : "assistant" ,
2026-09-08 16:59:17 +03:00
"content" : "It's currently 19°C and sunny with clear skies in Tokyo"
2026-09-08 15:12:35 +03:00
},
"finish_reason" : "stop"
}
]
}
```
2026-09-08 16:59:17 +03:00
---
2026-09-08 17:35:41 +03:00
### 6. Python OpenAI SDK example
2026-09-08 15:12:35 +03:00
```python
from openai import OpenAI
client = OpenAI (
base_url = "http://127.0.0.1:8080/v1" ,
api_key = "not-needed"
)
response = client . chat . completions . create (
model = "qwen/qwen3.6-27b" ,
messages = [
{ "role" : "system" , "content" : "You are a helpful coding assistant." },
{ "role" : "user" , "content" : "Write a Python function to reverse a string." }
]
)
print ( response . choices [ 0 ] . message . content )
```
## License
2026-09-08 17:35:41 +03:00
Created by Luxferre in 2026, released into the public domain with no warranties.