Skip to content
Open
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
8 changes: 8 additions & 0 deletions sdks/python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,14 @@ final_status = await client.documents.wait_for_task(result.task_id)
print(f"Status: {final_status.status}")
print(f"Successful files: {final_status.successful_files}")

# Get structured failure metadata for a task
enhanced_status = await client.documents.get_task_status_enhanced(result.task_id)
print(enhanced_status.files)

# List tasks with structured failure metadata
enhanced_tasks = await client.documents.list_tasks_enhanced()
print(len(enhanced_tasks.tasks))

# Ingest from file object
with open("./report.pdf", "rb") as f:
result = await client.documents.ingest(file=f, filename="report.pdf")
Expand Down
2 changes: 2 additions & 0 deletions sdks/python/openrag_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
Source,
SourcesEvent,
StreamEvent,
TaskListResponse,
UpdateKnowledgeFilterOptions,
)

Expand Down Expand Up @@ -93,6 +94,7 @@
"SearchResult",
"SearchFilters",
"IngestResponse",
"TaskListResponse",
"DeleteDocumentResponse",
"Conversation",
"ConversationDetail",
Expand Down
35 changes: 34 additions & 1 deletion sdks/python/openrag_sdk/documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@
from typing import TYPE_CHECKING, BinaryIO

from .exceptions import NotFoundError
from .models import DeleteDocumentResponse, IngestResponse, IngestTaskStatus
from .models import (
DeleteDocumentResponse,
IngestResponse,
IngestTaskStatus,
TaskListResponse,
)

if TYPE_CHECKING:
from .client import OpenRAGClient
Expand Down Expand Up @@ -97,6 +102,34 @@ async def get_task_status(self, task_id: str) -> IngestTaskStatus:
data = response.json()
return IngestTaskStatus(**data)

async def get_task_status_enhanced(self, task_id: str) -> IngestTaskStatus:
"""
Get the status of an ingestion task with structured failure metadata.

Args:
task_id: The task ID returned from ingest().

Returns:
IngestTaskStatus with current task status and classified failure details.
"""
response = await self._client._request(
"GET",
f"/api/v1/tasks/{task_id}/enhanced",
)
data = response.json()
return IngestTaskStatus(**data)

async def list_tasks_enhanced(self) -> TaskListResponse:
"""
List ingestion tasks with structured failure metadata.

Returns:
TaskListResponse with enhanced task status entries.
"""
response = await self._client._request("GET", "/api/v1/tasks/enhanced")
data = response.json()
return TaskListResponse(**data)

