Files
Antrophic-Qwen3.6-Proxy/doc/proxy-analysis.md
2026-05-10 10:46:41 +02:00

4.1 KiB

Proxy Implementierungsanalyse

Was der Proxy macht

Übersetzt Anthropic API (POST /v1/messages) → Ollama API (POST /api/chat)

  • Empfängt Anthropic SSE-Format
  • Gibt Anthropic SSE-Format zurück
  • Routing: localhost:11435https://ollama.aquantico.de/api/chat

Korrekte Implementierungen ✓

1. Model-Substitution (korrekt)

if (anthropicBody.model?.startsWith('claude-')) {
  anthropicBody.model = 'qwen3.6:35b-a3b-q4_K_M';
}

Alle claude-* Modelle werden durch das lokale Modell ersetzt.

2. Think-Modus deaktiviert (korrekt)

think: false

Hardcoded in convertAnthropicToOllama().

3. Tool-Schema-Konvertierung (korrekt)

Anthropic input_schema → Ollama function.parameters:

{ type: 'function', function: { name, description, parameters: sanitizeToolSchema(tool.input_schema) } }

4. Tool-Call-Parsing in Response (korrekt)

Ollama gibt tc.function.name und tc.function.arguments zurück — genau das liest der Proxy:

const toolName  = tc.function?.name;
const toolInput = parseToolArguments(tc.function?.arguments);

5. SSE-Event-Sequenz (korrekt)

Ausgabe entspricht Anthropic-Spec: message_startcontent_block_startcontent_block_deltacontent_block_stopmessage_deltamessage_stop

6. stop_reason (korrekt)

stop_reason: emittedToolUse ? 'tool_use' : 'end_turn'

7. Tool-Deduplizierung (korrekt)

Verhindert doppelte Tool-Calls via seenToolCalls Set mit Key name:args.


Bekannte Bugs / Schwachstellen ⚠️

BUG 1: Leerer finaler Buffer-Handler (app.js:350-358)

if (buffer.trim()) {
  try {
    const data = JSON.parse(buffer.trim());
    // gleichen Handling-Code wie oben ausführen  ← LEER, nie ausgeführt!
  } catch (e) { ... }
}

Problem: Wenn das letzte NDJSON-Chunk von Ollama nicht mit \n endet (was bei einigen Ollama-Versionen vorkommt), bleibt die finale done: true-Zeile im Buffer und wird nicht verarbeitet.

Auswirkung: messageFinished bleibt false, Fallback-Code (Zeile 360-381) sendet die Abschluss-Events ohne eval_count (output_tokens=0).

Fix: Den gleichen Parsing-Code aus der while-Schleife in den finalen Buffer-Handler kopieren.

BUG 2: message_start ohne usage.input_tokens (app.js:200-209)

res.write(`event: message_start\ndata: ${JSON.stringify({
  type: 'message_start',
  message: {
    id: messageId, type: 'message', role: 'assistant', content: [],
    model: anthropicBody.model
    // fehlt: stop_reason: null, usage: { input_tokens: 0, output_tokens: 0 }
  }
})}\n\n`);

Auswirkung: Anthropic-kompatible Clients erwarten usage.input_tokens in message_start. Kann bei strikten Clients zu Parse-Fehlern führen.

BUG 3: tool_use/tool_result als Text im Nachrichten-Verlauf

Wenn Anthropic-Clients tool_use (in Assistant-Nachrichten) und tool_result (in User-Nachrichten) im History senden, werden diese als Text-Strings in den Ollama-Messages eingebettet:

"Previous assistant tool call already made.\nTool name: ...\n..."

Korrekt wäre: Assistant-Nachrichten mit tool_calls senden, Tool-Results als role: "tool" Nachricht.

Auswirkung: Das Modell versteht den Tool-Call-Verlauf semantisch nicht korrekt. Die bestehende Deduplizierungs-Logik kompensiert dies teilweise.


Architektur-Übersicht

Client (Claude SDK)
    │  POST /v1/messages (Anthropic Format)
    ▼
noThinkProxy :11435
    │  convertAnthropicToOllama()
    │  - system → messages[0] role:system
    │  - tool_use → text string
    │  - tool_result → text string
    │  - model: claude-* → qwen3.6:35b-a3b-q4_K_M
    │  - think: false
    │  - options: { num_ctx:131072, num_predict, temperature:0.7 }
    │
    │  POST /api/chat (Ollama NDJSON)
    ▼
Ollama https://ollama.aquantico.de
    │  NDJSON stream: {message:{content, tool_calls}, done}
    ▼
noThinkProxy
    │  handleResponse()
    │  - text → content_block_delta (text_delta)
    │  - tool_calls → content_block_start/delta/stop (tool_use)
    │  - done → message_delta + message_stop
    ▼
Client (Anthropic SSE)