feat: add a File Processing model capability to skip extraction and pass raw files through

This commit is contained in:
silentoplayz 2026-07-27 19:37:53 -04:00
parent 31d08d592c
commit 7e5e62fd34
8 changed files with 81 additions and 8 deletions

View file

@ -1483,7 +1483,7 @@ async def get_sources_from_items(
or ('file', item.get('id')) in folder_items
):
query_result = {
'documents': [[file_object.data.get('content', '')]],
'documents': [[(file_object.data or {}).get('content', '')]],
'metadatas': [
[
{

View file

@ -2542,7 +2542,16 @@ async def query_chat_files(
if not accessible:
return JSONCodec.dumps({'error': 'No accessible files found'})
file_items = [{**item} for item, _ in accessible]
file_items = [{**item} for item, _ in accessible if item.get('processed') is not False]
if not file_items:
return json.dumps(
{
'error': (
'The attached files were uploaded without content extraction '
'(raw attachments); their content cannot be queried as text.'
)
}
)
rag_config = await Config.get_many(
'rag.top_k',
'rag.top_k_reranker',
@ -2791,6 +2800,19 @@ async def view_file(
if file.data:
content = file.data.get('content', '')
if not content and 'content' not in (file.data or {}):
return json.dumps(
{
'id': file.id,
'filename': file.filename,
'processed': False,
'note': (
'This file was uploaded without content extraction (raw attachment); '
'no text content is available.'
),
}
)
total_chars = len(content)
# Line-based addressing (overrides char-based offset/max_chars)
@ -2931,6 +2953,19 @@ async def view_knowledge_file(
if file.data:
content = file.data.get('content', '')
if not content and 'content' not in (file.data or {}):
return json.dumps(
{
'id': file.id,
'filename': file.filename,
'processed': False,
'note': (
'This file was uploaded without content extraction (raw attachment); '
'no text content is available.'
),
}
)
total_chars = len(content)
# Line-based addressing (overrides char-based offset/max_chars)

View file

@ -342,6 +342,8 @@ def get_citation_source_from_tool_result(
elif tool_name in ('view_knowledge_file', 'view_file'):
file_data = tool_result
if 'content' not in file_data:
return []
filename = file_data.get('filename', 'Unknown File')
file_id = file_data.get('id', '')
knowledge_name = file_data.get('knowledge_name', '')
@ -1856,7 +1858,12 @@ async def chat_completion_files_handler(
__event_emitter__ = extra_params['__event_emitter__']
sources = []
if files := body.get('metadata', {}).get('files', None):
files = [
item
for item in (body.get('metadata', {}).get('files', None) or [])
if item.get('processed') is not False
]
if files:
# Check if all files are in full context mode
all_full_context = all(item.get('context') == 'full' for item in files)

View file

@ -719,6 +719,7 @@
type ModelCapability =
| 'vision'
| 'file_upload'
| 'file_processing'
| 'web_search'
| 'image_generation'
| 'code_interpreter'
@ -746,6 +747,13 @@
modelCapabilitiesById
);
let fileProcessingCapableModels = [];
$: fileProcessingCapableModels = getCapableModelIds(
selectedModelIds,
'file_processing',
modelCapabilitiesById
);
let webSearchCapableModels = [];
$: webSearchCapableModels = getCapableModelIds(
selectedModelIds,
@ -871,6 +879,12 @@
return null;
}
const skipProcessing =
process && selectedModelIds.length > 0 && fileProcessingCapableModels.length === 0;
if (skipProcessing) {
process = false;
}
const tempItemId = uuidv4();
const fileItem = {
type: 'file',
@ -886,6 +900,7 @@
// Stamp the user's default upload mode so the sent payload carries it;
// the per-file toggle in FileItemModal can still override it afterwards.
...($settings?.defaultUploadContext === 'full' ? { context: 'full' } : {}),
...(skipProcessing ? { processed: false } : {}),
...itemData
};

View file

@ -198,7 +198,8 @@
};
const loadContent = async () => {
selectedTab = '';
const hasPreview = isAudio || isPDF || isExcel || isCode || isMarkdown || isDocx || isPptx;
selectedTab = item?.processed === false && hasPreview ? 'preview' : '';
expandedContent = false;
docxData = null;
if (item?.type === 'collection') {
@ -344,7 +345,7 @@
{/if}
</div>
{#if edit}
{#if edit && item?.processed !== false}
<div class=" self-end">
<Tooltip
content={enableFullContent
@ -438,7 +439,13 @@
</PanzoomContainer>
</div>
{:else if selectedTab === ''}
{#if item?.file?.data}
{#if item?.processed === false}
<div class="py-2 text-sm text-gray-500">
{$i18n.t(
'This file was uploaded without content extraction (raw attachment); no text content is available.'
)}
</div>
{:else if item?.file?.data}
{@const rawContent = (item?.file?.data?.content ?? '').trim() || 'No content'}
{@const isTruncated =
($settings?.renderMarkdownInPreviews ?? true) &&

View file

@ -17,6 +17,12 @@
label: $i18n.t('File Upload'),
description: $i18n.t('Model accepts file inputs')
},
file_processing: {
label: $i18n.t('File Processing'),
description: $i18n.t(
'Extract file content on upload. When disabled, uploads skip extraction entirely and files are passed along as raw attachments for the model or pipe to handle'
)
},
file_context: {
label: $i18n.t('File Context'),
description: $i18n.t('Inject file content into conversation context')
@ -74,9 +80,8 @@
capabilities = capabilities;
};
// Hide file_context when file_upload is disabled
$: visibleCapabilities = (Object.keys(capabilityLabels) as Capability[]).filter((cap) => {
if (cap === 'file_context' && !capabilities.file_upload) {
if ((cap === 'file_processing' || cap === 'file_context') && !capabilities.file_upload) {
return false;
}
return true;

View file

@ -103,6 +103,7 @@ export const SUPPORTED_FILE_EXTENSIONS = [
export const DEFAULT_CAPABILITIES = {
file_context: true,
file_processing: true,
vision: true,
file_upload: true,
web_search: true,

View file

@ -1152,6 +1152,7 @@
"External Web Loader URL": "",
"External Web Search API Key": "",
"External Web Search URL": "",
"Extract file content on upload. When disabled, uploads skip extraction entirely and files are passed along as raw attachments for the model or pipe to handle": "",
"Extract action items from this note.": "",
"Extract images from PDFs so OCR can process image-only pages.": "",
"Fade Effect for Streaming Text": "",
@ -1224,6 +1225,7 @@
"File content": "",
"File content updated successfully.": "",
"File Context": "",
"File Processing": "",
"File deleted successfully.": "",
"File Extensions": "",
"File Mode": "",
@ -2646,6 +2648,7 @@
"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "",
"This chat won't appear in history and your messages will not be saved.": "",
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "",
"This file was uploaded without content extraction (raw attachment); no text content is available.": "",
"This folder is empty": "",
"This is a default user permission and will remain enabled.": "",
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",