jzou19950715 commited on
Commit
af26e7b
·
verified ·
1 Parent(s): 0c5e004

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +105 -533
app.py CHANGED
@@ -1,530 +1,131 @@
1
  import json
2
  import logging
3
- import os
4
  from datetime import datetime
5
- from typing import Dict, List, Optional, Any, Tuple
6
- from dataclasses import dataclass, field
7
-
8
- import openai
9
  import gradio as gr
 
10
 
11
- # Set up logging
12
- logging.basicConfig(
13
- level=logging.INFO,
14
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
15
- handlers=[
16
- logging.StreamHandler(),
17
- logging.FileHandler('loss_dog.log')
18
- ]
19
- )
20
  logger = logging.getLogger(__name__)
21
 
22
- # System Prompts
23
- CONVERSATION_PROMPT = '''
24
- <?xml version="1.0" encoding="UTF-8"?>
25
- <system_prompt>
26
- <assistant_identity>
27
- <name>LOSS DOG</name>
28
- <role>Digital Profile Assistant</role>
29
- <purpose>To help users build comprehensive professional and digital presence profiles through natural, comfortable conversation</purpose>
30
- </assistant_identity>
31
-
32
- <core_personality>
33
- <traits>
34
- <trait>Friendly and approachable</trait>
35
- <trait>Attentive listener</trait>
36
- <trait>Professionally insightful</trait>
37
- <trait>Respectful of boundaries</trait>
38
- <trait>Naturally curious</trait>
39
- </traits>
40
- <voice_characteristics>
41
- <characteristic>Warm and encouraging</characteristic>
42
- <characteristic>Clear and professional</characteristic>
43
- <characteristic>Adaptable to user's style</characteristic>
44
- </voice_characteristics>
45
- </core_personality>
46
-
47
- <information_categories>
48
- <category name="professional_background">
49
- <fields>
50
- <field>Current role and responsibilities</field>
51
- <field>Career history and progression</field>
52
- <field>Notable achievements</field>
53
- <field>Professional goals</field>
54
- </fields>
55
- <approach>
56
- <step>Begin with current role</step>
57
- <step>Explore career journey naturally</step>
58
- <step>Discuss achievements organically</step>
59
- <step>Note specific metrics when shared</step>
60
- </approach>
61
- </category>
62
-
63
- <category name="education_training">
64
- <fields>
65
- <field>Formal education</field>
66
- <field>Certifications</field>
67
- <field>Specialized training</field>
68
- <field>Continuous learning</field>
69
- </fields>
70
- <data_points>
71
- <point>Institution names</point>
72
- <point>Degree details</point>
73
- <point>Time periods</point>
74
- <point>Special achievements</point>
75
- </data_points>
76
- </category>
77
-
78
- <category name="skills_expertise">
79
- <fields>
80
- <field>Technical skills</field>
81
- <field>Soft skills</field>
82
- <field>Tools and technologies</field>
83
- <field>Domain expertise</field>
84
- </fields>
85
- <metrics>
86
- <metric>Proficiency levels</metric>
87
- <metric>Years of experience</metric>
88
- <metric>Project applications</metric>
89
- </metrics>
90
- </category>
91
-
92
- <category name="digital_presence">
93
- <fields>
94
- <field>Social media impact</field>
95
- <field>Content creation</field>
96
- <field>Community engagement</field>
97
- <field>Digital assets</field>
98
- </fields>
99
- <metrics>
100
- <metric>Follower counts</metric>
101
- <metric>Engagement rates</metric>
102
- <metric>Content reach</metric>
103
- <metric>Portfolio value</metric>
104
- </metrics>
105
- </category>
106
-
107
- <category name="projects_contributions">
108
- <fields>
109
- <field>Major projects</field>
110
- <field>Open source contributions</field>
111
- <field>Creative works</field>
112
- <field>Impact metrics</field>
113
- </fields>
114
- <data_collection>
115
- <point>Project descriptions</point>
116
- <point>Role and responsibilities</point>
117
- <point>Technologies used</point>
118
- <point>Measurable outcomes</point>
119
- </data_collection>
120
- </category>
121
- </information_categories>
122
-
123
- <conversation_strategies>
124
- <engagement_patterns>
125
- <pattern type="initial_contact">
126
- <approach>Open with friendly, professional greeting</approach>
127
- <focus>Establish comfortable rapport</focus>
128
- <goal>Begin natural information gathering</goal>
129
- </pattern>
130
-
131
- <pattern type="information_gathering">
132
- <approach>Use natural conversation flow</approach>
133
- <focus>Follow user's narrative</focus>
134
- <goal>Collect relevant details organically</goal>
135
- </pattern>
136
-
137
- <pattern type="follow_up">
138
- <approach>Ask relevant, contextual questions</approach>
139
- <focus>Deepen understanding of shared information</focus>
140
- <goal>Gather additional context and details</goal>
141
- </pattern>
142
- </engagement_patterns>
143
-
144
- <response_handling>
145
- <scenario type="shared_information">
146
- <action>Acknowledge and validate</action>
147
- <action>Note key points</action>
148
- <action>Ask natural follow-up if appropriate</action>
149
- </scenario>
150
-
151
- <scenario type="hesitation">
152
- <action>Respect boundaries</action>
153
- <action>Shift to comfortable topics</action>
154
- <action>Leave door open for later sharing</action>
155
- </scenario>
156
-
157
- <scenario type="completion">
158
- <action>Summarize collected information</action>
159
- <action>Verify accuracy</action>
160
- <action>Transition smoothly to next topic</action>
161
- </scenario>
162
- </response_handling>
163
- </conversation_strategies>
164
-
165
- <output_guidelines>
166
- <quality_standards>
167
- <standard>Professional language</standard>
168
- <standard>Accurate representation</standard>
169
- <standard>Structured organization</standard>
170
- <standard>Clear categorization</standard>
171
- </quality_standards>
172
- </output_guidelines>
173
-
174
- <ethics_guidelines>
175
- <principle>Respect user privacy</principle>
176
- <principle>Never pressure for information</principle>
177
- <principle>Maintain professional boundaries</principle>
178
- <principle>Ensure data accuracy</principle>
179
- </ethics_guidelines>
180
- </system_prompt>
181
- '''
182
-
183
- EXTRACTION_PROMPT = '''
184
- <?xml version="1.0" encoding="UTF-8"?>
185
- <system_prompt>
186
- <assistant_identity>
187
- <name>LOSS DOG - Information Processor</name>
188
- <role>Conversation Analyzer and Information Extractor</role>
189
- <purpose>Process conversation history to extract and structure professional profile information</purpose>
190
- </assistant_identity>
191
-
192
- <task_description>
193
- Your task is to analyze the provided conversation history and extract structured profile information:
194
- 1. Process natural conversation into structured data
195
- 2. Identify and categorize relevant information
196
- 3. Make intelligent inferences when appropriate
197
- 4. Maintain high accuracy and data quality
198
- 5. Handle messy or non-linear conversation flows
199
- </task_description>
200
-
201
- <extraction_guidelines>
202
- <primary_objective>
203
- Convert conversation data into clean, structured JSON that matches these categories:
204
- - personal_info (name, contact, location)
205
- - education (degree, institution, field, dates)
206
- - work_experience (title, company, duration, responsibilities)
207
- - skills (technical, soft_skills, tools)
208
- - achievements (awards, publications, projects)
209
- - digital_presence (social_media, content_creation, community_impact)
210
- </primary_objective>
211
-
212
- <processing_rules>
213
- <rule>Focus on factual information over casual conversation</rule>
214
- <rule>Handle partial or incomplete information gracefully</rule>
215
- <rule>Use context to resolve ambiguities</rule>
216
- <rule>Track confidence levels for all extracted data</rule>
217
- <rule>Mark any inferred information clearly</rule>
218
- <rule>Maintain source context for future reference</rule>
219
- </processing_rules>
220
-
221
- <data_handling>
222
- <instruction>For each piece of extracted information, provide:
223
- - Category classification
224
- - Confidence score (0.0-1.0)
225
- - Source context (relevant conversation snippet)
226
- - List of any inferred fields
227
- - Structured data in appropriate format
228
- </instruction>
229
- </data_handling>
230
- </extraction_guidelines>
231
-
232
- <output_format>
233
- <format_rules>
234
- <rule>Return JSON object with categorized sections</rule>
235
- <rule>Include confidence scores (0.0-1.0) for each section</rule>
236
- <rule>Mark inferred information with "inferred": true</rule>
237
- <rule>Include source context for traceability</rule>
238
- <rule>Use consistent date formats (YYYY-MM-DD where possible)</rule>
239
- </format_rules>
240
-
241
- <structure>
242
- {
243
- "category_name": {
244
- "data": {
245
- // Structured data specific to category
246
- },
247
- "confidence": float,
248
- "source_context": string,
249
- "inferred_fields": [string],
250
- "metadata": {
251
- // Additional category-specific metadata
252
- }
253
- }
254
- }
255
- </structure>
256
- </output_format>
257
-
258
- <quality_controls>
259
- <validations>
260
- <validation>Check date consistency and sequences</validation>
261
- <validation>Verify logical relationships between entries</validation>
262
- <validation>Ensure required fields are present or marked missing</validation>
263
- <validation>Confirm confidence scores are justified</validation>
264
- </validations>
265
-
266
- <error_handling>
267
- <case>Handle conflicting information by preferring most recent/confident</case>
268
- <case>Mark ambiguous information with multiple possible interpretations</case>
269
- <case>Skip unverifiable information rather than making weak inferences</case>
270
- </error_handling>
271
- </quality_controls>
272
- </system_prompt>
273
- '''
274
-
275
- @dataclass
276
- class ProfileSection:
277
- """Represents a section of the professional profile with structured data."""
278
- category: str
279
- data: Dict[str, Any]
280
- confidence: float
281
- source_context: str
282
- inferred_fields: List[str] = field(default_factory=list)
283
- last_updated: datetime = field(default_factory=datetime.now)
284
- metadata: Dict[str, Any] = field(default_factory=dict)
285
-
286
- @dataclass
287
- class ConversationState:
288
- """Tracks the state of the information gathering conversation."""
289
- collected_sections: Dict[str, ProfileSection] = field(default_factory=dict)
290
- missing_information: List[str] = field(default_factory=list)
291
- conversation_history: List[Dict[str, str]] = field(default_factory=list)
292
- completion_status: Dict[str, float] = field(default_factory=dict)
293
- current_focus: Optional[str] = None
294
- extraction_history: List[Dict[str, Any]] = field(default_factory=list)
295
 
