autoreg-skills-max: initial public release (restored, redacted, hardened)

This commit is contained in:
baradok60915d52e8425
2026-10-20 02:16:32 +03:00
commit f58eafeb48
565 changed files with 60064 additions and 0 deletions

View File

@@ -0,0 +1,191 @@
# Gateway-Level Tool Use v2
Техника реализации function calling на уровне гейтвея, когда upstream API не поддерживает нативный tool calling.
## Суть
Модель получает определения тулзов через структурированный системный промпт, а не через нативный `tools` параметр API. Гейтвей парсит ответ и возвращает OpenAI-совместимый `tool_calls` формат.
## Промпт-инжект v2
```python
def build_tool_prompt(tools: list, tool_choice) -> str:
tool_defs = []
for i, tool in enumerate(tools):
t = tool.get("function", tool)
params = json.dumps(t.get("parameters", {}), ensure_ascii=False)
tool_defs.append(
f"### {t['name']}\n"
f"- Description: {t.get('description', 'No description')}\n"
f"- Parameters: `{params}`\n"
f"- Call ID: `call_{t['name']}_{i}`\n"
)
# Forced tool choice
forced_tool = None
if isinstance(tool_choice, dict):
forced_tool = tool_choice.get("function", {}).get("name")
elif tool_choice == "required":
forced_tool = "__required__"
force_line = ""
if forced_tool and forced_tool != "__required__":
force_line = f"\n**CRITICAL: You MUST call `{forced_tool}`. No text — only JSON.**\n"
elif forced_tool == "__required__":
force_line = "\n**CRITICAL: You MUST call one of the functions. No text — only JSON.**\n"
return (
"# SYSTEM: Function Calling Mode\n\n"
"You are a function-calling assistant. Available functions:\n\n"
+ "\n".join(tool_defs)
+ "\n\n## Response Format\n"
"```json\n"
"{\n"
' "tool_calls": [\n'
' {\n'
' "id": "call_<name>_<N>",\n'
' "type": "function",\n'
' "function": {\n'
' "name": "<name>",\n'
' "arguments": "<JSON-string>"\n'
' }\n'
' }\n'
' ]\n'
'}\n'
"```\n\n"
"## Rules\n"
"- If the user's request requires a function, respond with ONLY the JSON above — no other text.\n"
"- The `arguments` field MUST be a valid JSON string (double-escaped).\n"
"- Use correct JSON syntax — no trailing commas, no comments.\n"
+ force_line
+ "\n## User Request\n"
)
```
## Трёхуровневый парсинг v2
```python
def parse_tool_calls(text: str) -> list | None:
"""Level 1: direct JSON parse"""
try:
data = json.loads(text.strip())
if "tool_calls" in data:
return _normalize(data["tool_calls"])
except: pass
"""Level 2: JSON in markdown code blocks"""
for pat in [
r'```json\s*(\{[\s\S]*?"tool_calls"[\s\S]*?\})\s*```',
r'```\s*(\{[\s\S]*?"tool_calls"[\s\S]*?\})\s*```',
]:
m = re.search(pat, text)
if m:
try:
data = json.loads(m.group(1))
if "tool_calls" in data:
return _normalize(data["tool_calls"])
except: continue
"""Level 3: regex fallback — any JSON with tool_calls"""
for m in re.finditer(r'\{[\s\S]*?"tool_calls"[\s\S]*?\}', text):
try:
data = json.loads(m.group(0))
if "tool_calls" in data:
return _normalize(data["tool_calls"])
except: continue
return None
def _normalize(tool_calls):
"""Validate and normalize: arguments must be valid JSON string."""
for tc in tool_calls:
fn = tc.get("function", {})
args = fn.get("arguments", "{}")
if isinstance(args, dict):
args = json.dumps(args, ensure_ascii=False)
elif isinstance(args, str):
json.loads(args) # validate
fn["arguments"] = args
return tool_calls
```
## Retry-логика
```python
TOOL_RETRY_MAX = 2
for attempt in range(TOOL_RETRY_MAX + 1):
# ... execute chat ...
tool_calls = parse_tool_calls(full_text)
requires_tool = (
isinstance(tool_choice, dict) or
tool_choice == "required"
)
if requires_tool and not tool_calls and attempt < TOOL_RETRY_MAX:
# Усиливаем промпт
user_content += (
"\n\n**ERROR: You did not call the required function. "
"Respond with ONLY the function call JSON. No other text.**"
)
continue
break
```
## OpenAI-ответ
### Нестрим
```json
{
"choices": [{
"message": {
"role": "assistant",
"content": null,
"tool_calls": [{
"id": "call_get_weather_0",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"London\"}"
}
}]
},
"finish_reason": "tool_calls"
}]
}
```
### Стрим
```
data: {"choices":[{"delta":{"tool_calls":[{"index":0,"id":"call_get_weather_0","type":"function","function":{"name":"get_weather","arguments":""}}]}}]}
data: {"choices":[{"delta":{},"finish_reason":"tool_calls"}]}
data: [DONE]
```
## tool_choice — полная поддержка
| Значение | Поведение |
|----------|-----------|
| `"auto"` (default) | Инжектить тулзы, модель сама решает |
| `"none"` | Не инжектить тулзы, модель отвечает текстом |
| `"required"` | Форсировать вызов любого тула + retry |
| `{type:"function", function:{name:"X"}}` | Форсировать конкретный тул + retry |
## Ограничения
- **Слабые модели** (GPT-5 Nano, мелкие Llama) могут не следовать формату. Retry помогает но не гарантирует.
- **Стриминг** — tool_calls не парсятся на лету, клиент должен накопить полный ответ.
- **Multi-turn** — `tool` role сообщения + `tool_call_id` не пробрасываются (одноразовые сессии).
- **Parallel tool calls** — теоретически поддерживается форматом, но модель редко выдаёт больше одного.
## Рекомендуемые модели
Для надёжного tool use через промпт-инжект:
- **DeepSeek V3.2** — "agentic tool-use performance"
- **Mistral Small 3.2 24B** — "improved function calling"
- **Inkling (Thinking Machines)** — "tool-use systems"
- **Command R7B (Cohere)** — "tool use, agents"

View File

@@ -0,0 +1,88 @@
# Gateway Verification Checklist
После создания/перезапуска шлюза — полный цикл проверки всех endpoint-ов.
## 1. Health Check
```bash
curl -s http://127.0.0.1:PORT/health \
-H "Authorization: Bearer $KEY" | python -m json.tool
```
Ожидаемый ответ: `pool.total_accounts`, `pool.available`, `models`, `gateway_key`.
## 2. Non-Streaming Chat (N запросов = размер пула)
```bash
for i in $(seq 1 N); do
curl -s -X POST http://127.0.0.1:PORT/v1/chat/completions \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-5-nano","messages":[{"role":"user","content":"Say OK"}],"max_tokens":5}' \
| python -c "import sys,json; print(json.load(sys.stdin)['choices'][0]['message']['content'][:30])"
done
```
**Проверяем**: все N запросов возвращают 200 + контент (ротация работает).
## 3. Streaming
```bash
curl -s -N -X POST http://127.0.0.1:PORT/v1/chat/completions \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-5-nano","messages":[{"role":"user","content":"Say hi"}],"max_tokens":10,"stream":true}' \
--max-time 20
```
**Проверяем**: NDJSON/SSE чанки с `data:` + завершающий `data: [DONE]`.
**ВАЖНО**: не тестировать streaming через `execute_code` (Python `resp.read()` вешает инструмент). Только `curl -N`.
## 4. Tool Use
```bash
curl -s -X POST http://127.0.0.1:PORT/v1/chat/completions \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-5-nano","messages":[{"role":"user","content":"What is 15+27? Use calculator."}],"tools":[{"type":"function","function":{"name":"calculator","parameters":{"type":"object","properties":{"expr":{"type":"string"}}}}}],"tool_choice":"auto","max_tokens":100}'
```
**Проверяем**: `finish_reason: "tool_calls"` + `tool_calls[]` с именем функции и аргументами.
## 5. Pool Stats
```bash
curl -s http://127.0.0.1:PORT/health \
-H "Authorization: Bearer $KEY" \
| python -c "import sys,json; d=json.load(sys.stdin); [print(f'{a[\"name\"]}: {a[\"requests\"]} reqs, {a[\"errors\"]} errs, avail={a[\"available\"]}') for a in d['pool']['accounts']]"
```
**Проверяем**: все аккаунты `available=true`, ошибок нет, запросы распределены.
## 6. Models
```bash
curl -s http://127.0.0.1:PORT/v1/models \
-H "Authorization: Bearer $KEY" \
| python -c "import sys,json; d=json.load(sys.stdin); print(f'{len(d[\"data\"])} models')"
```
## 7. Gateway Key Recovery
Если 401 — ключ не в коде, а в state-файле:
```bash
python -c "import json; print(json.load(open('PROJECT_gateway_state.json'))['gateway_key'])"
```
## Expected Full Output
```
HEALTH: 200 | pool: 10 total, 10 avail
NON-STREAMING: 10/10 passed
STREAMING: chunks > 0, text: "Hi there!..."
TOOL USE: 1 tool call, calculator({"expr":"15+27"})
POOL: guest-1..N: 1-2 reqs, 0 errs, avail=True
MODELS: 35
```

View File

@@ -0,0 +1,65 @@
# grok2api Model Patching Pattern
When adding a new Grok model to the grok2api codebase, 6 files across 3 layers must be updated.
## Files to Patch
### Layer 1: Control Plane — Model Registry
**`app/control/model/enums.py`**
- Add new `ModeId` enum value (e.g., `GROK_4_6 = 5`)
- Add mapping in `to_api_str()` method
- Add to `ALL_MODES_FULL` tuple
**`app/control/model/registry.py`**
- Add `ModelSpec` entries for the new model and its variants
- Example pattern:
```python
ModelSpec("grok-4.6", ModeId.GROK_4_6, Tier.SUPER, Capability.CHAT, True, "Grok 4.6"),
ModelSpec("grok-4.6-super", ModeId.GROK_4_6, Tier.SUPER, Capability.CHAT, True, "Grok 4.6 Super"),
ModelSpec("grok-4.6-heavy", ModeId.GROK_4_6, Tier.HEAVY, Capability.CHAT, True, "Grok 4.6 Heavy"),
ModelSpec("grok-4.6-best", ModeId.GROK_4_6, Tier.SUPER, Capability.CHAT, True, "Grok 4.6 Best", prefer_best=True),
```
### Layer 2: Control Plane — Account Models
**`app/control/account/models.py`**
- Add `grok_4_6: QuotaWindow | None = None` to `AccountQuotaSet`
- Add `mode_id == 5` case in `get()` method
- Add `mode_id == 5` case in `set()` method
- Add `grok_4_6` to `to_dict()` and `from_dict()`
**`app/control/account/commands.py`**
- Add `quota_grok_4_6: dict[str, Any] | None = None` to `AccountPatch`
### Layer 3: Data Plane — Runtime Tables
**`app/dataplane/shared/enums.py`**
- Add `GROK_4_6 = 5` to `ModeId`
- Add to `ALL_MODE_IDS` tuple
**`app/dataplane/account/table.py`**
- Add `quota_grok_4_6_by_idx`, `total_grok_4_6_by_idx`, `window_grok_4_6_by_idx`, `reset_grok_4_6_at_by_idx` column arrays
- Update `_quota_col()`, `_reset_col()`, `_total_col()`, `_window_col()` to handle `mode_id == 5`
- Update `_append_slot()` signature and body with `grok_4_6` parameters
- Update `_update_slot()` signature and body with `grok_4_6` parameters
## Verification
After patching, run syntax check:
```bash
cd C:\Users\User\grok2api\grok2api
python -c "
import ast, sys
for f in ['app/control/model/enums.py','app/control/model/registry.py',
'app/control/account/models.py','app/control/account/commands.py',
'app/dataplane/shared/enums.py','app/dataplane/account/table.py']:
ast.parse(open(f).read()); print(f'OK: {f}')
"
```
## Pitfalls
- **Columnar arrays**: the table.py uses typed `array.array` columns. Each new mode needs 4 new columns (quota, total, window, reset). The `_append_slot` and `_update_slot` methods have long parameter lists that must match exactly.
- **Mode ID consistency**: the control-plane `ModeId` and data-plane `ModeId` must have the same integer values. The data-plane enum is a separate copy in `dataplane/shared/enums.py`.
- **The protocol layer (`xai_chat.py`) uses `mode_id.to_api_str()` generically** — no changes needed there if the mode_id mapping is correct.

View File

@@ -0,0 +1,108 @@
# xAI OAuth PKCE Flow
Complete constants and flow for xAI (Grok) OAuth auto-registration. Mirrors 9router's implementation.
## Constants
```python
XAI_CLIENT_ID = "b1a00492-073a-47ea-816f-4c329264a828"
XAI_ISSUER = "https://auth.x.ai"
XAI_SCOPE = "openid profile email offline_access grok-cli:access api:access"
XAI_LOOPBACK_PORT = 56121
XAI_CALLBACK_PATH = "/callback"
XAI_REDIRECT_URI = f"http://127.0.0.1:{XAI_LOOPBACK_PORT}{XAI_CALLBACK_PATH}"
XAI_PKCE_VERIFIER_BYTES = 96
XAI_API_BASE = "https://api.x.ai/v1"
XAI_USER_AGENT = "grok-cli/9router"
```
## OIDC Discovery
```
GET https://auth.x.ai/.well-known/openid-configuration
→ authorization_endpoint, token_endpoint
Static fallbacks:
authorize: https://auth.x.ai/oauth2/authorize
token: [REDACTED]
```
## OAuth Flow
### 1. PKCE Setup
```python
verifier = base64url(secrets.token_bytes(96)) # 96 bytes, no padding
challenge = base64url(sha256(verifier.encode()))
state = secrets.token_urlsafe(32)
```
### 2. Authorization URL
```
GET {authorize_url}?
response_type=code
client_id=b1a00492-073a-47ea-816f-4c329264a828
redirect_uri=http://127.0.0.1:56121/callback
scope=openid profile email offline_access grok-cli:access api:access
code_challenge={challenge}
code_challenge_method=S256
state={state}
nonce={hex}
plan=generic
referrer=cli-proxy-api
```
### 3. Token Exchange
```python
POST {token_url}
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
client_id=b1a00492-073a-47ea-816f-4c329264a828
code={code}
redirect_uri=http://127.0.0.1:56121/callback
code_verifier={verifier}
# Response:
# {"access_token": "xai-...", "refresh_token": "...", "expires_in": 3600, ...}
```
### 4. Token Refresh
```python
POST {token_url}
grant_type=refresh_token
client_id=b1a00492-073a-47ea-816f-4c329264a828
refresh_token={refresh_token}
```
## API Usage
```python
POST https://api.x.ai/v1/chat/completions
Authorization: Bearer {access_token}
Content-Type: application/json
User-Agent: grok-cli/9router
{"model": "grok-4.6", "messages": [{"role": "user", "content": "..."}], "stream": true}
```
## Grok 4.6 Model Details
- Released: August 12, 2026
- Context: 500,000 tokens
- Pricing: $2/$0.50/$6 per 1M tokens (input/cached/output) below 200K
- Reasoning: low, medium, high (default), xhigh
- Modalities: text + image input, text output
- API: Chat Completions + Responses API
- Also available: grok-build-0.1 (coding agent model)
## Gateway Implementation
See `gw46.py` at `C:\Users\User\Documents\grok46gw\gw46.py` for a complete FastAPI gateway:
- Key pool (env vars + SQLite DB + OAuth tokens)
- Round-robin key selection
- OpenAI-compatible /v1/chat/completions and /v1/responses
- OAuth auto-registration web flow at /oauth/register
- Account management at /accounts