1- use crate :: kg_email_extractor:: types:: LabelDocumentParams ;
1+ use crate :: kg_email_extractor:: types:: { DocumentType , LabelDocumentParams } ;
22use crate :: storage:: { AgentStorage , Message } ;
33use nocodo_llm_sdk:: client:: LlmClient ;
44use nocodo_llm_sdk:: types:: { CompletionRequest , ContentBlock , Message as LlmMessage } ;
55use nocodo_llm_sdk:: Tool ;
66use std:: sync:: Arc ;
77
8+ const RESPONSE_PREVIEW_CHARS : usize = 1200 ;
9+
10+ fn preview_text ( value : & str , max_chars : usize ) -> String {
11+ value. chars ( ) . take ( max_chars) . collect ( )
12+ }
13+
14+ fn fallback_label_from_template ( template : & str ) -> LabelDocumentParams {
15+ let text = template. to_ascii_lowercase ( ) ;
16+
17+ let has_receipt = text. contains ( "receipt" ) ;
18+ let has_payment = text. contains ( "payment" )
19+ || text. contains ( "paid" )
20+ || text. contains ( "debited" )
21+ || text. contains ( "charged" ) ;
22+ let has_bill = text. contains ( "amount due" )
23+ || text. contains ( "due date" )
24+ || text. contains ( "pay by" )
25+ || text. contains ( "billing period" )
26+ || text. contains ( "invoice" ) ;
27+ let has_order = text. contains ( "order" )
28+ || text. contains ( "shipment" )
29+ || text. contains ( "tracking" )
30+ || text. contains ( "delivered" ) ;
31+ let has_event = text. contains ( "meeting" )
32+ || text. contains ( "appointment" )
33+ || text. contains ( "invite" )
34+ || text. contains ( "calendar" )
35+ || text. contains ( "event" ) ;
36+
37+ let doc_type = if has_receipt {
38+ DocumentType :: Receipt
39+ } else if has_bill {
40+ DocumentType :: Bill
41+ } else if has_payment {
42+ DocumentType :: PaymentConfirmation
43+ } else if has_order {
44+ DocumentType :: Unknown
45+ } else if has_event {
46+ DocumentType :: Unknown
47+ } else {
48+ DocumentType :: Unknown
49+ } ;
50+
51+ LabelDocumentParams {
52+ doc_type,
53+ has_bill,
54+ has_transaction : has_payment || has_receipt,
55+ has_event,
56+ has_order,
57+ }
58+ }
59+
860pub struct TemplateDocumentLabelerAgent {
961 llm_client : Arc < dyn LlmClient > ,
1062 storage : Arc < dyn AgentStorage > ,
@@ -29,6 +81,12 @@ impl TemplateDocumentLabelerAgent {
2981
3082 pub async fn execute ( & self , session_id : i64 ) -> anyhow:: Result < LabelDocumentParams > {
3183 let system_prompt = super :: document_labeler_prompt:: build_system_prompt ( & self . template ) ;
84+ tracing:: info!(
85+ model = %self . model,
86+ prompt_len = system_prompt. len( ) ,
87+ template_len = self . template. len( ) ,
88+ "Document labeler starting"
89+ ) ;
3290
3391 let label_tool = Tool :: from_type :: < LabelDocumentParams > ( )
3492 . name ( "label_document" )
@@ -82,26 +140,42 @@ impl TemplateDocumentLabelerAgent {
82140 } ;
83141
84142 let response = self . llm_client . complete ( request) . await ?;
143+ let assistant_text = response
144+ . content
145+ . iter ( )
146+ . filter_map ( |block| match block {
147+ ContentBlock :: Text { text } => Some ( text. clone ( ) ) ,
148+ _ => None ,
149+ } )
150+ . collect :: < Vec < _ > > ( )
151+ . join ( "\n " ) ;
152+ tracing:: debug!(
153+ model = %self . model,
154+ iteration = iteration + 1 ,
155+ content_blocks = response. content. len( ) ,
156+ tool_calls = response. tool_calls. as_ref( ) . map( |t| t. len( ) ) . unwrap_or( 0 ) ,
157+ assistant_text_preview = %preview_text( & assistant_text, RESPONSE_PREVIEW_CHARS ) ,
158+ "Document labeler response received"
159+ ) ;
85160
86161 self . storage
87162 . create_message ( Message {
88163 id : None ,
89164 session_id,
90165 role : "assistant" . to_string ( ) ,
91- content : response
92- . content
93- . iter ( )
94- . filter_map ( |block| match block {
95- ContentBlock :: Text { text } => Some ( text. clone ( ) ) ,
96- _ => None ,
97- } )
98- . collect :: < Vec < _ > > ( )
99- . join ( "\n " ) ,
166+ content : assistant_text. clone ( ) ,
100167 } )
101168 . await ?;
102169
103170 if let Some ( tool_calls) = response. tool_calls {
104171 for tool_call in tool_calls {
172+ tracing:: debug!(
173+ iteration = iteration + 1 ,
174+ tool_id = %tool_call. id( ) ,
175+ tool_name = %tool_call. name( ) ,
176+ raw_arguments = %tool_call. raw_arguments( ) ,
177+ "Document labeler tool call"
178+ ) ;
105179 if tool_call. name ( ) == "label_document" {
106180 let params: LabelDocumentParams = tool_call. parse_arguments ( ) ?;
107181
@@ -120,6 +194,16 @@ impl TemplateDocumentLabelerAgent {
120194 return Ok ( params) ;
121195 }
122196 }
197+ tracing:: warn!(
198+ iteration = iteration + 1 ,
199+ "Document labeler returned tool calls but none matched label_document"
200+ ) ;
201+ } else {
202+ tracing:: warn!(
203+ iteration = iteration + 1 ,
204+ assistant_text_preview = %preview_text( & assistant_text, RESPONSE_PREVIEW_CHARS ) ,
205+ "Document labeler returned no tool calls"
206+ ) ;
123207 }
124208
125209 self . storage
@@ -133,8 +217,16 @@ impl TemplateDocumentLabelerAgent {
133217 . await ?;
134218 }
135219
136- Err ( anyhow:: anyhow!(
137- "Document labeler did not call label_document after 2 iterations"
138- ) )
220+ let fallback = fallback_label_from_template ( & self . template ) ;
221+ tracing:: warn!(
222+ model = %self . model,
223+ fallback_doc_type = ?fallback. doc_type,
224+ fallback_has_bill = fallback. has_bill,
225+ fallback_has_transaction = fallback. has_transaction,
226+ fallback_has_event = fallback. has_event,
227+ fallback_has_order = fallback. has_order,
228+ "Document labeler did not call label_document after 2 iterations; using heuristic fallback"
229+ ) ;
230+ Ok ( fallback)
139231 }
140232}
0 commit comments