296
  class ProfileBuilder:
297
- """
298
- Core class for building professional profiles through conversation and extraction.
299
- Implements two-phase approach:
300
- 1. Interactive conversation for information gathering
301
- 2. Structured information extraction and processing
302
- """
303
-
304
  def __init__(self):
305
- """Initialize the ProfileBuilder with default settings."""
306
- self.state = ConversationState()
307
- self.required_sections = {
308
- "personal_info": ["name", "contact", "location"],
309
- "education": ["degree", "institution", "field", "dates"],
310
- "work_experience": ["title", "company", "duration", "responsibilities"],
311
- "skills": ["technical", "soft_skills", "tools"],
312
- "achievements": ["awards", "publications", "projects"],
313
- "digital_presence": ["platforms", "metrics", "content"]
314
- }
315
- self._api_key: Optional[str] = None
316
- self._setup_logging()
317
 
318
- def _setup_logging(self) -> None:
319
- """Configure logging for the profile builder."""
320
- self.logger = logging.getLogger(__name__)
321
- handler = logging.FileHandler('profile_builder.log')
322
- handler.setFormatter(logging.Formatter(
323
- '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
324
- ))
325
- self.logger.addHandler(handler)
326
- self.logger.setLevel(logging.INFO)
 
 
 
 
 
 
 
 
327
 
