From f1320b99d33b2381289cffcd1f62c83265536f01 Mon Sep 17 00:00:00 2001 From: Tyler <68524461+TySP-Dev@users.noreply.github.com> Date: Fri, 15 May 2026 14:24:09 -0400 Subject: [PATCH] Making project Ollama focused --- ai_answers.py | 138 +++----------------------------------------------- 1 file changed, 7 insertions(+), 131 deletions(-) diff --git a/ai_answers.py b/ai_answers.py index 7a5357d..ebd2e75 100644 --- a/ai_answers.py +++ b/ai_answers.py @@ -46,14 +46,7 @@ PLUGIN_NAME = "AI Answers" DEFAULT_TABS = "general,science,it,news" PROVIDER_PRESETS = { - 'openai': {'url': 'https://api.openai.com/v1/chat/completions', 'model': 'gpt-4o-mini'}, - 'openrouter': {'url': 'https://openrouter.ai/api/v1/chat/completions', 'model': 'google/gemma-3-27b-it:free'}, 'ollama': {'url': 'http://localhost:11434/v1/chat/completions', 'model': 'llama3.2'}, - 'localai': {'url': 'http://localhost:8080/v1/chat/completions', 'model': 'gpt-4'}, - 'lmstudio': {'url': 'http://localhost:1234/v1/chat/completions', 'model': 'local-model'}, - 'gemini': {'url': 'https://generativelanguage.googleapis.com/v1beta/models/{model}:streamGenerateContent', 'model': 'gemma-3-27b-it'}, - 'azure': {'url': None, 'model': 'azure-deployment'}, - 'huggingface': {'url': 'https://api-inference.huggingface.co/models/{model}/v1/chat/completions', 'model': 'meta-llama/Meta-Llama-3-8B-Instruct'} } # UI assets @@ -793,37 +786,25 @@ class SXNGPlugin(Plugin): raw_url = os.getenv('LLM_URL', '').strip() if not raw_provider and raw_url: url_lower = raw_url.lower() - if 'openai.com' in url_lower: - raw_provider = 'openai' - elif 'openrouter.ai' in url_lower: - raw_provider = 'openrouter' - elif ':11434' in url_lower: + if ':11434' in url_lower: raw_provider = 'ollama' - elif 'generativelanguage.googleapis.com' in url_lower: - raw_provider = 'gemini' - elif 'openai.azure.com' in url_lower or '.azure.com' in url_lower: - raw_provider = 'azure' - elif 'huggingface.co' in url_lower: - raw_provider = 'huggingface' else: - raw_provider = 'openai' - logger.info(f"{PLUGIN_NAME}: Using OpenAI-compatible mode for custom URL") + raw_provider = 'error' + logger.info(f"{raw_provider}: Ollama not detected") if not raw_provider: self.provider = '' self.model = '' - self.is_gemini = False self.api_key = '' return if raw_provider not in PROVIDER_PRESETS: - logger.warning(f"{PLUGIN_NAME}: Unknown provider '{raw_provider}', falling back to 'openai'") - self.provider = raw_provider if raw_provider in PROVIDER_PRESETS else 'openai' - self.is_gemini = (self.provider == 'gemini') + logger.warning(f"{PLUGIN_NAME}: Not Ollama '{raw_provider}', please correct.") + self.provider = raw_provider if raw_provider in PROVIDER_PRESETS else 'Error' preset = PROVIDER_PRESETS[self.provider] self.api_key = os.getenv('LLM_KEY', '') - if not self.api_key and self.provider in ('ollama', 'localai', 'lmstudio'): + if not self.api_key and self.provider == 'ollama': self.api_key = 'none' self.api_key = self.api_key.strip() @@ -1121,112 +1102,7 @@ class SXNGPlugin(Plugin): {numbered_instructions} """ - - def call_gemini(): - base = self.endpoint_url.replace('streamGenerateContent', 'generateContent') - url = f"{base}&key={self.api_key}" if '?' in base else f"{base}?key={self.api_key}" - conn = None - try: - conn, path = _get_streaming_connection(url) - payload = json.dumps({ - "contents": [{"parts": [{"text": prompt}]}], - "generationConfig": {"maxOutputTokens": min(self.max_tokens * 4, 8192), "temperature": self.temperature} - }) - conn.request("POST", path, body=payload.encode('utf-8'), headers={"Content-Type": "application/json"}) - res = conn.getresponse() - if res.status != 200: - body = res.read(2048).decode('utf-8', errors='replace')[:500] - logger.error(f"{PLUGIN_NAME}: Gemini API {res.status}: {body}") - return '', f"API error {res.status}. Check server logs." - obj = json.loads(res.read().decode('utf-8', errors='replace')) - if obj.get('promptFeedback', {}).get('blockReason'): - return '', f"Gemini blocked prompt: {obj['promptFeedback']['blockReason']}" - candidates = obj.get('candidates', []) - if not candidates: - return '', "No candidates in Gemini response." - first = candidates[0] - if first.get('finishReason') == 'SAFETY': - return '', "Gemini stopped generation due to safety filters." - parts = first.get('content', {}).get('parts', []) - text = ''.join(p.get('text', '') for p in parts if isinstance(p, dict)) - return text, None - except Exception as e: - logger.error(f"{PLUGIN_NAME}: Gemini call error: {e}", exc_info=True) - return '', f"Connection Error: {e}" - finally: - if conn: conn.close() - - def call_openai_compatible(): - conn = None - try: - conn, path = _get_streaming_connection(self.endpoint_url) - payload_dict = { - "model": effective_model, - "messages": [ - {"role": "system", "content": SYSTEM}, - {"role": "user", "content": prompt}, - {"role": "assistant", "content": ""}, - ], - "stream": False, - "max_tokens": self.max_tokens, - "temperature": self.temperature - } - payload = json.dumps(payload_dict) - headers = { - "Content-Type": "application/json", - "HTTP-Referer": "https://github.com/searxng/searxng", - "X-Title": "SearXNG" - } - if self.provider == 'azure': - headers['api-key'] = self.api_key - else: - headers['Authorization'] = f"Bearer {self.api_key}" - conn.request("POST", path, body=payload.encode('utf-8'), headers=headers) - res = conn.getresponse() - if res.status != 200: - body = res.read(2048).decode('utf-8', errors='replace')[:500] - logger.error(f"{PLUGIN_NAME}: {self.provider} API {res.status}: {body}") - return '', f"API error {res.status}. Check server logs." - obj = json.loads(res.read().decode('utf-8', errors='replace')) - if "error" in obj: - err = obj["error"] - msg = err.get("message", str(err)) if isinstance(err, dict) else str(err) - return '', f"API Error: {msg}" - choices = obj.get("choices", []) - if not choices: - return '', "No choices in API response." - message = choices[0].get("message", {}) - content = re.sub(r'.*?', '', message.get("content") or "", flags=re.DOTALL).strip() - reasoning = message.get("reasoning") or message.get("reasoning_content") or "" - if not content and reasoning: - logger.warning(f"{PLUGIN_NAME}: {self.provider} returned empty content; extracting answer from reasoning field") - header_pat = re.compile(r'^\s*\*?\*?[A-Z][^:]{0,40}:\*?\*?\s*$', re.MULTILINE) - matches = list(header_pat.finditer(reasoning)) - if matches: - answer = reasoning[matches[-1].end():].strip() - else: - paras = [p.strip() for p in re.split(r'\n{2,}', reasoning) if p.strip()] - answer = paras[-1] if paras else reasoning.strip() - full = answer - else: - full = (f"\n{reasoning}\n\n\n" if reasoning else "") + content - full = re.sub(r'.*?', '', full, flags=re.DOTALL).strip() - return full, None - except Exception as e: - logger.error(f"{PLUGIN_NAME}: {self.provider} call error: {e}", exc_info=True) - return '', f"Connection Error: {e}" - finally: - if conn: conn.close() - - call_fn = call_gemini if self.is_gemini else call_openai_compatible - text, error = call_fn() - - if self.provider == 'ollama' and getattr(self, 'ollama_unload_after', False): - self._ollama_unload_model() - - return jsonify({"text": text, "error": error}) - return True - + def _assemble_context(self, clean_results, infoboxes, answers, offset=0) -> tuple[str, list]: """Builds context string from normalized search data. Returns (context_str, urls).""" context_parts = []