feat(B-LLM): Zentraler LLM Client — llm_complete() + llm_embed() + Migration + Tests + Doku
Check Cross-Plugin Imports / check (push) Has been cancelled

B-LLM: llm_client.py um generische llm_complete() und llm_embed() erweitert
- Provider-Auswahl, API-Key-Auflösung, Error-Handling, Cost-Tracking
- Retry mit Exponential-Backoff für transient errors
- Timeout konfigurierbar
- Helper: get_api_credentials(), build_model(), _classify_error()

B-LLM-MIG: Alle 8 direkten litellm.acompletion() Calls auf llm_complete() umgestellt
- agent_runner.py, query_understanding.py (2x), ai_proactive (3x), ai_assistant (2x)
- 0 verbleibende direkte litellm.acompletion() Calls außerhalb llm_client.py

B-LLM-TEST: 39 Tests in test_llm_client.py — alle grün
- Mock mode, error handling, embed, helpers, backward compat

B-LLM-DOC: Plugin-Dev-Guide Kapitel 7 (LLM Integration) hinzugefügt
This commit is contained in:
Agent Zero
2026-08-13 16:22:05 +02:00
parent 3d8210637e
commit e3ca3b3d28
12 changed files with 1230 additions and 234 deletions
+14 -25
View File
@@ -17,6 +17,7 @@ from typing import Any
from sqlalchemy import func, select
from app.core.db import get_session_factory
from app.ai.llm_client import llm_complete
logger = logging.getLogger(__name__)
@@ -154,48 +155,36 @@ async def run_agent(
system_prompt = agent.system_prompt or "You are a helpful AI assistant."
user_prompt = f"Context: {context_data}"
# Use litellm directly for more control
import litellm
litellm_model = agent.model or "gpt-4o"
if agent.provider and agent.provider != "openai":
litellm_model = f"{agent.provider}/{litellm_model}"
kwargs: dict[str, Any] = {
"model": litellm_model,
"messages": [
result = await llm_complete(
model=litellm_model,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt},
],
"temperature": 0.3,
"max_tokens": agent.max_tokens or 1000,
}
if agent.api_key:
kwargs["api_key"] = agent.api_key
if agent.api_base:
kwargs["api_base"] = agent.api_base
response = await litellm.acompletion(**kwargs)
content = response.choices[0].message.content
temperature=0.3,
max_tokens=agent.max_tokens or 1000,
api_key=agent.api_key or None,
api_base=agent.api_base or None,
)
content = result["content"]
# Track cost
if hasattr(response, "usage") and response.usage:
usage = response.usage
# Estimate cost (simplified)
input_cost = (usage.prompt_tokens or 0) * 0.00001 / 1000
output_cost = (usage.completion_tokens or 0) * 0.00003 / 1000
result_data["cost_usd"] = round(input_cost + output_cost, 6)
result_data["cost_usd"] = result["cost_usd"]
result_data["llm_response"] = content
# Execute tool calls if LLM returned function calls
if hasattr(response.choices[0].message, "tool_calls") and response.choices[0].message.tool_calls:
raw_response = result["raw_response"]
if hasattr(raw_response.choices[0].message, "tool_calls") and raw_response.choices[0].message.tool_calls:
from app.plugins.builtins.ai_assistant.contracts import get_tool_registry
registry = get_tool_registry()
tool_call_count: dict[str, int] = {}
for tc in response.choices[0].message.tool_calls:
for tc in raw_response.choices[0].message.tool_calls:
tool_name = tc.function.name
# ── Safety Check 4: Infinite Loop Detection ──
tool_call_count[tool_name] = tool_call_count.get(tool_name, 0) + 1