328
- def _initialize_client(self, api_key: str) -> None:
329
- """Initialize OpenAI client with API key."""
330
- try:
331
- if not api_key.startswith("sk-"):
332
- raise ValueError("Invalid API key format")
333
- self._api_key = api_key
334
- openai.api_key = api_key
335
- self.logger.info("OpenAI client initialized successfully")
336
  except Exception as e:
337
- self.logger.error(f"Failed to initialize OpenAI client: {str(e)}")
338
- raise
339
 
340
- async def _extract_information(self) -> Dict[str, ProfileSection]:
341
- """Extract structured information from the conversation history."""
342
  try:
343
- # Prepare conversation context
344
  conversation_text = "\n".join(
345
  f"{msg['role']}: {msg['content']}"
346
- for msg in self.state.conversation_history
347
  )
348
 
349
- messages = [
350
- {"role": "system", "content": EXTRACTION_PROMPT},
351
- {"role": "user", "content": f"Extract professional profile information from this conversation:\n\n{conversation_text}"}
352
- ]
353
-
354
  response = await openai.ChatCompletion.acreate(
355
  model="gpt-4o-mini",
356
- messages=messages,
357
- temperature=0.3,
358
- max_tokens=2000
 
 
359
  )
360
 
361
- # Parse and validate the response
362
- extracted_data = self._parse_extraction_response(response.choices[0].message.content)
363
-
364
- # Convert to ProfileSection objects
365
- sections = {}
366
- for category, data in extracted_data.items():
367
- sections[category] = ProfileSection(
368
- category=category,
369
- data=data.get("data", {}),
370
- confidence=data.get("confidence", 0.0),
371
- source_context=data.get("source_context", ""),
372
- inferred_fields=data.get("inferred_fields", []),
373
- metadata=data.get("metadata", {})
374
- )
375
-
376
- # Log extraction results
377
- self.logger.info(f"Successfully extracted information for {len(sections)} sections")
378
- return sections
379
-
380
- except Exception as e:
381
- self.logger.error(f"Error in extraction phase: {str(e)}")
382
- raise
383
-
384
- def _parse_extraction_response(self, response_text: str) -> Dict[str, Any]:
385
- """Parse and validate the extraction response."""
386
- try:
387
- extracted_data = json.loads(response_text)
388
- self._validate_extracted_data(extracted_data)
389
- return extracted_data
390
- except json.JSONDecodeError as e:
391
- self.logger.error(f"Failed to parse extraction response: {str(e)}")
392
- return {}
393
- except Exception as e:
394
- self.logger.error(f"Error processing extraction response: {str(e)}")
395
- return {}
396
-
397
- def _validate_extracted_data(self, data: Dict[str, Any]) -> None:
398
- """Validate the structure and content of extracted data."""
399
- required_keys = ["data", "confidence", "source_context"]
400
- for category, section in data.items():
401
- missing_keys = [key for key in required_keys if key not in section]
402
- if missing_keys:
403
- self.logger.warning(f"Missing required keys {missing_keys} in category {category}")
404
- raise ValueError(f"Invalid data structure for category {category}")
405
-
406
- def _update_completion_status(self) -> None:
407
- """Update the completion status based on collected information."""
408
- status = {}
409
- for section, required_fields in self.required_sections.items():
410
- if section in self.state.collected_sections:
411
- profile_section = self.state.collected_sections[section]
412
- fields_present = sum(
413
- 1 for field in required_fields
414
- if field in profile_section.data
415
- )
416
- confidence_factor = profile_section.confidence
417
- status[section] = (fields_present / len(required_fields)) * confidence_factor
418
- else:
419
- status[section] = 0.0
420
-
421
- self.state.completion_status = status
422
- self.logger.info(f"Updated completion status: {status}")
423
-
424
- def _get_missing_information(self) -> List[str]:
425
- """Identify missing required information."""
426
- missing = []
427
- for section, required_fields in self.required_sections.items():
428
- if section not in self.state.collected_sections:
429
- missing.extend([f"{section}.{field}" for field in required_fields])
430
- else:
431
- profile_section = self.state.collected_sections[section]
432
- missing.extend([
433
- f"{section}.{field}"
434
- for field in required_fields
435
- if field not in profile_section.data
436
- ])
437
- return missing
438
-
439
- async def process_message(self, message: str, api_key: str) -> Dict[str, Any]:
440
- """Process a user message through both conversation and extraction phases."""
441
- if not self._api_key:
442
- self._initialize_client(api_key)
443
-
444
- try:
445
- # Phase 1: Conversation
446
- self.state.conversation_history.append({"role": "user", "content": message})
447
- ai_response = await self._get_conversation_response(message)
448
- self.state.conversation_history.append({"role": "assistant", "content": ai_response})
449
 
