File size: 11,636 Bytes
401b16c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
import openai
import os
import json
from typing import Dict, Any, Optional, List, Tuple
from enum import Enum
from pydantic import BaseModel
from models import EntityExtraction

class ClarificationStatus(str, Enum):
    COMPLETE = "complete"
    NEEDS_CLARIFICATION = "needs_clarification"
    CANCELLED = "cancelled"

class ClarificationRequest(BaseModel):
    missing_fields: List[str]
    questions: List[str]
    suggested_values: Dict[str, Any] = {}
    explanation: str

class TransactionClarifier:
    def __init__(self, api_key: Optional[str] = None):
        """Initialize OpenAI client for transaction clarification"""
        self.client = openai.OpenAI(
            api_key=api_key or os.getenv('OPENAI_API_KEY')
        )
    
    def analyze_transaction_completeness(self, entities: EntityExtraction) -> Tuple[ClarificationStatus, Optional[ClarificationRequest]]:
        """
        Analyze if a transaction has all necessary information
        
        Args:
            entities: Extracted entities from user input
            
        Returns:
            Tuple of (status, clarification_request)
        """
        
        # Define required and optional fields based on transaction type
        if entities.transaction_type == "purchase":
            required_fields = ["product", "quantity", "supplier", "unit_price"]
            optional_fields = ["total_amount"]
        elif entities.transaction_type == "sale":
            required_fields = ["product", "quantity", "customer", "unit_price"]
            optional_fields = ["total_amount"]
        else:
            return ClarificationStatus.COMPLETE, None
        
        # Check for missing required fields
        missing_fields = []
        entity_dict = entities.dict()
        
        for field in required_fields:
            if not entity_dict.get(field):
                missing_fields.append(field)
        
        # If all required fields are present, transaction is complete
        if not missing_fields:
            return ClarificationStatus.COMPLETE, None
        
        # Generate intelligent clarification request
        clarification = self._generate_clarification_request(entities, missing_fields)
        
        return ClarificationStatus.NEEDS_CLARIFICATION, clarification
    
    def _generate_clarification_request(self, entities: EntityExtraction, missing_fields: List[str]) -> ClarificationRequest:
        """Generate intelligent questions for missing information"""
        
        # Prepare context about what we already know
        known_info = {}
        entity_dict = entities.dict()
        
        for field, value in entity_dict.items():
            if value is not None and field != "notes":
                known_info[field] = value
        
        system_prompt = f"""You are a helpful business assistant helping complete a {entities.transaction_type} transaction.

Generate natural, conversational questions to gather missing information. The user should be able to:
1. Provide the missing information
2. Say "N/A" or "skip" if the information is not available/applicable
3. Ask for suggestions if they're unsure

Create personalized questions based on the context of what we already know.

Return your response in this exact JSON format:
{{
    "questions": ["question1", "question2", ...],
    "suggested_values": {{"field": "suggested_value", ...}},
    "explanation": "Brief explanation of why we need this information"
}}

Missing fields to ask about: {missing_fields}
Transaction type: {entities.transaction_type}
"""

        user_prompt = f"""We're processing a {entities.transaction_type} transaction and need to gather some missing information.

What we already know:
{json.dumps(known_info, indent=2)}

Missing fields: {missing_fields}

Generate friendly, specific questions to gather the missing information. Make suggestions when appropriate."""

        try:
            response = self.client.chat.completions.create(
                model="gpt-4o-mini",
                messages=[
                    {"role": "system", "content": system_prompt},
                    {"role": "user", "content": user_prompt}
                ],
                temperature=0.3,
                max_tokens=400
            )
            
            response_text = response.choices[0].message.content.strip()
            
            try:
                result_dict = json.loads(response_text)
                return ClarificationRequest(
                    missing_fields=missing_fields,
                    questions=result_dict.get("questions", []),
                    suggested_values=result_dict.get("suggested_values", {}),
                    explanation=result_dict.get("explanation", "I need some additional information to complete this transaction.")
                )
            except (json.JSONDecodeError, KeyError) as e:
                # Fallback to simple questions
                return self._generate_fallback_questions(entities, missing_fields)
            
        except Exception as e:
            print(f"Error generating clarification: {e}")
            return self._generate_fallback_questions(entities, missing_fields)
    
    def _generate_fallback_questions(self, entities: EntityExtraction, missing_fields: List[str]) -> ClarificationRequest:
        """Generate fallback questions when LLM fails"""
        
        question_templates = {
            "product": "What product or item is involved in this transaction?",
            "quantity": f"How many units {'were purchased' if entities.transaction_type == 'purchase' else 'were sold'}?",
            "supplier": "Which supplier or vendor is this purchase from?",
            "customer": "Who is the customer for this sale?",
            "unit_price": "What is the price per unit?",
            "total_amount": "What is the total amount for this transaction?"
        }
        
        questions = []
        for field in missing_fields:
            questions.append(question_templates.get(field, f"What is the {field.replace('_', ' ')}?"))
        
        return ClarificationRequest(
            missing_fields=missing_fields,
            questions=questions,
            suggested_values={},
            explanation="I need some additional information to complete this transaction."
        )
    
    def process_clarification_response(self, original_entities: EntityExtraction, 
                                     missing_fields: List[str], 
                                     user_response: str) -> Tuple[EntityExtraction, bool]:
        """
        Process user's response to clarification questions
        
        Args:
            original_entities: Original extracted entities
            missing_fields: Fields we asked about
            user_response: User's response to our questions
            
        Returns:
            Tuple of (updated_entities, is_complete)
        """
        
        system_prompt = f"""You are processing a user's response to clarification questions about a {original_entities.transaction_type} transaction.

Extract the missing information from the user's response. The user may:
1. Provide specific values for the missing fields
2. Say "N/A", "skip", "not applicable", or similar to indicate the field should be null
3. Ask for help or say they don't know

Missing fields we asked about: {missing_fields}

Return a JSON object with the extracted values. Use null for fields that are N/A or skipped.

Example response format:
{{
    "product": "extracted product name",
    "quantity": 10,
    "supplier": null,
    "unit_price": 5.99,
    "interpretation": "Brief explanation of what you extracted"
}}"""

        user_prompt = f"""Original transaction: {original_entities.transaction_type}
Missing fields: {missing_fields}
User's response: "{user_response}"

Extract the values for the missing fields from the user's response."""

        try:
            response = self.client.chat.completions.create(
                model="gpt-4o-mini",
                messages=[
                    {"role": "system", "content": system_prompt},
                    {"role": "user", "content": user_prompt}
                ],
                temperature=0.1,
                max_tokens=300
            )
            
            response_text = response.choices[0].message.content.strip()
            
            try:
                extracted_values = json.loads(response_text)
                
                # Update original entities with extracted values
                updated_entities = self._update_entities(original_entities, extracted_values, missing_fields)
                
                # Check if transaction is now complete
                status, _ = self.analyze_transaction_completeness(updated_entities)
                is_complete = (status == ClarificationStatus.COMPLETE)
                
                return updated_entities, is_complete
                
            except (json.JSONDecodeError, KeyError) as e:
                print(f"Error parsing clarification response: {e}")
                return original_entities, False
                
        except Exception as e:
            print(f"Error processing clarification: {e}")
            return original_entities, False
    
    def _update_entities(self, original_entities: EntityExtraction, 
                        extracted_values: Dict[str, Any], 
                        missing_fields: List[str]) -> EntityExtraction:
        """Update entities with extracted clarification values"""
        
        # Convert to dict for easier manipulation
        entity_dict = original_entities.dict()
        
        # Update with extracted values
        for field in missing_fields:
            if field in extracted_values:
                value = extracted_values[field]
                
                # Handle type conversions
                if field in ["quantity"] and value is not None:
                    try:
                        entity_dict[field] = int(value)
                    except (ValueError, TypeError):
                        entity_dict[field] = None
                elif field in ["unit_price", "total_amount"] and value is not None:
                    try:
                        entity_dict[field] = float(value)
                    except (ValueError, TypeError):
                        entity_dict[field] = None
                else:
                    entity_dict[field] = value
        
        # Recalculate total if we have quantity and unit_price
        if entity_dict.get("quantity") and entity_dict.get("unit_price"):
            entity_dict["total_amount"] = entity_dict["quantity"] * entity_dict["unit_price"]
        
        return EntityExtraction(**entity_dict)
    
    def format_clarification_message(self, clarification: ClarificationRequest) -> str:
        """Format clarification request as a user-friendly message"""
        
        message = f"📝 {clarification.explanation}\n\n"
        
        for i, question in enumerate(clarification.questions, 1):
            message += f"{i}. {question}\n"
        
        # Add suggestions if available
        if clarification.suggested_values:
            message += "\n💡 Suggestions:\n"
            for field, suggestion in clarification.suggested_values.items():
                message += f"   • {field.replace('_', ' ').title()}: {suggestion}\n"
        
        message += "\n✨ You can say 'N/A' or 'skip' for any information that's not available."
        message += "\n📞 Please provide the missing information in your next message."
        
        return message