refac
This commit is contained in:
@@ -36,6 +36,7 @@ from open_webui.models.automations import AutomationModel, AutomationRuns, Autom
|
||||
from open_webui.models.chats import ChatForm, Chats
|
||||
from open_webui.models.config import Config
|
||||
from open_webui.models.folders import Folders
|
||||
from open_webui.models.messages import MessageForm
|
||||
from open_webui.models.users import Users
|
||||
from open_webui.utils.auth import create_token
|
||||
from open_webui.utils.misc import parse_duration
|
||||
@@ -300,33 +301,17 @@ def _build_request(
|
||||
return request
|
||||
|
||||
|
||||
def _resolve_model_tool_ids(app, model_id: str) -> list[str]:
|
||||
"""Read model-attached tool_ids from model config.
|
||||
|
||||
The frontend does this in Chat.svelte (model.info.meta.toolIds).
|
||||
The backend never auto-resolves them, so we must do it explicitly.
|
||||
"""
|
||||
models = getattr(app.state, 'MODELS', {})
|
||||
model = models.get(model_id, {})
|
||||
tool_ids = model.get('info', {}).get('meta', {}).get('toolIds', [])
|
||||
return list(tool_ids) if tool_ids else []
|
||||
|
||||
|
||||
async def _resolve_model_features(app, model_id: str) -> dict:
|
||||
"""Read model default features from model config.
|
||||
|
||||
The frontend does this in Chat.svelte (model.info.meta.defaultFeatureIds
|
||||
+ model.info.meta.capabilities). Enables features like web_search,
|
||||
code_interpreter, image_generation when the model has them as defaults
|
||||
AND the capability is enabled AND the admin has enabled the feature.
|
||||
"""
|
||||
async def _resolve_model_defaults(app, model_id: str) -> tuple[list[str], dict, list[str], Optional[str]]:
|
||||
models = getattr(app.state, 'MODELS', {})
|
||||
model = models.get(model_id, {})
|
||||
meta = model.get('info', {}).get('meta', {})
|
||||
|
||||
tool_ids = list(meta.get('toolIds') or [])
|
||||
filter_ids = list(meta.get('defaultFilterIds') or [])
|
||||
terminal_id = meta.get('terminalId') or None
|
||||
default_feature_ids = meta.get('defaultFeatureIds', [])
|
||||
if not default_feature_ids:
|
||||
return {}
|
||||
return tool_ids, {}, filter_ids, terminal_id
|
||||
|
||||
capabilities = meta.get('capabilities') or {}
|
||||
features = {}
|
||||
@@ -344,25 +329,7 @@ async def _resolve_model_features(app, model_id: str) -> dict:
|
||||
if capabilities.get(feature_id) and feature_checks[feature_id]:
|
||||
features[feature_id] = True
|
||||
|
||||
return features
|
||||
|
||||
|
||||
def _resolve_model_filter_ids(app, model_id: str) -> list[str]:
|
||||
"""Read model default filter_ids from model config."""
|
||||
models = getattr(app.state, 'MODELS', {})
|
||||
model = models.get(model_id, {})
|
||||
filter_ids = model.get('info', {}).get('meta', {}).get('defaultFilterIds', [])
|
||||
return list(filter_ids) if filter_ids else []
|
||||
|
||||
|
||||
def _resolve_model_terminal_id(app, model_id: str) -> Optional[str]:
|
||||
"""Read model default terminal_id from model config.
|
||||
|
||||
The frontend does this in Chat.svelte (model.info.meta.terminalId).
|
||||
"""
|
||||
models = getattr(app.state, 'MODELS', {})
|
||||
model = models.get(model_id, {})
|
||||
return model.get('info', {}).get('meta', {}).get('terminalId') or None
|
||||
return tool_ids, features, filter_ids, terminal_id
|
||||
|
||||
|
||||
async def _set_terminal_cwd(app, server_id: str, user, cwd: str, chat_id: str) -> None:
|
||||
@@ -413,10 +380,113 @@ async def _set_terminal_cwd(app, server_id: str, user, cwd: str, chat_id: str) -
|
||||
log.warning(f'Failed to set terminal CWD: {e}')
|
||||
|
||||
|
||||
async def _execute_channel_automation(
|
||||
app,
|
||||
automation: AutomationModel,
|
||||
user,
|
||||
prompt: str,
|
||||
model_id: str,
|
||||
token: str,
|
||||
) -> None:
|
||||
target = automation.data.get('target') or {}
|
||||
channel_id = target.get('channel_id')
|
||||
if not channel_id or not await Config.get('channels.enable'):
|
||||
raise ValueError('Channel not found')
|
||||
|
||||
model = getattr(app.state, 'MODELS', {}).get(model_id, {})
|
||||
request = _build_request(app, token=token)
|
||||
|
||||
from open_webui.routers.channels import new_message_handler
|
||||
|
||||
async with get_async_db() as db:
|
||||
user_message, channel = await new_message_handler(
|
||||
request,
|
||||
channel_id,
|
||||
MessageForm(
|
||||
content=prompt,
|
||||
data={},
|
||||
meta={'automation_id': automation.id},
|
||||
),
|
||||
user,
|
||||
db,
|
||||
)
|
||||
response_parent_id = (
|
||||
user_message.parent_id
|
||||
if user_message.parent_id
|
||||
else (user_message.id if await Config.get('channels.model_response_mode', 'thread') == 'thread' else None)
|
||||
)
|
||||
assistant_message, channel = await new_message_handler(
|
||||
request,
|
||||
channel.id,
|
||||
MessageForm(
|
||||
parent_id=response_parent_id,
|
||||
content='',
|
||||
data={},
|
||||
meta={
|
||||
'automation_id': automation.id,
|
||||
'model_id': model_id,
|
||||
'model_name': model.get('name', model_id),
|
||||
},
|
||||
),
|
||||
user,
|
||||
db,
|
||||
)
|
||||
|
||||
tool_ids, features, filter_ids, _ = await _resolve_model_defaults(app, model_id)
|
||||
|
||||
form_data = {
|
||||
'model': model_id,
|
||||
'messages': [
|
||||
{
|
||||
'role': 'system',
|
||||
'content': f'You are {model.get("name", model_id)}, participating in a channel conversation. Be concise and conversational.',
|
||||
},
|
||||
{'role': 'user', 'content': f'{user.name if user else "User"}: {prompt}'},
|
||||
],
|
||||
'stream': True,
|
||||
'chat_id': f'channel:{channel.id}',
|
||||
'id': assistant_message.id,
|
||||
'session_id': f'channel:{channel.id}',
|
||||
'automation_id': automation.id,
|
||||
'background_tasks': {},
|
||||
}
|
||||
if tool_ids:
|
||||
form_data['tool_ids'] = tool_ids
|
||||
if features:
|
||||
form_data['features'] = features
|
||||
if filter_ids:
|
||||
form_data['filter_ids'] = filter_ids
|
||||
|
||||
await app.state.CHAT_COMPLETION_HANDLER(request, form_data, user=user)
|
||||
|
||||
from open_webui.socket.main import sio
|
||||
|
||||
await sio.emit(
|
||||
'automation:result',
|
||||
{
|
||||
'automation_id': automation.id,
|
||||
'name': automation.name,
|
||||
'chat_id': f'channel:{channel.id}',
|
||||
'message_id': assistant_message.id,
|
||||
'status': 'success',
|
||||
},
|
||||
room=f'user:{automation.user_id}',
|
||||
)
|
||||
|
||||
await _record_run(automation.id, 'success', chat_id=f'channel:{channel.id}')
|
||||
await publish_event(
|
||||
app,
|
||||
EVENTS.AUTOMATION_RUN_COMPLETED,
|
||||
actor=user,
|
||||
subject_id=automation.id,
|
||||
data={'name': automation.name, 'channel_id': channel.id, 'message_id': assistant_message.id},
|
||||
)
|
||||
|
||||
|
||||
async def execute_automation(app, automation: AutomationModel) -> None:
|
||||
"""Execute an automation through the full chat completion pipeline.
|
||||
|
||||
Creates a real chat, then calls chat_completion exactly like the frontend:
|
||||
Creates a real chat or channel message, then calls chat_completion exactly like the frontend:
|
||||
session_id + chat_id + message_id → async task → pipeline handles everything
|
||||
(filters, model params, knowledge/RAG, tools, DB saves, webhooks).
|
||||
"""
|
||||
@@ -452,6 +522,20 @@ async def execute_automation(app, automation: AutomationModel) -> None:
|
||||
|
||||
prompt = await prompt_template(automation.data['prompt'], user)
|
||||
model_id = automation.data['model_id']
|
||||
try:
|
||||
expires_delta = parse_duration(str(await Config.get('automations.auth_token_expires_in', '1h')))
|
||||
except ValueError:
|
||||
expires_delta = None
|
||||
token = create_token(
|
||||
data={'id': user.id, 'typ': 'automation'},
|
||||
expires_delta=expires_delta or timedelta(hours=1),
|
||||
)
|
||||
|
||||
target = automation.data.get('target') or {}
|
||||
if target.get('type') == 'channel':
|
||||
await _execute_channel_automation(app, automation, user, prompt, model_id, token)
|
||||
return
|
||||
|
||||
folder_id = automation.folder_id
|
||||
if folder_id and not await Folders.get_folder_by_id_and_user_id(folder_id, automation.user_id):
|
||||
await Automations.clear_folder_ids(automation.user_id, [folder_id])
|
||||
@@ -528,12 +612,7 @@ async def execute_automation(app, automation: AutomationModel) -> None:
|
||||
)
|
||||
|
||||
# Resolve model defaults (frontend does this, backend doesn't)
|
||||
tool_ids = _resolve_model_tool_ids(app, model_id)
|
||||
features = await _resolve_model_features(app, model_id)
|
||||
filter_ids = _resolve_model_filter_ids(app, model_id)
|
||||
|
||||
# Resolve terminal from model config
|
||||
terminal_id = _resolve_model_terminal_id(app, model_id)
|
||||
tool_ids, features, filter_ids, terminal_id = await _resolve_model_defaults(app, model_id)
|
||||
|
||||
# Build the same payload the frontend sends to /api/chat/completions
|
||||
form_data = {
|
||||
@@ -564,14 +643,6 @@ async def execute_automation(app, automation: AutomationModel) -> None:
|
||||
|
||||
# Call the full chat completion pipeline (same as POST /api/chat/completions).
|
||||
# The handler reference is stored on app.state to avoid circular imports.
|
||||
try:
|
||||
expires_delta = parse_duration(str(await Config.get('automations.auth_token_expires_in', '1h')))
|
||||
except ValueError:
|
||||
expires_delta = None
|
||||
token = create_token(
|
||||
data={'id': user.id, 'typ': 'automation'},
|
||||
expires_delta=expires_delta or timedelta(hours=1),
|
||||
)
|
||||
request = _build_request(app, token=token)
|
||||
await app.state.CHAT_COMPLETION_HANDLER(request, form_data, user=user)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user