450
- # Phase 2: Information Extraction
451
- extracted_sections = await self._extract_information()
452
-
453
- # Update state with new information
454
- self.state.collected_sections.update(extracted_sections)
455
- self._update_completion_status()
456
-
457
- # Track extraction history
458
- self.state.extraction_history.append({
459
- "timestamp": datetime.now().isoformat(),
460
- "sections_extracted": list(extracted_sections.keys())
461
- })
462
-
463
- return {
464
- "response": ai_response,
465
- "extracted_sections": {
466
- category: {
467
- "data": section.data,
468
- "confidence": section.confidence,
469
- "inferred_fields": section.inferred_fields
470
- }
471
- for category, section in extracted_sections.items()
472
- },
473
- "completion_status": self.state.completion_status,
474
- "missing_information": self._get_missing_information()
475
- }
476
-
477
- except Exception as e:
478
- self.logger.error(f"Error processing message: {str(e)}")
479
- return {
480
- "error": str(e),
481
- "completion_status": self.state.completion_status
482
- }
483
-
484
- def generate_profile(self) -> Dict[str, Any]:
485
- """Generate the final structured profile with all collected information."""
486
- try:
487
  profile = {
488
- "profile_data": {
489
- category: {
490
- "data": section.data,
491
- "confidence": section.confidence,
492
- "inferred_fields": section.inferred_fields,
493
- "metadata": section.metadata
494
- }
495
- for category, section in self.state.collected_sections.items()
496
- },
497
  "metadata": {
498
  "generated_at": datetime.now().isoformat(),
499
- "completion_status": self.state.completion_status,
500
- "missing_information": self._get_missing_information(),
501
- "conversation_length": len(self.state.conversation_history),
502
- "extraction_history": self.state.extraction_history
503
  }
504
  }
