"""Cognee runtime configuration for the local Cerebras/Gemma setup.""" from __future__ import annotations import json import os import importlib from typing import Any, Callable from app.agents.cerebras_client import CerebrasClient COGNEE_LLM_MODEL = "openai/gemma-4-31b" COGNEE_CEREBRAS_BASE_URL = "https://api.cerebras.ai/v1" COGNEE_INSTRUCTOR_MODE = "json_schema_mode" COGNEE_SKIP_CONNECTION_TEST = "true" COGNEE_OPENAI_ADAPTER_MODULE = ( "cognee.infrastructure.llm.structured_output_framework.litellm_instructor.llm.openai.adapter" ) def _cognee_llm_args() -> dict[str, Any]: """LiteLLM args shared by all Cognee structured-output calls.""" return { "temperature": 0, "top_p": 1, "seed": 0, } def configure_cognee_llm( cognee_config: Any, *, clear_llm_client_cache: Callable[[], None] | None = None, ) -> dict[str, Any]: """Force Cognee's LiteLLM/instructor path onto Cerebras strict JSON mode. StudyBuddy's own ``CerebrasClient`` already uses provider-native ``response_format={"type": "json_schema", ... "strict": true}``. Cognee reaches Cerebras through LiteLLM + instructor instead, so we configure both the Cognee config object and the environment it reads from before any Cognee client can be cached. """ cerebras_key = os.environ.get("CEREBRAS_API_KEY", "") llm_args = _cognee_llm_args() config = { "structured_output_framework": "instructor", "llm_provider": "openai", "llm_model": COGNEE_LLM_MODEL, "llm_endpoint": COGNEE_CEREBRAS_BASE_URL, "llm_api_key": cerebras_key, "llm_instructor_mode": COGNEE_INSTRUCTOR_MODE, "llm_temperature": 0.0, "llm_max_completion_tokens": 16384, "llm_args": llm_args, } os.environ["OPENAI_API_KEY"] = cerebras_key os.environ["OPENAI_API_BASE"] = COGNEE_CEREBRAS_BASE_URL os.environ["LLM_API_KEY"] = cerebras_key os.environ["LLM_API_BASE"] = COGNEE_CEREBRAS_BASE_URL os.environ["LLM_ENDPOINT"] = COGNEE_CEREBRAS_BASE_URL os.environ["LLM_MODEL"] = COGNEE_LLM_MODEL os.environ["LLM_PROVIDER"] = "openai" os.environ["LLM_INSTRUCTOR_MODE"] = COGNEE_INSTRUCTOR_MODE os.environ["LLM_TEMPERATURE"] = "0" os.environ["LLM_ARGS"] = json.dumps(llm_args) os.environ["COGNEE_SKIP_CONNECTION_TEST"] = COGNEE_SKIP_CONNECTION_TEST cognee_config.set_llm_config(config) patch_cognee_cerebras_structured_output() if clear_llm_client_cache is not None: clear_llm_client_cache() return config def patch_cognee_cerebras_structured_output() -> bool: """Route Cognee+Cerebras structured outputs through native strict JSON schema. Cognee's default OpenAI adapter uses LiteLLM + instructor. With Cerebras/Gemma this can produce fenced or schema-shaped JSON that fails Cognee's Pydantic models during memify/cognify. ResearchMate's own Cerebras client already uses provider-native strict JSON schema; this patch makes Cognee use the same path for BaseModel structured outputs while preserving the original adapter for plain string calls and non-Cerebras endpoints. """ module = importlib.import_module(COGNEE_OPENAI_ADAPTER_MODULE) adapter_cls = getattr(module, "OpenAIAdapter") if getattr(adapter_cls, "_researchmate_cerebras_patch", False): return True original = getattr(adapter_cls, "acreate_structured_output", None) async def _native_cerebras_structured_output( self: Any, text_input: str, system_prompt: str, response_model: type[Any], **kwargs: Any, ) -> Any: if response_model is str: if hasattr(self, "acreate_str_output"): return await self.acreate_str_output(text_input, system_prompt, **kwargs) if original is not None: return await original(self, text_input, system_prompt, response_model, **kwargs) model = str(getattr(self, "model", "") or COGNEE_LLM_MODEL) endpoint = str(getattr(self, "endpoint", "") or "") is_cerebras = COGNEE_CEREBRAS_BASE_URL in endpoint or "gemma" in model.lower() if not is_cerebras and original is not None: return await original(self, text_input, system_prompt, response_model, **kwargs) merged_kwargs = {**(getattr(self, "llm_args", {}) or {}), **kwargs} max_completion_tokens = getattr(self, "max_completion_tokens", None) if max_completion_tokens and "max_completion_tokens" not in merged_kwargs: merged_kwargs["max_completion_tokens"] = max_completion_tokens native_model = model.removeprefix("openai/") messages = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": text_input}, ] api_key = getattr(self, "api_key", None) or None try: client = CerebrasClient(api_key=api_key) except TypeError: client = CerebrasClient() return await _run_sync_structured_complete( client=client, messages=messages, response_model=response_model, model=native_model, kwargs=merged_kwargs, ) adapter_cls.acreate_structured_output = _native_cerebras_structured_output adapter_cls._researchmate_cerebras_patch = True return True async def _run_sync_structured_complete( *, client: CerebrasClient, messages: list[dict[str, Any]], response_model: type[Any], model: str, kwargs: dict[str, Any], ) -> Any: import asyncio return await asyncio.to_thread( lambda: client.structured_complete(messages, response_model, model=model, **kwargs) )