@@ -35,13 +35,77 @@ function resolveAdapter(config: ExtractorConfig): FormAdapter {
3535 }
3636}
3737
38- const SYSTEM_PROMPT =
38+ const BASE_SYSTEM_PROMPT =
3939 'You are a document data extraction assistant. ' +
4040 'Extract field values from the scanned form image. ' +
4141 'Return valid JSON only, matching the specified field names exactly. ' +
4242 'For each field, include your confidence (0.0-1.0) in a parallel "_confidence" object. ' +
4343 'If a field is not visible or unreadable, use null.' ;
4444
45+ interface FormElementLike {
46+ type ?: string ;
47+ name ?: string ;
48+ title ?: string ;
49+ elements ?: FormElementLike [ ] ;
50+ templateElements ?: FormElementLike [ ] ;
51+ }
52+
53+ interface SignatureFieldDescriptor {
54+ name : string ;
55+ label : string ;
56+ }
57+
58+ function collectFormElements ( elements : FormElementLike [ ] | undefined ) : FormElementLike [ ] {
59+ if ( ! elements || elements . length === 0 ) return [ ] ;
60+
61+ const result : FormElementLike [ ] = [ ] ;
62+ for ( const el of elements ) {
63+ result . push ( el ) ;
64+ if ( el . elements && el . elements . length > 0 ) {
65+ result . push ( ...collectFormElements ( el . elements ) ) ;
66+ }
67+ if ( el . templateElements && el . templateElements . length > 0 ) {
68+ result . push ( ...collectFormElements ( el . templateElements ) ) ;
69+ }
70+ }
71+
72+ return result ;
73+ }
74+
75+ function getSignaturePadFields ( formDefinition : Record < string , unknown > ) : SignatureFieldDescriptor [ ] {
76+ const pages = formDefinition . pages as Array < { elements ?: FormElementLike [ ] } > | undefined ;
77+ if ( ! pages || pages . length === 0 ) return [ ] ;
78+
79+ const fields : SignatureFieldDescriptor [ ] = [ ] ;
80+ for ( const page of pages ) {
81+ const elements = collectFormElements ( page . elements ) ;
82+ for ( const el of elements ) {
83+ if ( el . type !== 'signaturepad' || ! el . name ) continue ;
84+ fields . push ( { name : el . name , label : el . title ?? el . name } ) ;
85+ }
86+ }
87+
88+ return fields ;
89+ }
90+
91+ function buildSystemPrompt ( formDefinition : Record < string , unknown > ) : string {
92+ const signatureFields = getSignaturePadFields ( formDefinition ) ;
93+ if ( signatureFields . length === 0 ) {
94+ return BASE_SYSTEM_PROMPT ;
95+ }
96+
97+ const fieldLabels = signatureFields
98+ . map ( ( f ) => `"${ f . name } " (label: "${ f . label } ")` )
99+ . join ( ', ' ) ;
100+
101+ return (
102+ BASE_SYSTEM_PROMPT +
103+ ' For signaturepad fields, use field names and labels to identify the signature areas and extract the actual handwritten signature marks.' +
104+ ' Return each signature as a Base64-encoded image string without markdown, code fences, or data URL prefixes.' +
105+ ` Signature fields: ${ fieldLabels } .`
106+ ) ;
107+ }
108+
45109/**
46110 * Extracts JSON content from an LLM response that may contain markdown
47111 * code fences and/or preamble/postamble text.
@@ -160,6 +224,7 @@ export function createExtractor(config: ExtractorConfig) {
160224 // 3. Generate prompt
161225 const fieldPrompt = adapter . toPrompt ( input . formDefinition ) ;
162226 const basePrompt = fieldPrompt ;
227+ const systemPrompt = buildSystemPrompt ( input . formDefinition ) ;
163228
164229 // 4. Get output schema for validation
165230 const outputSchema = adapter . toOutputSchema ( input . formDefinition ) ;
@@ -177,7 +242,7 @@ export function createExtractor(config: ExtractorConfig) {
177242 const response = await provider . extractFromImage ( {
178243 image : imageBase64 ,
179244 prompt,
180- systemPrompt : SYSTEM_PROMPT ,
245+ systemPrompt,
181246 } ) ;
182247
183248 const rawContent = response . content ;
0 commit comments