Initial commit: Hermes Agent Skills collection
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,79 @@
|
||||
# Noris Provider: OpenAI Endpoint (vLLM)
|
||||
|
||||
## Endpoint Status
|
||||
|
||||
Noris currently runs **vLLM with OpenAI-compatible API only**. The Anthropic-compatible endpoint does **not work**.
|
||||
|
||||
| Format | Base URL | Status |
|
||||
|--------|----------|--------|
|
||||
| OpenAI | `https://ai.noris.de/v1` | ✅ Working |
|
||||
| Anthropic | `https://ai.noris.de/anthropic` | ❌ HTTP 405 (Method Not Allowed) |
|
||||
|
||||
**Only endpoint that works:** `POST https://ai.noris.de/v1/chat/completions`
|
||||
|
||||
## Correct Hermes Config
|
||||
|
||||
```yaml
|
||||
providers:
|
||||
noris:
|
||||
type: openai
|
||||
base_url: https://ai.noris.de/v1
|
||||
api_key: sk-bf-8779d73c-6a51-49d6-a22f-00a0290ca6a7
|
||||
```
|
||||
|
||||
**Critical:** Use `https://`, **NOT** `http://` — HTTP returns 503. The path must be exactly `https://ai.noris.de/v1` (no trailing `/chat/completions`; Hermes appends that).
|
||||
|
||||
## Prompt Caching Status: NOT FUNCTIONAL
|
||||
|
||||
**Test result (2026-06-18):** `cache_control` blocks are silently ignored by the vLLM backend. vLLM may do transparent prefix caching internally, but the API does not expose cache hit/miss counters.
|
||||
|
||||
- Anthropic-style `cache_control: {type: "ephemeral"}` → not supported (Anthropic endpoint is 405 anyway)
|
||||
- OpenAI-style prefix caching → vLLM handles this automatically, but no API visibility
|
||||
|
||||
## Why Neither Caching Approach Works via API
|
||||
|
||||
The Anthropic endpoint (`/anthropic`) returns HTTP 405 for all tested paths (`/`, `/v1/messages`). The OpenAI endpoint (`/v1/chat/completions`) runs vLLM 0.22.1, which does **not** expose `cache_read_input_tokens` or similar in the `usage` object. Any client-side cache accounting will always show zero.
|
||||
|
||||
## Cost / Speed Implications
|
||||
|
||||
- **No explicit caching benefit visible to client.** Every request carries full prompt cost.
|
||||
- **No speed gain measurable via API.** vLLM may internally cache KV-cache prefixes, but this is opaque.
|
||||
- **No harm** sending `cache_control` (it is ignored), but also no benefit.
|
||||
|
||||
## Recommendation
|
||||
|
||||
Always use the OpenAI endpoint (`type: openai`, `base_url: https://ai.noris.de/v1`). Do not attempt Anthropic format with Noris.
|
||||
|
||||
## Verification Command (OpenAI)
|
||||
|
||||
```bash
|
||||
API_KEY="$(grep -A1 'providers:' ~/.hermes/config.yaml | grep api_key | head -n1 | sed 's/.*: //')"
|
||||
|
||||
curl -s -X POST https://ai.noris.de/v1/chat/completions \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer ${API_KEY}" \
|
||||
-d '{
|
||||
"model": "vllm/qwen3.6-27b-nvfp4",
|
||||
"max_tokens": 50,
|
||||
"messages": [{"role": "user", "content": "Say hi"}]
|
||||
}' | python3 -c "import sys,json; d=json.load(sys.stdin); print('Model:', d.get('model')); print('Usage:', d.get('usage')); print('Content:', d.get('choices',[{}])[0].get('message',{}).get('content'))"
|
||||
```
|
||||
|
||||
## Noris Virtual Key Routing: `vllm/` Prefix Required
|
||||
|
||||
Noris uses **virtual-key routing** — the `vllm/` prefix in model names is **not optional**, it is how the provider routes your request to the correct inference backend. Omitting it causes "virtual key not found" errors even when the API key is valid.
|
||||
|
||||
| ❌ Wrong | ✅ Right |
|
||||
|---------|----------|
|
||||
| `moonshotai/kimi-k2.6` | `vllm/moonshotai/kimi-k2.6` |
|
||||
|
||||
**This applies everywhere:** `model.model`, `model.default`, cronjob `model` fields, auxiliary overrides, and inline `/model` commands.
|
||||
|
||||
Hermes itself strips the `vllm/` prefix when calling the provider, but Noris requires it in the model identifier. If you see **HTTP 401 "virtual key not found"** with a valid API key, check that the model name includes the `vllm/` prefix.
|
||||
|
||||
### Quick verification
|
||||
|
||||
```bash
|
||||
hermes config get model.model # should start with "vllm/"
|
||||
hermes cron list # check model column for missing prefix
|
||||
```
|
||||
@@ -0,0 +1,113 @@
|
||||
# Noris Provider: Available Models
|
||||
|
||||
As of 2026-06-27, the noris provider at `https://ai.noris.de/v1` exposes these models for the Schön Consulting virtual key:
|
||||
|
||||
## Anthropic Models (vision-capable)
|
||||
|
||||
- `anthropic/claude-fable-5`
|
||||
- `anthropic/claude-haiku-4-5-20251001` — ✅ fast, vision-capable, good for auxiliary.vision
|
||||
- `anthropic/claude-opus-4-1-20250805`
|
||||
- `anthropic/claude-opus-4-5-20251101`
|
||||
- `anthropic/claude-opus-4-6`
|
||||
- `anthropic/claude-opus-4-7`
|
||||
- `anthropic/claude-opus-4-8`
|
||||
- `anthropic/claude-sonnet-4-5-20250929`
|
||||
- `anthropic/claude-sonnet-4-6`
|
||||
|
||||
## Self-hosted vLLM Models
|
||||
|
||||
- `vllm/gemma-4-31b-it` — usable (nutrition-coach uses this)
|
||||
- `vllm/gemma-4-31b-it-dynamo`
|
||||
- `vllm/glm-5-2-nvfp4` — ✅ verified working (2026-06-27). Current CEO/default model.
|
||||
- `vllm/gpt-oss-120b` — usable
|
||||
- `vllm/harrier-oss-v1-0.6b`
|
||||
- `vllm/moonshotai/kimi-k2.6` — usable (infra-sre uses this)
|
||||
- `vllm/qwen3.5-122b-a10b`
|
||||
- `vllm/qwen3.6-27b-nvfp4` — ✅ verified working (2026-06-18). Returns reasoning-only format: `content: null`, `reasoning: "..."`
|
||||
- `vllm/qwen3.6-35b-a3b`
|
||||
|
||||
## Vision / Multimodal Capability (tested 2026-06-27)
|
||||
|
||||
Tested by sending image+text to `/v1/chat/completions`:
|
||||
|
||||
| Model | Vision? | Notes |
|
||||
|-------|---------|-------|
|
||||
| `vllm/gemma-4-31b-it` | ✅ Yes | Good descriptions, recommended for `auxiliary.vision` |
|
||||
| `vllm/qwen3.5-122b-a10b` | ✅ Yes | Works but terse output |
|
||||
| `vllm/glm-5-2-nvfp4` | ❌ No | Returns `"glm-5-2-nvfp4 is not a multimodal model"` |
|
||||
| `vllm/qwen3.6-27b-nvfp4` | ❌ No | HTTP 400 |
|
||||
| All `anthropic/*` models | ✅ Yes | `claude-haiku-4-5` fastest option |
|
||||
|
||||
**User preference: prefer `vllm/` models over `anthropic/` for auxiliary tasks.**
|
||||
|
||||
Current `auxiliary.vision` config:
|
||||
```bash
|
||||
hermes config set auxiliary.vision.provider noris
|
||||
hermes config set auxiliary.vision.model vllm/gemma-4-31b-it
|
||||
hermes config set auxiliary.vision.base_url ''
|
||||
hermes config set auxiliary.vision.api_key ''
|
||||
```
|
||||
|
||||
### Testing vision capability programmatically
|
||||
|
||||
```python
|
||||
import yaml, urllib.request, json, base64
|
||||
|
||||
with open("/home/debian/.hermes/config.yaml") as f:
|
||||
cfg = yaml.safe_load(f)
|
||||
key = cfg["providers"]["noris"]["api_key"]
|
||||
|
||||
with open("test_image.jpg", "rb") as f:
|
||||
img_b64 = base64.b64encode(f.read()).decode()
|
||||
|
||||
payload = {
|
||||
"model": "<MODEL_NAME>",
|
||||
"messages": [{"role": "user", "content": [
|
||||
{"type": "text", "text": "Describe this image briefly."},
|
||||
{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{img_b64}"}}
|
||||
]}],
|
||||
"max_tokens": 50
|
||||
}
|
||||
req = urllib.request.Request(
|
||||
"https://ai.noris.de/v1/chat/completions",
|
||||
data=json.dumps(payload).encode(),
|
||||
headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"}
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
print(json.loads(resp.read())["choices"][0]["message"]["content"])
|
||||
except Exception as e:
|
||||
print(f"Not multimodal: {e}")
|
||||
```
|
||||
|
||||
## Embedding / Reranker Models (non-chat)
|
||||
|
||||
- `vllm/bge-reranker-v2-m3`
|
||||
- `vllm/jina-reranker-v2-base-multilingual`
|
||||
|
||||
## Blocked Models
|
||||
|
||||
- `vllm/gemma-4-27b-it` — returns 403: `{"type":"model_blocked","error":{"message":"Model 'gemma-4-27b-it' is not allowed for this virtual key"}}`
|
||||
|
||||
## Verification
|
||||
|
||||
```python
|
||||
import yaml, urllib.request, json
|
||||
|
||||
with open("/home/debian/.hermes/config.yaml") as f:
|
||||
cfg = yaml.safe_load(f)
|
||||
key = cfg["providers"]["noris"]["api_key"]
|
||||
|
||||
req = urllib.request.Request(
|
||||
"https://ai.noris.de/v1/models",
|
||||
headers={"Authorization": f"Bearer {key}"}
|
||||
)
|
||||
with urllib.request.urlopen(req) as resp:
|
||||
models = json.loads(resp.read())
|
||||
for m in sorted(models["data"], key=lambda x: x["id"]):
|
||||
print(m["id"])
|
||||
```
|
||||
|
||||
Always verify model availability via the `/v1/models` endpoint before assigning to an agent profile. A blocked model will cause silent 403 errors on first user interaction.
|
||||
|
||||
**Vision pitfall:** Not all chat models support image input. `vllm/glm-5-2-nvfp4` (the default CEO model) is NOT multimodal — `vision_analyze` will fail with `"is not a multimodal model"`. Set `auxiliary.vision.model` to a vision-capable model like `vllm/gemma-4-31b-it`. See the "Vision / Multimodal Capability" section above for the full tested matrix.
|
||||
@@ -0,0 +1,38 @@
|
||||
# Noris / vLLM Model Name Prefix Pitfall
|
||||
|
||||
## Problem
|
||||
|
||||
When a model name contains a `vllm/` prefix (e.g. `vllm/moonshotai/kimi-k2.6`) inside `model.model` or `model.default`, Hermes resolves it to `noris/vllm/moonshotai/kimi-k2.6`. This fails model catalog lookup because the prefix is interpreted as part of the model string, not a provider qualifier.
|
||||
|
||||
## When It Happens
|
||||
|
||||
- Profile was previously configured for a **custom/local vLLM endpoint** where `vllm/…` is required
|
||||
- Later migrated to **Noris (managed provider)** without stripping the prefix
|
||||
- Any provider where model catalog lookup happens (OpenRouter, Nous Portal, Noris, etc.)
|
||||
|
||||
## Fix
|
||||
|
||||
Strip the `vllm/` prefix when using a managed provider:
|
||||
|
||||
```bash
|
||||
hermes config set model.model moonshotai/kimi-k2.6
|
||||
hermes config set model.default moonshotai/kimi-k2.6
|
||||
```
|
||||
|
||||
When using a **custom vLLM endpoint** (e.g. local server on `http://10.0.30.99:8080/v1`), keep the prefix:
|
||||
|
||||
```yaml
|
||||
model:
|
||||
model: vllm/moonshotai/kimi-k2.6
|
||||
provider: custom
|
||||
base_url: http://10.0.30.99:8080/v1
|
||||
```
|
||||
|
||||
## Detection
|
||||
|
||||
Check if `hermes config` shows a `vllm/` prefix in the Model line:
|
||||
```bash
|
||||
hermes config | grep -E "Model:|model:"
|
||||
```
|
||||
|
||||
If it shows `vllm/` and the provider is Noris/OpenRouter/Nous (not `custom`), strip it.
|
||||
@@ -0,0 +1,86 @@
|
||||
# Noris: vLLM Reasoning Format Behavior
|
||||
|
||||
## Observed Behavior (ai.noris.de, vLLM 0.22.1)
|
||||
|
||||
Models on the Noris vLLM cluster that have chain-of-thought reasoning enabled return **only a `reasoning` field**, with `content: null`:
|
||||
|
||||
```json
|
||||
{
|
||||
"choices": [{
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": null,
|
||||
"reasoning": "Here's a thinking process:\n\n1. **Analyze User Input:** ...",
|
||||
"reasoning_details": [{
|
||||
"index": 0,
|
||||
"type": "reasoning.text",
|
||||
"text": "Here's a thinking process:\n\n1. **Analyze User Input:** ..."
|
||||
}]
|
||||
}
|
||||
}],
|
||||
"usage": {
|
||||
"prompt_tokens": 21,
|
||||
"completion_tokens": 100,
|
||||
"total_tokens": 121
|
||||
},
|
||||
"system_fingerprint": "vllm-0.22.1-a8c0978c",
|
||||
"extra_fields": {
|
||||
"request_type": "chat_completion",
|
||||
"provider": "vllm",
|
||||
"latency": 1738,
|
||||
"chunk_index": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## What This Means for Clients
|
||||
|
||||
| Field | Value | Implication |
|
||||
|-------|-------|-------------|
|
||||
| `content` | `null` | No final assistant message text |
|
||||
| `reasoning` | Non-empty string | Chain-of-thought thinking process |
|
||||
| `reasoning_details` | Array of blocks | Token-level reasoning decomposition |
|
||||
| `finish_reason` | `"length"` | Fixed length responses even if reasoning completes mid-sentence |
|
||||
|
||||
## Models Affected
|
||||
|
||||
| Model | Reasoning Field |
|
||||
|-------|-----------------|
|
||||
| `vllm/qwen3.6-27b-nvfp4` | ✅ reasoning-only |
|
||||
| `vllm/moonshotai/kimi-k2.6` | Unknown (not tested for this pattern) |
|
||||
| `vllm/gpt-oss-120b` | Unknown |
|
||||
| `vllm/gemma-4-31b-it` | Unknown |
|
||||
|
||||
## Client Handling
|
||||
|
||||
When integrating with Noris vLLM reasoning models, clients must:
|
||||
|
||||
1. **Check `reasoning` before `content`** — `content` will be `null`
|
||||
2. **Extract text from `reasoning` field** for user-facing output
|
||||
3. **Account for `finish_reason: "length"`** — reasoning may be truncated mid-thought
|
||||
4. **Consider latency** — reasoning models are slower (~890ms–1700ms for 50–100 tokens on Noris)
|
||||
|
||||
## Example: Minimal curl test with reasoning extraction
|
||||
|
||||
```bash
|
||||
curl -sS -X POST "https://ai.noris.de/v1/chat/completions" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer sk-bf-..." \
|
||||
-d '{
|
||||
"model": "vllm/qwen3.6-27b-nvfp4",
|
||||
"max_tokens": 100,
|
||||
"messages": [{"role": "user", "content": "What is prompt caching?"}]
|
||||
}' | python3 -c "
|
||||
import sys, json
|
||||
d = json.load(sys.stdin)
|
||||
msg = d['choices'][0]['message']
|
||||
print('Content:', msg.get('content'))
|
||||
print('Has reasoning:', 'reasoning' in msg)
|
||||
print('Reasoning preview:', msg.get('reasoning', 'N/A')[:200])
|
||||
print('Usage:', d['usage'])
|
||||
"
|
||||
```
|
||||
|
||||
## OpenAI API Spec Compliance
|
||||
|
||||
This is **non-standard** OpenAI-format behavior. Per OpenAI spec, `content` should contain the assistant's message text. vLLM with reasoning enabled diverges by putting the model output in `reasoning` and leaving `content` null. Clients expecting standard OpenAI responses will see empty assistant messages unless they handle this field.
|
||||
Reference in New Issue
Block a user