4747 KnowledgeService ,
4848 knowledge_base_qa_service ,
4949)
50- from app .services .knowledge .orchestrator import knowledge_orchestrator
50+ from app .services .knowledge .orchestrator import (
51+ MAX_DOCUMENT_READ_LIMIT ,
52+ knowledge_orchestrator ,
53+ )
5154from shared .telemetry .decorators import (
5255 add_span_event ,
5356 capture_trace_context ,
6164router = APIRouter ()
6265
6366
67+ def _serialize_standalone_document_detail (
68+ detail : DocumentDetailResponse | dict ,
69+ * ,
70+ include_content : bool ,
71+ include_summary : bool ,
72+ ) -> dict :
73+ """Preserve the standalone endpoint's pre-refactor response shape."""
74+ payload = detail .model_dump () if hasattr (detail , "model_dump" ) else dict (detail )
75+ response = {
76+ "document_id" : payload ["document_id" ],
77+ }
78+ if include_content :
79+ response ["content" ] = payload .get ("content" )
80+ response ["content_length" ] = payload .get ("content_length" )
81+ response ["truncated" ] = payload .get ("truncated" )
82+ if include_summary :
83+ response ["summary" ] = payload .get ("summary" )
84+ return response
85+
86+
87+ def _raise_document_detail_http_error (error : ValueError ) -> None :
88+ """Map orchestrator detail errors to stable HTTP responses."""
89+ error_msg = str (error )
90+ if "not found" in error_msg .lower ():
91+ raise HTTPException (
92+ status_code = status .HTTP_404_NOT_FOUND ,
93+ detail = error_msg ,
94+ )
95+ if "access denied" in error_msg .lower ():
96+ raise HTTPException (
97+ status_code = status .HTTP_403_FORBIDDEN ,
98+ detail = error_msg ,
99+ )
100+ raise HTTPException (
101+ status_code = status .HTTP_400_BAD_REQUEST ,
102+ detail = error_msg ,
103+ )
104+
105+
106+ def _validate_knowledge_base_access_or_raise (
107+ db : Session ,
108+ * ,
109+ knowledge_base_id : int ,
110+ user : User ,
111+ ):
112+ """Restore the KB-scoped endpoint's KB-level error semantics."""
113+ knowledge_base , has_access = KnowledgeService .get_knowledge_base (
114+ db = db ,
115+ knowledge_base_id = knowledge_base_id ,
116+ user_id = user .id ,
117+ )
118+ if not knowledge_base :
119+ raise HTTPException (
120+ status_code = status .HTTP_404_NOT_FOUND ,
121+ detail = "Knowledge base not found" ,
122+ )
123+ if not has_access :
124+ raise HTTPException (
125+ status_code = status .HTTP_403_FORBIDDEN ,
126+ detail = "Access denied to this knowledge base" ,
127+ )
128+ return knowledge_base
129+
130+
64131# ============== Knowledge Base Endpoints ==============
65132
66133
@@ -695,8 +762,8 @@ async def update_document_content(
695762
696763
697764@document_router .get ("/{document_id}/detail" )
698- @trace_sync ("get_document_detail_standalone" , "knowledge.api" )
699- def get_document_detail_standalone (
765+ @trace_async ("get_document_detail_standalone" , "knowledge.api" )
766+ async def get_document_detail_standalone (
700767 document_id : int ,
701768 include_content : bool = Query (True , description = "Include document content" ),
702769 include_summary : bool = Query (True , description = "Include document summary" ),
@@ -709,76 +776,23 @@ def get_document_detail_standalone(
709776 This is a convenience endpoint for getting document content when the kb_id
710777 is not readily available (e.g., in citation tooltips).
711778 """
712- from app .models .knowledge import KnowledgeDocument
713-
714- # Get document
715- document = (
716- db .query (KnowledgeDocument ).filter (KnowledgeDocument .id == document_id ).first ()
717- )
718-
719- if not document :
720- raise HTTPException (
721- status_code = status .HTTP_404_NOT_FOUND ,
722- detail = "Document not found" ,
723- )
724-
725- # Check access permission via knowledge base
726- kb , has_access = KnowledgeService .get_knowledge_base (
727- db = db ,
728- knowledge_base_id = document .kind_id ,
729- user_id = current_user .id ,
730- )
731- if not kb :
732- raise HTTPException (
733- status_code = status .HTTP_404_NOT_FOUND ,
734- detail = "Knowledge base not found" ,
779+ try :
780+ detail = await knowledge_orchestrator .get_document_detail (
781+ db = db ,
782+ user = current_user ,
783+ document_id = document_id ,
784+ include_content = include_content ,
785+ include_summary = include_summary ,
786+ offset = 0 ,
787+ limit = MAX_DOCUMENT_READ_LIMIT ,
735788 )
736- if not has_access :
737- raise HTTPException (
738- status_code = status . HTTP_403_FORBIDDEN ,
739- detail = "Access denied to this document" ,
789+ return _serialize_standalone_document_detail (
790+ detail ,
791+ include_content = include_content ,
792+ include_summary = include_summary ,
740793 )
741-
742- # Get content if requested
743- content = None
744- content_length = None
745- truncated = False
746-
747- if include_content and document .attachment_id :
748- try :
749- from app .services .context import context_service
750-
751- attachment = context_service .get_context_by_id (
752- db = db ,
753- context_id = document .attachment_id ,
754- )
755- if attachment and attachment .extracted_text :
756- full_content = attachment .extracted_text
757- content_length = len (full_content )
758- # Truncate if too large
759- max_length = 100000
760- if content_length > max_length :
761- content = full_content [:max_length ]
762- truncated = True
763- else :
764- content = full_content
765- except Exception as e :
766- logger .warning (f"Failed to get document content: { e } " )
767-
768- # Build response
769- response = {
770- "document_id" : document_id ,
771- }
772-
773- if include_content :
774- response ["content" ] = content
775- response ["content_length" ] = content_length
776- response ["truncated" ] = truncated
777-
778- if include_summary :
779- response ["summary" ] = document .summary
780-
781- return response
794+ except ValueError as error :
795+ _raise_document_detail_http_error (error )
782796
783797
784798@document_router .post ("/batch/delete" , response_model = BatchOperationResult )
@@ -1104,23 +1118,13 @@ async def get_document_detail(
11041118 - summary: Document summary object (if include_summary=true)
11051119 """
11061120 from app .models .knowledge import KnowledgeDocument
1107- from app .models .subtask_context import SubtaskContext
1108- from app .services .knowledge import get_summary_service
11091121
1110- # Validate KB access permission first
1111- kb , has_access = KnowledgeService .get_knowledge_base (db , kb_id , current_user .id )
1112- if not kb :
1113- raise HTTPException (
1114- status_code = status .HTTP_404_NOT_FOUND ,
1115- detail = "Knowledge base not found" ,
1116- )
1117- if not has_access :
1118- raise HTTPException (
1119- status_code = status .HTTP_403_FORBIDDEN ,
1120- detail = "Access denied to this knowledge base" ,
1121- )
1122+ _validate_knowledge_base_access_or_raise (
1123+ db ,
1124+ knowledge_base_id = kb_id ,
1125+ user = current_user ,
1126+ )
11221127
1123- # Validate document belongs to the specified knowledge base
11241128 document = (
11251129 db .query (KnowledgeDocument )
11261130 .filter (
@@ -1135,53 +1139,18 @@ async def get_document_detail(
11351139 detail = "Document not found in the specified knowledge base" ,
11361140 )
11371141
1138- # Initialize response data
1139- content = None
1140- content_length = None
1141- truncated = None
1142- summary = None
1143-
1144- # Get document content if requested
1145- if include_content :
1146- content = ""
1147- truncated = False
1148- max_length = 100000 # 100k characters limit for frontend display
1149-
1150- if document .attachment_id :
1151- context = (
1152- db .query (SubtaskContext )
1153- .filter (SubtaskContext .id == document .attachment_id )
1154- .first ()
1155- )
1156-
1157- if context and context .extracted_text :
1158- content = context .extracted_text
1159- # Truncate if too long
1160- if len (content ) > max_length :
1161- content = content [:max_length ]
1162- truncated = True
1163-
1164- content_length = len (content )
1165-
1166- # Get document summary if requested
1167- if include_summary :
1168- summary_service = get_summary_service (db )
1169- summary_obj = await summary_service .get_document_summary (doc_id )
1170- # Convert DocumentSummary object to dict for response
1171- if summary_obj :
1172- summary = (
1173- summary_obj .model_dump ()
1174- if hasattr (summary_obj , "model_dump" )
1175- else summary_obj
1176- )
1177-
1178- return DocumentDetailResponse (
1179- document_id = doc_id ,
1180- content = content ,
1181- content_length = content_length ,
1182- truncated = truncated ,
1183- summary = summary ,
1184- )
1142+ try :
1143+ return await knowledge_orchestrator .get_document_detail (
1144+ db = db ,
1145+ user = current_user ,
1146+ document_id = doc_id ,
1147+ include_content = include_content ,
1148+ include_summary = include_summary ,
1149+ offset = 0 ,
1150+ limit = MAX_DOCUMENT_READ_LIMIT ,
1151+ )
1152+ except ValueError as error :
1153+ _raise_document_detail_http_error (error )
11851154
11861155
11871156@summary_router .get ("/{kb_id}/documents/{doc_id}/summary" )
0 commit comments