async def wait_for_task(
self,
task_id: str,
Expand Down
6 changes: 6 additions & 0 deletions sdks/python/openrag_sdk/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ class IngestTaskStatus(BaseModel):
files: dict = {} # Detailed per-file status


class TaskListResponse(BaseModel):
"""Response from listing ingestion tasks."""

tasks: list[IngestTaskStatus]


class DeleteDocumentResponse(BaseModel):
"""Response from document deletion."""

Expand Down
8 changes: 8 additions & 0 deletions sdks/typescript/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,14 @@ const finalStatus = await client.documents.waitForTask(result.task_id);
console.log(`Status: ${finalStatus.status}`);
console.log(`Successful files: ${finalStatus.successful_files}`);

// Get structured failure metadata for a task
const enhancedStatus = await client.documents.getTaskStatusEnhanced(result.task_id);
console.log(enhancedStatus.files);

// List tasks with structured failure metadata
const enhancedTasks = await client.documents.listTasksEnhanced();
console.log(enhancedTasks.tasks.length);

// Ingest from File object (browser)
const file = new File([...], "report.pdf");
const result = await client.documents.ingest({
Expand Down
49 changes: 42 additions & 7 deletions sdks/typescript/src/documents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
IngestResponse,
IngestTaskStatus,
NotFoundError,
TaskListResponse,
} from "./types";

export interface IngestOptions {
Expand Down Expand Up @@ -100,14 +101,48 @@ export class DocumentsClient {
`/api/v1/tasks/${taskId}`
);
const data = await response.json();
return this.parseTaskStatus(data);
}

/**
* Get the status of an ingestion task with structured failure metadata.
*
* @param taskId - The task ID returned from ingest().
* @returns IngestTaskStatus with current task status and classified failure details.
*/
async getTaskStatusEnhanced(taskId: string): Promise<IngestTaskStatus> {
const response = await this.client._request(
"GET",
`/api/v1/tasks/${taskId}/enhanced`
);
const data = await response.json();
return this.parseTaskStatus(data);
}

/**
* List ingestion tasks with structured failure metadata.
*
* @returns TaskListResponse with enhanced task status entries.
*/
async listTasksEnhanced(): Promise<TaskListResponse> {
const response = await this.client._request("GET", "/api/v1/tasks/enhanced");
const data = await response.json();
return {
task_id: data.task_id,
status: data.status,
total_files: data.total_files ?? 0,
processed_files: data.processed_files ?? 0,
successful_files: data.successful_files ?? 0,
failed_files: data.failed_files ?? 0,
files: data.files ?? {},
tasks: (data.tasks ?? []).map((task: Record<string, unknown>) =>
this.parseTaskStatus(task)
),
Comment on lines +131 to +133

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard data.tasks with Array.isArray before calling .map().

If the API returns a non-array truthy value for tasks, this will throw at runtime. A narrow shape guard keeps the method resilient.

Suggested change
   async listTasksEnhanced(): Promise<TaskListResponse> {
     const response = await this.client._request("GET", "/api/v1/tasks/enhanced");
     const data = await response.json();
+    const tasksRaw = Array.isArray(data.tasks) ? data.tasks : [];
     return {
-      tasks: (data.tasks ?? []).map((task: Record<string, unknown>) =>
+      tasks: tasksRaw.map((task: Record<string, unknown>) =>
         this.parseTaskStatus(task)
       ),
     };
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
tasks: (data.tasks ?? []).map((task: Record<string, unknown>) =>
this.parseTaskStatus(task)
),
async listTasksEnhanced(): Promise<TaskListResponse> {
const response = await this.client._request("GET", "/api/v1/tasks/enhanced");
const data = await response.json();
const tasksRaw = Array.isArray(data.tasks) ? data.tasks : [];
return {
tasks: tasksRaw.map((task: Record<string, unknown>) =>
this.parseTaskStatus(task)
),
};
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdks/typescript/src/documents.ts` around lines 131 - 133, The code maps
data.tasks without ensuring it's an array, which can throw if the API returns a
non-array; update the tasks assignment in the relevant method in documents.ts to
guard with Array.isArray(data.tasks) and only map when true, otherwise default
to an empty array (or preserve expected fallback), calling this.parseTaskStatus
for each item; reference the data.tasks field and this.parseTaskStatus to locate
and update the logic.

};
}

private parseTaskStatus(data: Record<string, unknown>): IngestTaskStatus {
return {
task_id: data["task_id"] as string,
status: data["status"] as string,
total_files: (data["total_files"] as number | undefined) ?? 0,
processed_files: (data["processed_files"] as number | undefined) ?? 0,
successful_files: (data["successful_files"] as number | undefined) ?? 0,
failed_files: (data["failed_files"] as number | undefined) ?? 0,
files: (data["files"] as Record<string, unknown> | undefined) ?? {},
};
}

Expand Down
2 changes: 2 additions & 0 deletions sdks/typescript/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ export {
SearchResult,
// Document types
IngestResponse,
IngestTaskStatus,
TaskListResponse,
DeleteDocumentResponse,
// Conversation types
Conversation,
Expand Down
4 changes: 4 additions & 0 deletions sdks/typescript/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ export interface IngestTaskStatus {
files: Record<string, unknown>;
}

export interface TaskListResponse {
tasks: IngestTaskStatus[];
}

export interface DeleteDocumentResponse {
success: boolean;
deleted_chunks: number;
Expand Down
15 changes: 15 additions & 0 deletions sdks/typescript/tests/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,21 @@ describe.skipIf(SKIP_TESTS)("OpenRAG TypeScript SDK Integration", () => {
expect(finalStatus.status).toBeDefined();
});

it("should get enhanced task status", async () => {
const result = await client.documents.ingest({
filePath: testFilePath,
wait: false,
});
const taskId = (result as any).task_id;
expect(taskId).toBeDefined();

const enhancedStatus = await client.documents.getTaskStatusEnhanced(taskId);
expect(enhancedStatus.status).toBeDefined();

const enhancedTasks = await client.documents.listTasksEnhanced();
expect(Array.isArray(enhancedTasks.tasks)).toBe(true);
});

it("should delete a document", async () => {
// First ingest (wait for completion)
const ingestResult = await client.documents.ingest({ filePath: testFilePath });
Expand Down
8 changes: 8 additions & 0 deletions tests/integration/sdk/test_documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,14 @@ async def test_task_status_polling(self, client, tmp_path):
status = await client.documents.get_task_status(task_response.task_id)
assert status.status is not None

enhanced_status = await client.documents.get_task_status_enhanced(
task_response.task_id
)
assert enhanced_status.status is not None

enhanced_tasks = await client.documents.list_tasks_enhanced()
assert isinstance(enhanced_tasks.tasks, list)
Comment on lines +119 to +125

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Shared root cause: enhanced integration tests are under-asserting contract fidelity in tests/integration/sdk/test_documents.py and sdks/typescript/tests/integration.test.ts.
Both tests only verify basic shape (status defined / tasks is array), so they can pass even when enhanced metadata regresses. Tighten both to assert enhanced files presence and that the created task appears in enhanced task listings.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/sdk/test_documents.py` around lines 119 - 125, The enhanced
integration test is under-asserting contract fidelity: update tests in
tests/integration/sdk/test_documents.py to verify enhanced metadata by asserting
that enhanced_status.files is present and non-empty and that the created task
(task_response.task_id) appears in the enhanced_tasks.tasks listing (e.g., at
least one entry has matching task_id); specifically add assertions after calling
client.documents.get_task_status_enhanced and
client.documents.list_tasks_enhanced to check enhanced_status.files (or
equivalent files field) exists and contains expected file entries and that any
task in enhanced_tasks.tasks has task_id == task_response.task_id to ensure the
created task is included.


final = await client.documents.wait_for_task(task_response.task_id)
assert final.status in ("completed", "failed")

Expand Down
Loading