feat(B-LLM): Zentraler LLM Client — llm_complete() + llm_embed() + Migration + Tests + Doku
Check Cross-Plugin Imports / check (push) Has been cancelled
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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user