fix(api): preserve integer knowledge ingestion task IDs

This commit is contained in:
huanghuoguoguo
2026-10-29 15:47:56 +08:00
parent d2176eaa27
commit 8f2f537e34
6 changed files with 116 additions and 12 deletions
@@ -255,3 +255,107 @@ async def test_chat_management_uses_scoped_history_and_real_connection_queues(re
== []
)
assert len(adapter.websocket_group_session.message_lists[pipeline]) == 1
@pytest.mark.asyncio
@pytest.mark.parametrize('extension', ['txt', 'zip'])
async def test_knowledge_upload_returns_real_integer_task_id(resources, tmp_path, monkeypatch, extension):
import asyncio
import io
import zipfile
from langbot.pkg.api.context import ExecutionContext
from langbot.pkg.core.taskmgr import AsyncTaskManager
from langbot.pkg.entity.persistence.rag import File
from langbot.pkg.entity.persistence.workspace import Workspace, WorkspaceExecutionState
from langbot.pkg.storage.mgr import StorageMgr
from langbot.pkg.storage.providers import localstorage
api, ap, engine = resources
workspace = '00000000-0000-0000-0000-00000000000a'
request = context(workspace)
execution = ExecutionContext.from_request(request)
kb_data = {
'uuid': 'ingestion-kb',
'workspace_uuid': workspace,
'name': 'Ingestion',
'knowledge_engine_plugin_id': 'test/engine',
'collection_id': 'ingestion-kb',
'creation_settings': {},
'retrieval_settings': {},
'initialized': True,
}
async with engine.begin() as connection:
await connection.execute(
sa.insert(Workspace).values(
uuid=workspace,
instance_uuid=request.instance_uuid,
name='Ingestion',
slug='ingestion',
source='cloud_projection',
)
)
await connection.execute(
sa.insert(WorkspaceExecutionState).values(
workspace_uuid=workspace, instance_uuid=request.instance_uuid, active_generation=1, source='cloud'
)
)
await connection.execute(sa.insert(KnowledgeBase).values(**kb_data))
ap.event_loop = asyncio.get_running_loop()
ap.task_mgr = AsyncTaskManager(ap)
monkeypatch.setattr(localstorage, 'LOCAL_STORAGE_PATH', str(tmp_path / 'uploads'))
ap.storage_mgr = StorageMgr(ap)
ap.storage_mgr.storage_provider = localstorage.LocalStorageProvider(ap)
entered, release = asyncio.Event(), asyncio.Event()
async def ingest(_plugin_id, _payload):
entered.set()
await release.wait()
return {'document_id': 'engine-document', 'status': 'completed'}
ap.plugin_connector = SimpleNamespace(
is_enable_plugin=True,
require_workspace_context=AsyncMock(side_effect=lambda scope: scope),
list_knowledge_engines=AsyncMock(
return_value=[{'plugin_id': 'test/engine', 'capabilities': ['doc_ingestion']}]
),
call_rag_ingest=AsyncMock(side_effect=ingest),
)
await ap.rag_mgr.load_knowledge_base(execution, KnowledgeBase(**kb_data))
content = b'A document for ingestion.'
if extension == 'zip':
buffer = io.BytesIO()
with zipfile.ZipFile(buffer, 'w') as archive:
archive.writestr('document.txt', content)
content = buffer.getvalue()
upload = await ap.storage_mgr.save_scoped(
execution, owner_type='upload_document', owner='ingestion-test', key='document.' + extension, value=content
)
try:
task_id = await api.execute('knowledge.file.store', request, {'kb_uuid': kb_data['uuid'], 'file_id': upload})
assert type(task_id) is int
task = ap.task_mgr.get_task_by_id(
task_id, instance_uuid=request.instance_uuid, workspace_uuid=workspace, placement_generation=1
)
assert task is not None and task.kind == 'knowledge-operation'
await asyncio.wait_for(entered.wait(), 5)
async with engine.connect() as connection:
pending = (
(await connection.execute(sa.select(File).where(File.workspace_uuid == workspace))).mappings().one()
)
assert pending['kb_id'] == kb_data['uuid'] and pending['status'] == 'processing'
release.set()
await asyncio.wait_for(task.task, 5)
async with engine.connect() as connection:
stored = (await connection.execute(sa.select(File).where(File.uuid == pending['uuid']))).mappings().one()
assert stored['status'] == 'completed' and stored['engine_document_id'] == 'engine-document'
assert not await ap.storage_mgr.exists_scoped_object_key(
execution, upload, expected_owner_type='upload_document'
)
finally:
release.set()
tasks = [wrapper.task for wrapper in ap.task_mgr.get_all_tasks()]
for task in tasks:
if not task.done():
task.cancel()
await asyncio.gather(*tasks, return_exceptions=True)
+2 -2
View File
@@ -123,7 +123,7 @@ def fake_knowledge_app():
app.knowledge_service.get_files_by_knowledge_base = AsyncMock(
return_value=[{'uuid': 'test-file-uuid', 'filename': 'test.pdf'}]
)
app.knowledge_service.store_file = AsyncMock(return_value='test-task-id')
app.knowledge_service.store_file = AsyncMock(return_value=17)
app.knowledge_service.delete_file = AsyncMock()
app.knowledge_service.retrieve_knowledge_base = AsyncMock(return_value=[{'content': 'test result', 'score': 0.95}])
@@ -304,7 +304,7 @@ class TestKnowledgeBaseFilesEndpoints:
assert called.await_args.args[0].workspace_uuid == '00000000-0000-0000-0000-00000000000a'
assert called.await_args.args[1:] == ('test-kb-uuid', 'test-file-id')
assert called.await_args.kwargs == {'parser_plugin_id': 'test/parser'}
assert data['data'] == {'task_id': 'test-task-id'}
assert data['data'] == {'task_id': 17}
@pytest.mark.asyncio
async def test_delete_file_from_knowledge_base(self, quart_test_client, fake_knowledge_app):