refac
This commit is contained in:
@@ -498,6 +498,114 @@ async def edit_image(
|
||||
return JSONCodec.dumps({'error': str(e)})
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# USER INPUT TOOLS
|
||||
# =============================================================================
|
||||
|
||||
|
||||
async def ask_user(
|
||||
questions: list[dict],
|
||||
allow_other: bool = True,
|
||||
__event_call__: callable = None,
|
||||
) -> str:
|
||||
"""
|
||||
Ask the user clarifying questions before continuing.
|
||||
Use this when the next step depends on user intent, preference, or a tradeoff that cannot be inferred safely.
|
||||
|
||||
:param questions: 1-3 question objects, each with id, header, question, and 2-3 options. Each option needs label and description.
|
||||
:param allow_other: Whether users may enter a free-form answer instead of choosing one of the options
|
||||
:return: JSON with status and answers keyed by question id
|
||||
"""
|
||||
try:
|
||||
if not isinstance(questions, list) or not 1 <= len(questions) <= 3:
|
||||
raise ValueError('ask_user requires 1-3 questions.')
|
||||
|
||||
normalized_questions = []
|
||||
seen_ids = set()
|
||||
for index, question in enumerate(questions):
|
||||
if not isinstance(question, dict):
|
||||
raise ValueError('Each question must be an object.')
|
||||
|
||||
question_id = str(question.get('id') or '').strip()[:64]
|
||||
if not question_id:
|
||||
raise ValueError('Each question requires a non-empty id.')
|
||||
if question_id in seen_ids:
|
||||
raise ValueError(f'Duplicate question id: {question_id}')
|
||||
seen_ids.add(question_id)
|
||||
|
||||
options = question.get('options')
|
||||
if not isinstance(options, list) or not 2 <= len(options) <= 3:
|
||||
raise ValueError('Each question requires 2-3 options.')
|
||||
|
||||
normalized_options = []
|
||||
for option in options:
|
||||
if not isinstance(option, dict):
|
||||
raise ValueError('Each option must be an object.')
|
||||
|
||||
label = str(option.get('label') or '').strip()[:80]
|
||||
description = str(option.get('description') or '').strip()[:240]
|
||||
if not label or not description:
|
||||
raise ValueError('Each option requires a label and description.')
|
||||
|
||||
normalized_options.append(
|
||||
{
|
||||
'label': label,
|
||||
'description': description,
|
||||
}
|
||||
)
|
||||
|
||||
question_text = str(question.get('question') or '').strip()[:500]
|
||||
if not question_text:
|
||||
raise ValueError('Each question requires question text.')
|
||||
|
||||
normalized_questions.append(
|
||||
{
|
||||
'id': question_id,
|
||||
'header': str(question.get('header') or '').strip()[:48] or f'Question {index + 1}',
|
||||
'question': question_text,
|
||||
'options': normalized_options,
|
||||
'allow_other': bool(question.get('allow_other', allow_other)),
|
||||
}
|
||||
)
|
||||
|
||||
if __event_call__ is None:
|
||||
return JSONCodec.dumps(
|
||||
{
|
||||
'status': 'error',
|
||||
'error': 'User input requires an active browser session with WebSocket connection.',
|
||||
},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
output = await __event_call__(
|
||||
{
|
||||
'type': 'request:user_input',
|
||||
'data': {
|
||||
'questions': normalized_questions,
|
||||
'allow_other': allow_other,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
if not isinstance(output, dict):
|
||||
return JSONCodec.dumps({'status': 'error', 'error': 'Invalid user input response.'}, ensure_ascii=False)
|
||||
if output.get('error'):
|
||||
return JSONCodec.dumps({'status': 'error', 'error': output.get('error')}, ensure_ascii=False)
|
||||
if output.get('status') == 'cancelled':
|
||||
return JSONCodec.dumps({'status': 'cancelled', 'answers': {}}, ensure_ascii=False)
|
||||
|
||||
return JSONCodec.dumps(
|
||||
{
|
||||
'status': 'answered',
|
||||
'answers': output.get('answers', {}),
|
||||
},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
except Exception as e:
|
||||
log.exception(f'ask_user error: {e}')
|
||||
return JSONCodec.dumps({'status': 'error', 'error': str(e)}, ensure_ascii=False)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# CODE INTERPRETER TOOLS
|
||||
# =============================================================================
|
||||
|
||||
@@ -47,6 +47,7 @@ from open_webui.models.tools import Tools
|
||||
from open_webui.models.users import UserModel
|
||||
from open_webui.tools.builtin import (
|
||||
add_memory,
|
||||
ask_user,
|
||||
calculate_timestamp,
|
||||
create_automation,
|
||||
create_calendar_event,
|
||||
@@ -583,6 +584,9 @@ async def get_builtin_tools(
|
||||
if is_builtin_tool_enabled('time'):
|
||||
builtin_functions.extend([get_current_timestamp, calculate_timestamp])
|
||||
|
||||
if is_builtin_tool_enabled('user_input'):
|
||||
builtin_functions.append(ask_user)
|
||||
|
||||
metadata = extra_params.get('__metadata__') or {}
|
||||
chat_files = metadata.get('files') or extra_params.get('__files__') or []
|
||||
has_chat_files = any(
|
||||
|
||||
Reference in New Issue
Block a user