505
 
506
- # Save profile to file
507
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
508
  filename = f"profile_{timestamp}.json"
509
  with open(filename, 'w', encoding='utf-8') as f:
510
- json.dump(profile, f, indent=2, ensure_ascii=False)
511
 
512
- self.logger.info(f"Generated profile saved to {filename}")
513
  return {
514
  "profile": profile,
515
- "filename": filename,
516
- "status": "success"
517
  }
518
 
519
  except Exception as e:
520
- self.logger.error(f"Error generating profile: {str(e)}")
521
- return {
522
- "error": str(e),
523
- "status": "error"
524
- }
525
 
526
- def create_gradio_interface() -> gr.Blocks:
527
- """Create the Gradio interface for the profile builder."""
528
  builder = ProfileBuilder()
529
 
530
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
@@ -535,78 +136,53 @@ def create_gradio_interface() -> gr.Blocks:
535
  api_key = gr.Textbox(
536
  label="OpenAI API Key",
537
  type="password",
538
- placeholder="Enter your OpenAI API key (sk-...)"
539
  )
540
 
541
- chatbot = gr.Chatbot(
542
- label="Conversation",
543
- height=400
544
- )
545
 
546
  with gr.Row():
547
  msg = gr.Textbox(
548
  label="Message",
549
  placeholder="Chat with LOSS DOG..."
550
  )
551
- send = gr.Button("Send", variant="primary")
552
 
553
  with gr.Column(scale=1):
554
- with gr.Tabs():
555
- with gr.Tab("Extracted Info"):
556
- extracted_info = gr.JSON(
557
- label="Extracted Information",
558
- show_label=True
559
- )
560
- with gr.Tab("Progress"):
561
- completion = gr.JSON(
562
- label="Completion Status",
563
- show_label=True
564
- )
565
- missing = gr.JSON(
566
- label="Missing Information",
567
- show_label=True
568
- )
569
-
570
- generate_btn = gr.Button("Generate Profile", variant="secondary")
571
  profile_output = gr.JSON(label="Generated Profile")
572
  download_btn = gr.File(label="Download Profile")
573
 
574
  # Event handlers
575
- async def on_message(message: str, history: List[List[str]], key: str) -> Tuple[Any, Any, Any, Any]:
576
  if not message.strip():
577
- return history, None, None, None
578
 
579
  result = await builder.process_message(message, key)
580
 
581
  if "error" in result:
582
- return history, None, None, {"error": result["error"]}
583
 
584
  history = history + [[message, result["response"]]]
