Skip to content

Commit 3dddcd8

Browse files
authored
Merge pull request #196 from Ontos-AI/feat/wangbinqi/knowhere-v2-page-memory-api
Add v2 page-memory ingestion API
2 parents 54d18fd + 5b1c1aa commit 3dddcd8

34 files changed

Lines changed: 1303 additions & 324 deletions

apps/api/.env.example

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,6 @@ MAX_PDF_PAGE_LIMIT=200
9898
OVERSIZED_PDF_SHARD_ENABLED=true
9999
OVERSIZED_PDF_SOFT_LIMIT=1500
100100
PDF_PROFILE_TOC_ENABLED=false
101-
RETRIEVAL_PAGE_MEMORY_ENABLED=false
102-
PAGE_MEMORY_ASSET_EXTRACTION_ENABLED=false
103-
PAGE_MEMORY_ASSET_SUMMARY_ENABLED=false
104-
PAGE_MEMORY_MAX_PAGES=1500
105101
MINERU_SHARD_CONCURRENCY=3
106102
PARSE_AGENT_PLAN_BUDGET=50000
107103
PARSE_AGENT_VISUAL_BUDGET=80000
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
"""Add v2 job polling system limit.
2+
3+
Revision ID: fbe1c2d3e4f5
4+
Revises: fae1b2c3d4e5
5+
Create Date: 2026-07-01 13:45:00.000000
6+
7+
"""
8+
9+
from typing import Sequence, Union
10+
11+
from alembic import op
12+
13+
14+
# revision identifiers, used by Alembic.
15+
revision: str = "fbe1c2d3e4f5"
16+
down_revision: Union[str, Sequence[str], None] = "fae1b2c3d4e5"
17+
branch_labels: Union[str, Sequence[str], None] = None
18+
depends_on: Union[str, Sequence[str], None] = None
19+
20+
21+
def upgrade() -> None:
22+
"""Cap v2 job-result polling like v1 job-result polling."""
23+
op.execute(
24+
"""
25+
INSERT INTO system_limits (
26+
method,
27+
api_pattern,
28+
priority,
29+
rpm,
30+
period,
31+
description
32+
)
33+
VALUES (
34+
'GET',
35+
'/v2/jobs/*',
36+
200,
37+
200,
38+
'minute',
39+
'Job queries - prevent polling'
40+
)
41+
ON CONFLICT (method, api_pattern) DO NOTHING
42+
"""
43+
)
44+
45+
46+
def downgrade() -> None:
47+
"""Remove the v2 job-result polling cap."""
48+
op.execute(
49+
"""
50+
DELETE FROM system_limits
51+
WHERE method = 'GET'
52+
AND api_pattern = '/v2/jobs/*'
53+
"""
54+
)

apps/api/app/api/api_router.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
"""
44

55
from app.api.v1.api_v1 import api_router as v1_router
6+
from app.api.v2.api_v2 import api_router as v2_router
67
from fastapi import APIRouter
78

89
api_router = APIRouter()
910

1011
# Register the versioned API routes.
1112
api_router.include_router(v1_router, prefix="/v1")
13+
api_router.include_router(v2_router, prefix="/v2")
1214

1315
__all__ = ["api_router"]

apps/api/app/api/v1/routes/jobs.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ async def create_job( # pyright: ignore[reportGeneralTypeIssues]
4646
"""
4747
Create a parsing job.
4848
"""
49-
return await _document_ingestion_service.create_job(
49+
return await _document_ingestion_service.create_v1_job(
5050
db,
5151
payload=payload,
5252
current_user=current_user,

apps/api/app/api/v2/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""API v2 package."""

