Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions plugins/iterative-research/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
75 changes: 55 additions & 20 deletions plugins/iterative-research/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -49,6 +50,7 @@ async def _emit_citation(
)



import json
import logging
import re
Expand Down Expand Up @@ -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"
Expand All @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down
56 changes: 40 additions & 16 deletions plugins/iterative-research/src/_llm.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import json
import logging
import re
Expand Down Expand Up @@ -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"
Expand All @@ -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,
Expand Down
22 changes: 17 additions & 5 deletions plugins/iterative-research/src/_research.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from __future__ import annotations

import asyncio
import inspect
import logging
import re
from typing import Any, AsyncGenerator, Awaitable, Callable, Optional
Expand Down Expand Up @@ -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)

Expand Down
1 change: 1 addition & 0 deletions plugins/iterative-research/src/meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading