From 9f4d9f23f2e8c40ec59856373d750c6b2fa52854 Mon Sep 17 00:00:00 2001 From: Philipp Rich Date: Sun, 16 Aug 2026 09:54:15 +0400 Subject: [PATCH] feat(iterative-research): achieve Open WebUI v11 compatibility --- plugins/iterative-research/CHANGELOG.md | 3 + plugins/iterative-research/plugin.py | 75 +++++++++++++++------ plugins/iterative-research/src/_llm.py | 56 ++++++++++----- plugins/iterative-research/src/_research.py | 22 ++++-- plugins/iterative-research/src/meta.py | 1 + 5 files changed, 116 insertions(+), 41 deletions(-) diff --git a/plugins/iterative-research/CHANGELOG.md b/plugins/iterative-research/CHANGELOG.md index efb0cdd..cf107f0 100644 --- a/plugins/iterative-research/CHANGELOG.md +++ b/plugins/iterative-research/CHANGELOG.md @@ -8,8 +8,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- Explicit `requirements` metadata field for automated dependency provisioning in isolated environments (v10+). ### Fixed +- **Open WebUI v11 Compatibility**: Refactored internal imports (`Models`, `generate_chat_completion`) to support both legacy and modern `open_webui` namespaces. +- **Resilience**: Implemented dynamic sync/async resolution for `Users` model calls to ensure stability across database driver upgrades. ## [0.9.2] - 2026-08-15 diff --git a/plugins/iterative-research/plugin.py b/plugins/iterative-research/plugin.py index a72e479..5a5c05d 100644 --- a/plugins/iterative-research/plugin.py +++ b/plugins/iterative-research/plugin.py @@ -3,6 +3,7 @@ author: toolfab-ai author_url: https://github.com/toolfab-ai/ version: 0.9.2 +requirements: httpx, beautifulsoup4 description: Autonomous multi-turn web search & scraping loops to synthesize detailed cited research-reports. license: MIT github: https://github.com/toolfab-ai/owui-plugins @@ -49,6 +50,7 @@ async def _emit_citation( ) + import json import logging import re @@ -86,23 +88,33 @@ async def _get_backend_model(self, body: dict[str, Any]) -> str: return self.valves.MODEL # 2. Check if we can find a non-pipe model in the workspace + # We use noqa: N806 for variable naming since Models is a class name being imported dynamically. + Models = None # noqa: N806 try: from open_webui.models.models import Models + except ImportError: + try: + from open_webui.apps.webui.models.models import Models + except ImportError: + logger.warning("Could not import Models from open_webui") + Models = None # noqa: N806 - all_models = await Models.get_all_models() - for m in all_models: - m_id = getattr(m, "id", None) - if not m_id and isinstance(m, dict): - m_id = m.get("id") - if ( - m_id - and m_id != "iterative_research" - and m_id != "iterative_research_pipe" - and "pipe" not in m_id - ): - return m_id - except Exception as e: - logger.warning("Could not list models from Models: %s", e) + if Models is not None: + try: + all_models = await Models.get_all_models() + for m in all_models: + m_id = getattr(m, "id", None) + if not m_id and isinstance(m, dict): + m_id = m.get("id") + if ( + m_id + and m_id != "iterative_research" + and m_id != "iterative_research_pipe" + and "pipe" not in m_id + ): + return m_id + except Exception as e: + logger.warning("Could not list models from Models: %s", e) # 3. Fallback to some common default model ID return "gpt-4o-mini" @@ -116,7 +128,19 @@ async def _call_llm( model_id: str, ) -> str: """Invoke generate_chat_completion with system and user prompts.""" - from open_webui.utils.chat import generate_chat_completion + generate_chat_completion = None + try: + from open_webui.utils.chat import generate_chat_completion + except ImportError: + try: + from open_webui.apps.webui.utils.chat import generate_chat_completion + except ImportError: + logger.warning("Could not import generate_chat_completion from open_webui") + generate_chat_completion = None + + if generate_chat_completion is None: + logger.warning("generate_chat_completion is None, skipping LLM call") + return "Warning: Chat completion engine not available." payload = { "model": model_id, @@ -175,7 +199,9 @@ def _parse_json_completions(self, text: str) -> dict[str, Any]: return {"gaps": ["Manual parsing fallback"], "queries": queries[:3]} + import asyncio +import inspect import logging import re from typing import Any, AsyncGenerator, Awaitable, Callable, Optional @@ -242,15 +268,24 @@ async def _stream_research( user_id = __user__.get("id") if __user__ else None user_obj = None if user_id: + # We use noqa: N806 for variable naming since Users is a class name being imported dynamically. + Users = None # noqa: N806 try: from open_webui.models.users import Users - - user_obj = await Users.get_user_by_id(user_id) - except Exception: + except ImportError: try: - from open_webui.models.users import Users + from open_webui.apps.webui.models.users import Users + except ImportError: + logger.warning("Could not import Users from open_webui") + Users = None # noqa: N806 - user_obj = Users.get_user_by_id(user_id) + if Users is not None: + try: + res = Users.get_user_by_id(user_id) + if inspect.isawaitable(res) or asyncio.iscoroutine(res): + user_obj = await res + else: + user_obj = res except Exception as e: logger.warning("Could not load user object: %s", e) diff --git a/plugins/iterative-research/src/_llm.py b/plugins/iterative-research/src/_llm.py index 4e791ee..d711dc8 100644 --- a/plugins/iterative-research/src/_llm.py +++ b/plugins/iterative-research/src/_llm.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import json import logging import re @@ -35,23 +37,33 @@ async def _get_backend_model(self, body: dict[str, Any]) -> str: return self.valves.MODEL # 2. Check if we can find a non-pipe model in the workspace + # We use noqa: N806 for variable naming since Models is a class name being imported dynamically. + Models = None # noqa: N806 try: from open_webui.models.models import Models - - all_models = await Models.get_all_models() - for m in all_models: - m_id = getattr(m, "id", None) - if not m_id and isinstance(m, dict): - m_id = m.get("id") - if ( - m_id - and m_id != "iterative_research" - and m_id != "iterative_research_pipe" - and "pipe" not in m_id - ): - return m_id - except Exception as e: - logger.warning("Could not list models from Models: %s", e) + except ImportError: + try: + from open_webui.apps.webui.models.models import Models + except ImportError: + logger.warning("Could not import Models from open_webui") + Models = None # noqa: N806 + + if Models is not None: + try: + all_models = await Models.get_all_models() + for m in all_models: + m_id = getattr(m, "id", None) + if not m_id and isinstance(m, dict): + m_id = m.get("id") + if ( + m_id + and m_id != "iterative_research" + and m_id != "iterative_research_pipe" + and "pipe" not in m_id + ): + return m_id + except Exception as e: + logger.warning("Could not list models from Models: %s", e) # 3. Fallback to some common default model ID return "gpt-4o-mini" @@ -65,7 +77,19 @@ async def _call_llm( model_id: str, ) -> str: """Invoke generate_chat_completion with system and user prompts.""" - from open_webui.utils.chat import generate_chat_completion + generate_chat_completion = None + try: + from open_webui.utils.chat import generate_chat_completion + except ImportError: + try: + from open_webui.apps.webui.utils.chat import generate_chat_completion + except ImportError: + logger.warning("Could not import generate_chat_completion from open_webui") + generate_chat_completion = None + + if generate_chat_completion is None: + logger.warning("generate_chat_completion is None, skipping LLM call") + return "Warning: Chat completion engine not available." payload = { "model": model_id, diff --git a/plugins/iterative-research/src/_research.py b/plugins/iterative-research/src/_research.py index 658fedb..90c5bf8 100644 --- a/plugins/iterative-research/src/_research.py +++ b/plugins/iterative-research/src/_research.py @@ -1,4 +1,7 @@ +from __future__ import annotations + import asyncio +import inspect import logging import re from typing import Any, AsyncGenerator, Awaitable, Callable, Optional @@ -65,15 +68,24 @@ async def _stream_research( user_id = __user__.get("id") if __user__ else None user_obj = None if user_id: + # We use noqa: N806 for variable naming since Users is a class name being imported dynamically. + Users = None # noqa: N806 try: from open_webui.models.users import Users - - user_obj = await Users.get_user_by_id(user_id) - except Exception: + except ImportError: try: - from open_webui.models.users import Users + from open_webui.apps.webui.models.users import Users + except ImportError: + logger.warning("Could not import Users from open_webui") + Users = None # noqa: N806 - user_obj = Users.get_user_by_id(user_id) + if Users is not None: + try: + res = Users.get_user_by_id(user_id) + if inspect.isawaitable(res) or asyncio.iscoroutine(res): + user_obj = await res + else: + user_obj = res except Exception as e: logger.warning("Could not load user object: %s", e) diff --git a/plugins/iterative-research/src/meta.py b/plugins/iterative-research/src/meta.py index f361cbe..a69b68b 100644 --- a/plugins/iterative-research/src/meta.py +++ b/plugins/iterative-research/src/meta.py @@ -3,6 +3,7 @@ author: toolfab-ai author_url: https://github.com/toolfab-ai/ version: 0.9.2 +requirements: httpx, beautifulsoup4 description: Autonomous multi-turn web search & scraping loops to synthesize detailed cited research-reports. license: MIT github: https://github.com/toolfab-ai/owui-plugins