diff --git a/app/plugins/builtins/ai_proactive/jobs.py b/app/plugins/builtins/ai_proactive/jobs.py index efa3682..066120a 100644 --- a/app/plugins/builtins/ai_proactive/jobs.py +++ b/app/plugins/builtins/ai_proactive/jobs.py @@ -187,23 +187,35 @@ async def deep_analysis( # Generate extended suggestion with deep analysis prompt model = settings.model or "ollama/deepseek-v4" + + # Get API key from DB (like ai_assistant does) or fall back to env + from app.plugins.builtins.ai_proactive.services import _get_llm_api_key + api_key, api_base = await _get_llm_api_key(db, tid) + if not api_key: + api_key = os.environ.get('API_KEY_OLLAMA_CLOUD', '') or None + + litellm_kwargs: dict[str, Any] = dict( + model=model, + messages=[ + {"role": "system", "content": DEEP_SYSTEM_PROMPT}, + { + "role": "user", + "content": json.dumps( + extended_context, default=str, ensure_ascii=False + ), + }, + ], + temperature=0.3, + max_tokens=800, + response_format={"type": "json_object"}, + ) + if api_key: + litellm_kwargs["api_key"] = api_key + if api_base: + litellm_kwargs["api_base"] = api_base + try: - response = await litellm.acompletion( - model=model, - messages=[ - {"role": "system", "content": DEEP_SYSTEM_PROMPT}, - { - "role": "user", - "content": json.dumps( - extended_context, default=str, ensure_ascii=False - ), - }, - ], - temperature=0.3, - max_tokens=800, - response_format={"type": "json_object"}, - api_key=OLLAMA_API_KEY, - ) + response = await litellm.acompletion(**litellm_kwargs) content = response.choices[0].message.content if not content: return diff --git a/app/plugins/builtins/ai_proactive/services.py b/app/plugins/builtins/ai_proactive/services.py index 94e74dd..8328820 100644 --- a/app/plugins/builtins/ai_proactive/services.py +++ b/app/plugins/builtins/ai_proactive/services.py @@ -33,7 +33,22 @@ logger = logging.getLogger(__name__) litellm.suppress_debug_info = True -OLLAMA_API_KEY = os.environ.get('API_KEY_OLLAMA_CLOUD', '') +async def _get_llm_api_key(db: AsyncSession, tenant_id: uuid.UUID) -> tuple[str | None, str | None]: + """Get API key and base_url from the default AI provider in the DB. + + Falls back to API_KEY_OLLAMA_CLOUD env var if no provider found. + Returns (api_key, base_url). + """ + try: + from app.plugins.builtins.ai_assistant.services import get_default_provider + provider = await get_default_provider(db, tenant_id) + if provider and provider.api_key: + return provider.api_key, provider.base_url + except Exception: + logger.debug("Failed to get provider from DB, falling back to env") + # Fallback to env var + env_key = os.environ.get('API_KEY_OLLAMA_CLOUD', '') + return (env_key if env_key else None), None # ─── SSE Push Infrastructure ─── @@ -372,7 +387,8 @@ Antworte mit JSON: async def generate_suggestion( - context_data: dict[str, Any], settings: ProactiveSettings + context_data: dict[str, Any], settings: ProactiveSettings, + db: AsyncSession | None = None, tenant_id: uuid.UUID | None = None, ) -> dict[str, Any] | None: """LLM generates a suggestion from context data. @@ -380,21 +396,35 @@ async def generate_suggestion( or None on failure. """ model = settings.model or "ollama/deepseek-v4" + + # Get API key from DB (like ai_assistant does) or fall back to env + api_key = None + api_base = None + if db and tenant_id: + api_key, api_base = await _get_llm_api_key(db, tenant_id) + if not api_key: + api_key = os.environ.get('API_KEY_OLLAMA_CLOUD', '') or None + + litellm_kwargs: dict[str, Any] = dict( + model=model, + messages=[ + {"role": "system", "content": SYSTEM_PROMPT}, + { + "role": "user", + "content": json.dumps(context_data, default=str, ensure_ascii=False), + }, + ], + temperature=0.3, + max_tokens=500, + response_format={"type": "json_object"}, + ) + if api_key: + litellm_kwargs["api_key"] = api_key + if api_base: + litellm_kwargs["api_base"] = api_base + try: - response = await litellm.acompletion( - model=model, - messages=[ - {"role": "system", "content": SYSTEM_PROMPT}, - { - "role": "user", - "content": json.dumps(context_data, default=str, ensure_ascii=False), - }, - ], - temperature=0.3, - max_tokens=500, - response_format={"type": "json_object"}, - api_key=OLLAMA_API_KEY, - ) + response = await litellm.acompletion(**litellm_kwargs) content = response.choices[0].message.content if not content: return None @@ -481,7 +511,7 @@ async def handle_context_change(payload: dict[str, Any]) -> None: context_data = await gather_context(db, entity_type, entity_id, tenant_id) # Generate suggestion - suggestion_data = await generate_suggestion(context_data, settings) + suggestion_data = await generate_suggestion(context_data, settings, db, tenant_id) if suggestion_data is None: logger.debug("handle_context_change: no suggestion generated") return