585
-
586
- return (
587
- history,
588
- result["extracted_sections"],
589
- result["completion_status"],
590
- result["missing_information"]
591
- )
592
 
593
- def on_generate() -> Tuple[Dict[str, Any], str]:
594
- result = builder.generate_profile()
595
- if result["status"] == "success":
596
- return result["profile"], result["filename"]
597
- return {"error": result["error"]}, None
598
 
599
  # Bind events
600
  msg.submit(
601
  on_message,
602
  inputs=[msg, chatbot, api_key],
603
- outputs=[chatbot, extracted_info, completion, missing]
604
- ).then(lambda: "", None, msg) # Clear message box after sending
605
 
606
  send.click(
607
  on_message,
608
  inputs=[msg, chatbot, api_key],
609
- outputs=[chatbot, extracted_info, completion, missing]
610
  ).then(lambda: "", None, msg)
611
 
612
  generate_btn.click(
@@ -618,8 +194,4 @@ def create_gradio_interface() -> gr.Blocks:
618
 
619
  if __name__ == "__main__":
620
  demo = create_gradio_interface()
621
- demo.launch(
622
- server_name="0.0.0.0",
623
- server_port=7860,
624
- share=True
625
- )
 
1
  import json
2
  import logging
 
3
  from datetime import datetime
4
+ from typing import Dict, List, Optional, Any
 
 
 
5
  import gradio as gr
6
+ import openai
7
 
8
+ # Configure logging
9
+ logging.basicConfig(level=logging.INFO)
 
 
 
 
 
 
 
10
  logger = logging.getLogger(__name__)
11
 
12
+ # System prompts
13
+ CONVERSATION_PROMPT = """You are LOSS DOG, a professional profile builder. Your goal is to have natural conversations
14
+ with users to gather information about their professional background across 9 categories:
15
+
16
+ 1. Work History & Experience
17
+ 2. Salary & Compensation
18
+ 3. Skills & Certifications
19
+ 4. Education & Learning
20
+ 5. Personal Branding & Online Presence
21
+ 6. Achievements & Awards
22
+ 7. Social Proof & Networking
23
+ 8. Project Contributions & Leadership
24
+ 9. Work Performance & Impact Metrics
25
+
26
+ Be friendly and conversational. Ask follow-up questions naturally. When appropriate, guide users to share more details
27
+ but respect their boundaries. Once you believe you have gathered sufficient information (or if the user indicates they
28
+ have nothing more to share), let them know they can click 'Generate Profile' to proceed.
29
+ """
30
+
31
+ EXTRACTION_PROMPT = """You are LOSS DOG's data processing system. Analyze the provided conversation and extract
32
+ structured information into the following categories:
33
+
34
+ 1. Work History & Experience: Job titles, companies, industries, locations, adaptability, promotions
35
+ 2. Salary & Compensation: Base salary, bonuses, equity, benefits (if shared)
36
+ 3. Skills & Certifications: Technical skills, languages, certifications, licenses
37
+ 4. Education & Learning: Degrees, institutions, courses, research
38
+ 5. Personal Branding: Online presence, portfolio, blockchain projects, social media
39
+ 6. Achievements & Awards: Industry recognition, hackathons, creative projects
40
+ 7. Social Proof: Mentors, references, memberships, conferences
41
+ 8. Project Contributions: Major projects, open-source, patents, impact
42
+ 9. Performance Metrics: KPIs, revenue impact, growth metrics
43
+
44
+ Format the output as clean, structured JSON. Include confidence scores for each extracted piece of information.
45
+ Mark any inferred information clearly."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
  class ProfileBuilder:
 
 
 
 
 
 
 
48
  def __init__(self):
49
+ self.conversation_history = []
50
+ self.api_key = None
51
+
52
+ async def process_message(self, message: str, api_key: str) -> Dict[str, Any]:
53
+ """Process a user message through conversation phase."""
54
+ try:
55
+ if not self.api_key:
56
+ self.api_key = api_key
57
+ openai.api_key = api_key
 
 
 
58
 
59
+ # Add message to history
60
+ self.conversation_history.append({"role": "user", "content": message})
61
+
62
+ # Get AI response
63
+ response = await openai.ChatCompletion.acreate(
64
+ model="gpt-4o-mini",
65
+ messages=[
66
+ {"role": "system", "content": CONVERSATION_PROMPT},
67
+ *self.conversation_history
68
+ ],
69
+ temperature=0.7
70
+ )
71
+
72
+ ai_message = response.choices[0].message.content
73
+ self.conversation_history.append({"role": "assistant", "content": ai_message})
74
+
75
+ return {"response": ai_message}
76
 
 
 
 
 
 
 
 
 
77
  except Exception as e:
78
+ logger.error(f"Error processing message: {str(e)}")
79
+ return {"error": str(e)}
80
 
81
+ async def generate_profile(self) -> Dict[str, Any]:
82
+ """Process conversation history into structured profile."""
83
  try:
84
+ # Convert conversation history to text
85
  conversation_text = "\n".join(
86
  f"{msg['role']}: {msg['content']}"
87
+ for msg in self.conversation_history
88
  )
89
 
90
+ # Extract structured information
 
 
 
 
91
  response = await openai.ChatCompletion.acreate(
92
  model="gpt-4o-mini",
93
+ messages=[
94
+ {"role": "system", "content": EXTRACTION_PROMPT},
95
+ {"role": "user", "content": f"Extract profile information from this conversation:\n\n{conversation_text}"}
96
+ ],
97
+ temperature=0.3
98
  )
99
 
100
+ # Parse the structured response
101
+ profile_data = json.loads(response.choices[0].message.content)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
 
103
+ # Add metadata
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  profile = {
105
+ "profile_data": profile_data,
 
 
 
 
 
 
 
 
106
  "metadata": {
107
  "generated_at": datetime.now().isoformat(),
108
+ "conversation_length": len(self.conversation_history)
 
 
 
109
  }
110
  }
111
 
112
+ # Save to file
113
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
114
  filename = f"profile_{timestamp}.json"
115
  with open(filename, 'w', encoding='utf-8') as f:
116
+ json.dump(profile, f, indent=2)
117
 
 
118
  return {
119
  "profile": profile,
120
+ "filename": filename
 
121
  }
122
 
123
  except Exception as e:
124
+ logger.error(f"Error generating profile: {str(e)}")
125
+ return {"error": str(e)}
 
 
 
126
 
127
+ def create_gradio_interface():
128
+ """Create the Gradio interface."""
129
  builder = ProfileBuilder()
130
 
131
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
 
136
  api_key = gr.Textbox(
137
  label="OpenAI API Key",
138
  type="password",
139
+ placeholder="Enter your OpenAI API key"
140
  )
141
 
142
+ chatbot = gr.Chatbot(label="Conversation")
 
 
 
143
 
144
  with gr.Row():
145
  msg = gr.Textbox(
146
  label="Message",
147
  placeholder="Chat with LOSS DOG..."
148
  )
149
+ send = gr.Button("Send")
150
 
151
  with gr.Column(scale=1):
152
+ generate_btn = gr.Button("Generate Profile")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  profile_output = gr.JSON(label="Generated Profile")
154
  download_btn = gr.File(label="Download Profile")
155
 
156
  # Event handlers
157
+ async def on_message(message: str, history: List[List[str]], key: str):
158
  if not message.strip():
159
+ return history, None
160
 
161
  result = await builder.process_message(message, key)
162
 
163
  if "error" in result:
164
+ return history, {"error": result["error"]}
165
 
166
  history = history + [[message, result["response"]]]
167
+ return history, None
 
 
 
 
 
 
168
 
169
+ async def on_generate():
170
+ result = await builder.generate_profile()
171
+ if "error" in result:
172
+ return {"error": result["error"]}, None
173
+ return result["profile"], result["filename"]
174
 
175
  # Bind events
176
  msg.submit(
177
  on_message,
178
  inputs=[msg, chatbot, api_key],
179
+ outputs=[chatbot, profile_output]
180
+ ).then(lambda: "", None, msg)
181
 
182
  send.click(
183
  on_message,
184
  inputs=[msg, chatbot, api_key],
185
+ outputs=[chatbot, profile_output]
186
  ).then(lambda: "", None, msg)
187
 
188
  generate_btn.click(
 
194
 
195
  if __name__ == "__main__":
196
  demo = create_gradio_interface()
197
+ demo.launch(server_name="0.0.0.0", server_port=7860)