Initial commit: Hermes Agent Skills collection

This commit is contained in:
Debian
2026-07-12 19:02:59 +00:00
commit e9cc106625
789 changed files with 233126 additions and 0 deletions
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 (~890ms1700ms for 50100 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.