apps/api/app/api/v2/api_v2.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
"""API v2 route registry."""
2+
3+
from app.api.v2.routes import documents, jobs, retrieval
4+
from fastapi import APIRouter
5+
6+
api_router = APIRouter()
7+
8+
api_router.include_router(jobs.router, prefix="/jobs", tags=["Jobs"])
9+
api_router.include_router(retrieval.router, prefix="/retrieval", tags=["Retrieval"])
10+
api_router.include_router(documents.router, prefix="/documents", tags=["Documents"])
11+
12+
__all__ = ["api_router"]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""API v2 route package."""
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"""Documents API v2 routes."""
2+
3+
from app.api.v1.routes.documents import router
4+
5+
__all__ = ["router"]

apps/api/app/api/v2/routes/jobs.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
"""Jobs API v2 routes."""
2+
3+
from __future__ import annotations
4+
5+
from datetime import datetime
6+
from typing import Optional
7+
8+
from app.api.dependencies.auth import require_write_permission
9+
from app.api.dependencies.current_user import with_current_user
10+
from app.api.dependencies.job_admission import require_billing_limits
11+
from app.services.document_ingestion import DocumentIngestionService
12+
from app.services.jobs import (
13+
get_job_result_for_user,
14+
list_jobs_for_user,
15+
)
16+
from app.services.rate_limit.data_structures import CurrentUser
17+
from fastapi import APIRouter, Depends, Query
18+
from sqlalchemy.ext.asyncio import AsyncSession
19+
20+
from shared.core.database import get_db
21+
from shared.models.schemas.job import (
22+
ConfirmUploadRequest,
23+
JobCreateV2,
24+
JobList,
25+
JobResponse,
26+
JobResultResponse,
27+
)
28+
29+
router = APIRouter(tags=["Jobs"])
30+
_document_ingestion_service = DocumentIngestionService()
31+
32+
33+
@router.post("", response_model=JobResponse, summary="Create a v2 parsing job")
34+
@router.post("/", include_in_schema=False)
35+
async def create_job( # pyright: ignore[reportGeneralTypeIssues]
36+
payload: JobCreateV2,
37+
current_user: CurrentUser = Depends(require_billing_limits),
38+
_write_permission: None = Depends(require_write_permission),
39+
db: AsyncSession = Depends(get_db),
40+
):
41+
"""Create a v2 parsing job."""
42+
return await _document_ingestion_service.create_v2_job(
43+
db,
44+
payload=payload,
45+
current_user=current_user,
46+
)
47+
48+
49+
@router.get("", response_model=JobList, summary="List jobs")
50+
@router.get("/page", response_model=JobList, include_in_schema=False)
51+
async def list_jobs(
52+
page: int = Query(1, ge=1, description="Page number"),
53+
page_size: int = Query(20, ge=1, le=100, description="Items per page"),
54+
job_status: Optional[str] = Query(None, description="Status filter"),
55+
job_type: Optional[str] = Query(None, description="Job type filter"),
56+
recent_days: Optional[int] = Query(
57+
None,
58+
description="Recent-day filter; supported values are 1, 7, and 30",
59+
enum=[1, 7, 30],
60+
),
61+
start_time: Optional[datetime] = Query(
62+
None, description="Start time in ISO format"
63+
),
64+
end_time: Optional[datetime] = Query(None, description="End time in ISO format"),
65+
current_user: CurrentUser = Depends(with_current_user),
66+
db: AsyncSession = Depends(get_db),
67+
):
68+
"""List jobs for the current user."""
69+
return await list_jobs_for_user(
70+
db,
71+
user_id=current_user.user_id,
72+
page=page,
73+
page_size=page_size,
74+
job_status=job_status,
75+
job_type=job_type,
76+
recent_days=recent_days,
77+
start_time=start_time,
78+
end_time=end_time,
79+
)
80+
81+
82+
@router.get("/{job_id}", response_model=JobResultResponse, summary="Get a job result")
83+
async def get_job_result(
84+
job_id: str,
85+
current_user: CurrentUser = Depends(with_current_user),
86+
db: AsyncSession = Depends(get_db),
87+
):
88+
"""Return the result payload for one job."""
89+
return await get_job_result_for_user(
90+
db,
91+
job_id=job_id,
92+
user_id=current_user.user_id,
93+
)
94+
95+
96+
@router.post(
97+
"/{job_id}/confirm-upload",
98+
response_model=dict,
99+
summary="Confirm file upload",
100+
)
101+
async def confirm_upload(
102+
job_id: str,
103+
request: Optional[ConfirmUploadRequest] = None,
104+
current_user: CurrentUser = Depends(with_current_user),
105+
_write_permission: None = Depends(require_write_permission),
106+
db: AsyncSession = Depends(get_db),
107+
):
108+
"""Confirm a completed file upload as a fallback path."""
109+
return await _document_ingestion_service.confirm_upload(
110+
db,
111+
job_id=job_id,
112+
request_payload=request,
113+
user_id=current_user.user_id,
114+
)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"""Retrieval API v2 routes."""
2+
3+
from app.api.v1.routes.retrieval import router
4+
5+
__all__ = ["router"]

0 commit comments

Comments
 (0)