feat(local-llm): B1b-Plumbing — tools/tool_calls durch Adapter/Bridge/Brain-Client
Traegt OpenAI-Tool-Definitionen (tools) durch den ganzen lokalen Pfad und gibt tool_calls zurueck: - adapter.py: tools -> llama.cpp /v1/chat/completions (tool_choice=auto), message.tool_calls zurueck in llm_response. - aria_bridge.py: _local_llm + /internal/local-llm reichen tools durch, geben tool_calls zurueck. - local_llm.py: local_llm_chat akzeptiert tools, result enthaelt tool_calls. Inert bis der Brain-Tool-Loop (naechster Schritt) tools uebergibt — Verhalten unveraendert. Tool-Set + lokale Tool-Loop + Router-Anpassung folgen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -69,9 +69,12 @@ async def _send(ws, mtype: str, payload: dict) -> None:
|
||||
|
||||
|
||||
async def _call_llama(messages: list, *, max_tokens: int, temperature: float,
|
||||
stop) -> dict:
|
||||
stop, tools=None) -> dict:
|
||||
"""Ruft llama.cpp /v1/chat/completions (OpenAI-Format). Gibt
|
||||
{ok, content, error} zurueck — wirft nie."""
|
||||
{ok, content, tool_calls, error} zurueck — wirft nie.
|
||||
|
||||
tools: optionale OpenAI-Tool-Definitionen (B1b). llama.cpp (--jinja) mit
|
||||
Qwen3 kann natives Tool-Calling und liefert dann message.tool_calls."""
|
||||
body = {
|
||||
"model": LLM_MODEL,
|
||||
"messages": messages,
|
||||
@@ -81,6 +84,9 @@ async def _call_llama(messages: list, *, max_tokens: int, temperature: float,
|
||||
}
|
||||
if stop:
|
||||
body["stop"] = stop
|
||||
if tools:
|
||||
body["tools"] = tools
|
||||
body["tool_choice"] = "auto"
|
||||
if LLM_DISABLE_THINKING:
|
||||
# llama.cpp (--jinja) reicht chat_template_kwargs an die Chat-Vorlage
|
||||
# weiter. Qwen3 unterdrueckt damit den <think>-Block.
|
||||
@@ -90,8 +96,13 @@ async def _call_llama(messages: list, *, max_tokens: int, temperature: float,
|
||||
r = await client.post(f"{LLAMA_URL}/v1/chat/completions", json=body)
|
||||
r.raise_for_status()
|
||||
data = r.json()
|
||||
content = (data.get("choices") or [{}])[0].get("message", {}).get("content", "")
|
||||
return {"ok": True, "content": content or "", "usage": data.get("usage")}
|
||||
msg = (data.get("choices") or [{}])[0].get("message", {}) or {}
|
||||
return {
|
||||
"ok": True,
|
||||
"content": msg.get("content") or "",
|
||||
"tool_calls": msg.get("tool_calls") or None,
|
||||
"usage": data.get("usage"),
|
||||
}
|
||||
except Exception as e:
|
||||
logger.warning("llama.cpp-Call fehlgeschlagen: %s", e)
|
||||
return {"ok": False, "content": "", "error": str(e)[:300]}
|
||||
@@ -108,17 +119,20 @@ async def _handle_llm_request(ws, payload: dict) -> None:
|
||||
max_tokens = int(payload.get("max_tokens", 512) or 512)
|
||||
temperature = float(payload.get("temperature", 0.7) or 0.7)
|
||||
stop = payload.get("stop")
|
||||
tools = payload.get("tools") or None
|
||||
t0 = time.time()
|
||||
res = await _call_llama(messages, max_tokens=max_tokens,
|
||||
temperature=temperature, stop=stop)
|
||||
temperature=temperature, stop=stop, tools=tools)
|
||||
dt = time.time() - t0
|
||||
logger.info("llm_request id=%s -> ok=%s %.2fs content_len=%d",
|
||||
tc = res.get("tool_calls")
|
||||
logger.info("llm_request id=%s -> ok=%s %.2fs content_len=%d tool_calls=%d",
|
||||
(req_id[:8] if req_id else "?"), res.get("ok"), dt,
|
||||
len(res.get("content") or ""))
|
||||
len(res.get("content") or ""), len(tc) if tc else 0)
|
||||
await _send(ws, "llm_response", {
|
||||
"requestId": req_id,
|
||||
"ok": res.get("ok", False),
|
||||
"content": res.get("content", ""),
|
||||
"tool_calls": tc,
|
||||
"error": res.get("error"),
|
||||
"model": LLM_MODEL,
|
||||
"elapsedMs": int(dt * 1000),
|
||||
|
||||
Reference in New Issue
Block a user