cite-agent 1.3.9__py3-none-any.whl → 1.4.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. cite_agent/__init__.py +13 -13
  2. cite_agent/__version__.py +1 -1
  3. cite_agent/action_first_mode.py +150 -0
  4. cite_agent/adaptive_providers.py +413 -0
  5. cite_agent/archive_api_client.py +186 -0
  6. cite_agent/auth.py +0 -1
  7. cite_agent/auto_expander.py +70 -0
  8. cite_agent/cache.py +379 -0
  9. cite_agent/circuit_breaker.py +370 -0
  10. cite_agent/citation_network.py +377 -0
  11. cite_agent/cli.py +8 -16
  12. cite_agent/cli_conversational.py +113 -3
  13. cite_agent/confidence_calibration.py +381 -0
  14. cite_agent/deduplication.py +325 -0
  15. cite_agent/enhanced_ai_agent.py +689 -371
  16. cite_agent/error_handler.py +228 -0
  17. cite_agent/execution_safety.py +329 -0
  18. cite_agent/full_paper_reader.py +239 -0
  19. cite_agent/observability.py +398 -0
  20. cite_agent/offline_mode.py +348 -0
  21. cite_agent/paper_comparator.py +368 -0
  22. cite_agent/paper_summarizer.py +420 -0
  23. cite_agent/pdf_extractor.py +350 -0
  24. cite_agent/proactive_boundaries.py +266 -0
  25. cite_agent/quality_gate.py +442 -0
  26. cite_agent/request_queue.py +390 -0
  27. cite_agent/response_enhancer.py +257 -0
  28. cite_agent/response_formatter.py +458 -0
  29. cite_agent/response_pipeline.py +295 -0
  30. cite_agent/response_style_enhancer.py +259 -0
  31. cite_agent/self_healing.py +418 -0
  32. cite_agent/similarity_finder.py +524 -0
  33. cite_agent/streaming_ui.py +13 -9
  34. cite_agent/thinking_blocks.py +308 -0
  35. cite_agent/tool_orchestrator.py +416 -0
  36. cite_agent/trend_analyzer.py +540 -0
  37. cite_agent/unpaywall_client.py +226 -0
  38. {cite_agent-1.3.9.dist-info → cite_agent-1.4.3.dist-info}/METADATA +15 -1
  39. cite_agent-1.4.3.dist-info/RECORD +62 -0
  40. cite_agent-1.3.9.dist-info/RECORD +0 -32
  41. {cite_agent-1.3.9.dist-info → cite_agent-1.4.3.dist-info}/WHEEL +0 -0
  42. {cite_agent-1.3.9.dist-info → cite_agent-1.4.3.dist-info}/entry_points.txt +0 -0
  43. {cite_agent-1.3.9.dist-info → cite_agent-1.4.3.dist-info}/licenses/LICENSE +0 -0
  44. {cite_agent-1.3.9.dist-info → cite_agent-1.4.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,295 @@
1
+ """
2
+ Response Pipeline - Comprehensive Quality Processing
3
+ Integrates all quality improvements into a single pipeline
4
+
5
+ This is the "intelligence layer" that was missing
6
+ """
7
+
8
+ import logging
9
+ from typing import Dict, Any, Optional
10
+ from dataclasses import dataclass
11
+
12
+ from .error_handler import GracefulErrorHandler
13
+ from .response_formatter import ResponseFormatter
14
+ from .quality_gate import ResponseQualityGate, QualityAssessment
15
+ from .response_enhancer import ResponseEnhancer
16
+ from .response_style_enhancer import ResponseStyleEnhancer
17
+ from .action_first_mode import ActionFirstMode
18
+ from .auto_expander import AutoExpander
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ @dataclass
24
+ class ProcessedResponse:
25
+ """Result of pipeline processing"""
26
+ final_response: str
27
+ quality_score: float
28
+ improvements_applied: list
29
+ processing_notes: list
30
+
31
+
32
+ class ResponsePipeline:
33
+ """
34
+ End-to-end response processing pipeline
35
+
36
+ Steps:
37
+ 1. Clean any technical errors
38
+ 2. Apply appropriate formatting
39
+ 3. Assess quality
40
+ 4. Improve if needed
41
+ 5. Final safety check
42
+ """
43
+
44
+ @classmethod
45
+ async def process(
46
+ cls,
47
+ raw_response: str,
48
+ query: str,
49
+ context: Dict[str, Any],
50
+ response_type: str = "generic"
51
+ ) -> ProcessedResponse:
52
+ """
53
+ Process a response through the quality pipeline
54
+
55
+ Args:
56
+ raw_response: The initial response from LLM
57
+ query: The original user query
58
+ context: Additional context (tools used, API results, etc.)
59
+ response_type: Type of response (greeting, file_list, data, code, etc.)
60
+
61
+ Returns:
62
+ ProcessedResponse with final polished response and metadata
63
+ """
64
+ improvements = []
65
+ notes = []
66
+
67
+ # Step 1: Clean technical errors (safety first!)
68
+ cleaned_response = GracefulErrorHandler.wrap_response_with_error_handling(raw_response)
69
+
70
+ if cleaned_response != raw_response:
71
+ improvements.append("Removed technical error messages")
72
+ notes.append(f"Cleaned {len(raw_response) - len(cleaned_response)} chars of technical details")
73
+
74
+ # Step 2: Apply appropriate formatting based on response type
75
+ formatted_response = cls._apply_smart_formatting(
76
+ cleaned_response,
77
+ query,
78
+ context,
79
+ response_type
80
+ )
81
+
82
+ if formatted_response != cleaned_response:
83
+ improvements.append("Applied smart formatting")
84
+
85
+ # Step 3: Assess quality
86
+ assessment = ResponseQualityGate.assess(formatted_response, query, context)
87
+
88
+ notes.append(f"Quality score: {assessment.overall_score:.2f}")
89
+ if assessment.issues:
90
+ notes.append(f"Issues: {', '.join(assessment.issues[:3])}")
91
+
92
+ # Step 4: Apply automatic improvements
93
+ if assessment.should_retry or assessment.overall_score < 0.75:
94
+ improved_response = ResponseQualityGate.improve_response(
95
+ formatted_response,
96
+ assessment,
97
+ query
98
+ )
99
+
100
+ if improved_response != formatted_response:
101
+ improvements.append("Applied automatic quality improvements")
102
+ formatted_response = improved_response
103
+
104
+ # Re-assess after improvements
105
+ new_assessment = ResponseQualityGate.assess(improved_response, query, context)
106
+ notes.append(f"Improved quality: {new_assessment.overall_score:.2f}")
107
+ assessment = new_assessment
108
+
109
+ # Step 4.5: ENHANCE to push quality even higher
110
+ if assessment.overall_score < 0.80:
111
+ enhanced_response = ResponseEnhancer.enhance(
112
+ formatted_response,
113
+ query,
114
+ context
115
+ )
116
+
117
+ if enhanced_response != formatted_response:
118
+ improvements.append("Enhanced for higher quality")
119
+ formatted_response = enhanced_response
120
+
121
+ # Re-assess after enhancement
122
+ final_assessment = ResponseQualityGate.assess(enhanced_response, query, context)
123
+ notes.append(f"Enhanced quality: {final_assessment.overall_score:.2f}")
124
+ assessment = final_assessment
125
+
126
+ # Step 4.7: STYLE ENHANCEMENT - Make responses pleasant and stylish
127
+ # This is what makes responses ACTUALLY GOOD, not just functional
128
+ styled_response = ResponseStyleEnhancer.enhance(
129
+ formatted_response,
130
+ query,
131
+ context
132
+ )
133
+
134
+ if styled_response != formatted_response:
135
+ improvements.append("Enhanced style to be pleasant and friendly")
136
+ notes.append("Applied style enhancements: warm, natural")
137
+ formatted_response = styled_response
138
+
139
+ # Step 4.8: ACTION-FIRST MODE - Remove asking phrases, prioritize action over talk
140
+ # User wants agent to DO things, not ask permission
141
+ action_first_response = ActionFirstMode.make_action_first(
142
+ formatted_response,
143
+ query,
144
+ context
145
+ )
146
+
147
+ if action_first_response != formatted_response:
148
+ improvements.append("Transformed to action-first mode")
149
+ notes.append("Removed asking phrases - agent shows results proactively")
150
+ formatted_response = action_first_response
151
+
152
+ # Step 4.9: AUTO-EXPANSION CHECK - Detect if response needs more detail
153
+ # This is a quality check - if it detects expansion needed, it means
154
+ # the LLM didn't follow action-first guidelines properly
155
+ checked_response = AutoExpander.expand(formatted_response, query, context)
156
+ # (This currently just logs warnings if expansion is detected as needed)
157
+
158
+ # Step 5: Final safety check
159
+ final_response = GracefulErrorHandler.wrap_response_with_error_handling(checked_response)
160
+
161
+ return ProcessedResponse(
162
+ final_response=final_response,
163
+ quality_score=assessment.overall_score,
164
+ improvements_applied=improvements,
165
+ processing_notes=notes
166
+ )
167
+
168
+ @classmethod
169
+ def _apply_smart_formatting(
170
+ cls,
171
+ response: str,
172
+ query: str,
173
+ context: Dict[str, Any],
174
+ response_type: str
175
+ ) -> str:
176
+ """
177
+ Apply intelligent formatting based on response type and context
178
+ """
179
+ # Detect response type if not specified
180
+ if response_type == "generic":
181
+ response_type = cls._detect_response_type(response, query, context)
182
+
183
+ # Apply appropriate formatter
184
+ if response_type == "greeting":
185
+ # Greetings should be brief and friendly
186
+ if len(response.split()) > 30:
187
+ return ResponseFormatter.format_greeting(query)
188
+ return response
189
+
190
+ elif response_type == "acknowledgment":
191
+ # Thanks/acknowledgments should be brief
192
+ if len(response.split()) > 20:
193
+ return ResponseFormatter.format_acknowledgment(query)
194
+ return response
195
+
196
+ elif response_type == "file_list":
197
+ # File listings should be structured
198
+ # Extract file paths from response
199
+ import re
200
+ file_pattern = r'[\/\w\-\.]+\.[\w]+'
201
+ files = re.findall(file_pattern, response)
202
+
203
+ if files:
204
+ return ResponseFormatter.format_file_listing(files, query)
205
+ return response
206
+
207
+ elif response_type == "clarification":
208
+ # Clarifications should have bullets
209
+ # Extract options if present
210
+ lines = response.split('\n')
211
+ options = [line.strip('- •*').strip() for line in lines if line.strip().startswith(('-', '•', '*'))]
212
+
213
+ if not options:
214
+ # No bullets found - check for sentence-based options
215
+ sentences = response.split('.')
216
+ if len(sentences) >= 2:
217
+ # Try to extract options from sentences
218
+ options = [s.strip() + '.' for s in sentences[1:4] if len(s.strip()) > 10]
219
+
220
+ if options:
221
+ question = lines[0] if lines else "What would you like to focus on?"
222
+ return ResponseFormatter.format_clarification(question, options)
223
+ return response
224
+
225
+ elif response_type == "code":
226
+ # Code should have structure (summary + code + offer)
227
+ return response # Already should be formatted
228
+
229
+ elif response_type == "shell_output":
230
+ # Shell output should be clean and focused
231
+ output_type = context.get('shell_output_type', 'generic')
232
+ command = context.get('command', '')
233
+ output = context.get('output', response)
234
+
235
+ return ResponseFormatter.format_shell_output(command, output, output_type)
236
+
237
+ # Default: apply progressive disclosure if too long
238
+ elif len(response) > 600:
239
+ return ResponseFormatter.apply_progressive_disclosure(response, "generic", 500)
240
+
241
+ return response
242
+
243
+ @classmethod
244
+ def _detect_response_type(cls, response: str, query: str, context: Dict[str, Any]) -> str:
245
+ """
246
+ Detect what type of response this is
247
+ """
248
+ query_lower = query.lower()
249
+ response_lower = response.lower()
250
+
251
+ # Greeting
252
+ if any(word in query_lower for word in ['hi', 'hey', 'hello']) and len(query.split()) <= 3:
253
+ return "greeting"
254
+
255
+ # Acknowledgment
256
+ if any(word in query_lower for word in ['thanks', 'thank you', 'thx']):
257
+ return "acknowledgment"
258
+
259
+ # File listing
260
+ if ('file' in query_lower or 'directory' in query_lower or 'folder' in query_lower):
261
+ if any(ext in response for ext in ['.py', '.js', '.md', '.txt', '.json', '.csv']):
262
+ return "file_list"
263
+
264
+ # Clarification
265
+ if '?' in response and any(word in response_lower for word in ['which', 'what kind', 'tell me more']):
266
+ return "clarification"
267
+
268
+ # Code
269
+ if '```' in response or 'def ' in response or 'class ' in response:
270
+ return "code"
271
+
272
+ # Shell output
273
+ if context.get('tools_used') and 'shell_execution' in context.get('tools_used', []):
274
+ return "shell_output"
275
+
276
+ return "generic"
277
+
278
+
279
+ # Convenience function
280
+ async def process_response(
281
+ response: str,
282
+ query: str,
283
+ context: Dict[str, Any] = None,
284
+ response_type: str = "generic"
285
+ ) -> str:
286
+ """
287
+ Quick response processing - returns just the final response string
288
+ """
289
+ result = await ResponsePipeline.process(
290
+ response,
291
+ query,
292
+ context or {},
293
+ response_type
294
+ )
295
+ return result.final_response
@@ -0,0 +1,259 @@
1
+ """
2
+ Response Style Enhancer - Make responses PLEASANT and STYLISH
3
+
4
+ Transforms robotic, formal responses into warm, friendly, conversational ones
5
+ Target: 0.80+ style score on all responses
6
+
7
+ Key transformations:
8
+ 1. Warm greetings instead of formal ones
9
+ 2. Natural language instead of robotic phrasing
10
+ 3. Elegant formatting with bullets and structure
11
+ 4. Anticipatory offers to help more
12
+ 5. Personality and friendliness
13
+ """
14
+
15
+ import re
16
+ from typing import Dict, Any, List, Tuple
17
+ import logging
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ class ResponseStyleEnhancer:
23
+ """
24
+ Enhances response style to be pleasant and stylish
25
+
26
+ Focus areas:
27
+ 1. WARMTH - Replace cold/formal with friendly/warm
28
+ 2. NATURAL - Replace robotic with conversational
29
+ 3. ELEGANT - Add beautiful formatting
30
+ 4. ANTICIPATORY - Offer to help with next steps
31
+ 5. PERSONALITY - Make it feel like a smart friend
32
+ """
33
+
34
+ # Formal → Warm transformations
35
+ GREETING_TRANSFORMS = {
36
+ r'^Hello\.\s*How can I assist you today\?': 'Hi there! Ready to help - what can I dig into for you?',
37
+ r'^Hello\.\s*': 'Hi! ',
38
+ r'^Greetings\.\s*': 'Hey there! ',
39
+ r'^Good (morning|afternoon|evening)\.\s*': 'Hi! ',
40
+ r'How may I help you\?': 'What can I help you with?',
41
+ r'How can I assist you\?': 'What would you like to know?',
42
+ r'Is there anything else\?': 'Need anything else?',
43
+ }
44
+
45
+ # Robotic → Natural transformations
46
+ NATURAL_TRANSFORMS = {
47
+ r'I have analyzed': "I've looked at",
48
+ r'I have determined': "I found",
49
+ r'I have located': "I found",
50
+ r'I have identified': "I see",
51
+ r'I have processed': "I've checked",
52
+ r'I have executed': "I ran",
53
+ r'Processing complete': "All done",
54
+ r'Execution successful': "Got it",
55
+ r'Operation completed': "Done",
56
+ r'Please note that': "Note:",
57
+ r'It should be noted': "Keep in mind",
58
+ r'Additionally,': "Also,",
59
+ r'Furthermore,': "Plus,",
60
+ r'However,': "But",
61
+ r'Therefore,': "So",
62
+ r'Subsequently,': "Then",
63
+ r'You are welcome': "Happy to help! Let me know if you need anything else",
64
+ }
65
+
66
+ # Cold → Warm phrases
67
+ WARM_TRANSFORMS = {
68
+ r'You must': 'You should',
69
+ r'You need to': "You'll want to",
70
+ r'It is required': "You'll need to",
71
+ r'It is necessary': "You'll want to",
72
+ r'Error:': 'Hmm,',
73
+ r'Failed to': "Couldn't",
74
+ r'Unable to': "Can't",
75
+ }
76
+
77
+ # Add anticipatory phrases based on context
78
+ ANTICIPATORY_PATTERNS = [
79
+ (r'(I found|Here are|Here\'s)\s+(\d+)\s+(files?|items?|results?)',
80
+ lambda m: f"{m.group(0)}\n\nWant me to show you what's in any of these?"),
81
+ (r'(The|This)\s+(file|code|function)\s+(\w+)',
82
+ lambda m: f"{m.group(0)}. Want me to walk through how it works?"),
83
+ (r'(Here\'s|Here is)\s+(what|how|the)',
84
+ lambda m: f"{m.group(0)}"), # Keep as is, will add offer at end
85
+ ]
86
+
87
+ @classmethod
88
+ def enhance(cls, response: str, query: str, context: Dict[str, Any]) -> str:
89
+ """
90
+ Enhance response style to be pleasant and stylish
91
+
92
+ Args:
93
+ response: Original response
94
+ query: User's query
95
+ context: Context including tools, data, etc.
96
+
97
+ Returns:
98
+ Enhanced stylish response
99
+ """
100
+ if not response or len(response) < 5:
101
+ return response
102
+
103
+ enhanced = response
104
+
105
+ # Step 1: Make greetings warm and friendly
106
+ enhanced = cls._enhance_greetings(enhanced, query)
107
+
108
+ # Step 2: Make language natural instead of robotic
109
+ enhanced = cls._make_natural(enhanced)
110
+
111
+ # Step 3: Add warmth
112
+ enhanced = cls._add_warmth(enhanced)
113
+
114
+ # Step 4: Improve formatting to be elegant
115
+ enhanced = cls._make_elegant(enhanced, context)
116
+
117
+ # Step 5: Add anticipatory offers
118
+ enhanced = cls._add_anticipatory_offers(enhanced, query, context)
119
+
120
+ # Step 6: Add personality touches
121
+ enhanced = cls._add_personality(enhanced, query)
122
+
123
+ return enhanced
124
+
125
+ @classmethod
126
+ def _enhance_greetings(cls, response: str, query: str) -> str:
127
+ """Replace formal greetings with warm friendly ones"""
128
+ # Check if query is a greeting
129
+ query_lower = query.lower().strip()
130
+ is_greeting = any(g in query_lower for g in ['hi', 'hello', 'hey', 'greetings'])
131
+
132
+ if is_greeting and len(query_lower) < 20:
133
+ # Replace formal greeting responses
134
+ for pattern, replacement in cls.GREETING_TRANSFORMS.items():
135
+ response = re.sub(pattern, replacement, response, flags=re.IGNORECASE)
136
+
137
+ return response
138
+
139
+ @classmethod
140
+ def _make_natural(cls, response: str) -> str:
141
+ """Replace robotic phrasing with natural conversation"""
142
+ enhanced = response
143
+
144
+ for pattern, replacement in cls.NATURAL_TRANSFORMS.items():
145
+ enhanced = re.sub(pattern, replacement, enhanced, flags=re.IGNORECASE)
146
+
147
+ return enhanced
148
+
149
+ @classmethod
150
+ def _add_warmth(cls, response: str) -> str:
151
+ """Replace cold/formal phrases with warm ones"""
152
+ enhanced = response
153
+
154
+ for pattern, replacement in cls.WARM_TRANSFORMS.items():
155
+ enhanced = re.sub(pattern, replacement, enhanced, flags=re.IGNORECASE)
156
+
157
+ return enhanced
158
+
159
+ @classmethod
160
+ def _make_elegant(cls, response: str, context: Dict[str, Any]) -> str:
161
+ """
162
+ Add elegant formatting
163
+
164
+ - Convert plain lists to bulleted lists
165
+ - Add proper spacing
166
+ - Use emphasis where appropriate
167
+ """
168
+ # Check if response has lists that aren't bulleted
169
+ lines = response.split('\n')
170
+
171
+ # Detect list patterns like "file1, file2, file3"
172
+ for i, line in enumerate(lines):
173
+ # If line has comma-separated items (3+)
174
+ if line.count(',') >= 2 and len(line) < 200:
175
+ parts = [p.strip() for p in line.split(',')]
176
+
177
+ if len(parts) >= 3:
178
+ # Check if these look like filenames or items
179
+ if any(ext in line for ext in ['.py', '.js', '.md', '.txt', '.json']) or \
180
+ all(len(p) < 50 for p in parts):
181
+ # Convert to bulleted list
182
+ intro = "I found these:" if i == 0 else ""
183
+ bulleted = '\n'.join([f"• {p}" for p in parts])
184
+ lines[i] = f"{intro}\n\n{bulleted}" if intro else bulleted
185
+
186
+ enhanced = '\n'.join(lines)
187
+
188
+ # Add proper paragraph spacing if missing
189
+ if enhanced.count('\n\n') == 0 and len(enhanced) > 200:
190
+ # Split into paragraphs at sentence boundaries
191
+ sentences = re.split(r'(?<=[.!?])\s+', enhanced)
192
+ if len(sentences) >= 3:
193
+ # Group into 2-3 sentence paragraphs
194
+ paragraphs = []
195
+ current = []
196
+ for sent in sentences:
197
+ current.append(sent)
198
+ if len(current) >= 2:
199
+ paragraphs.append(' '.join(current))
200
+ current = []
201
+ if current:
202
+ paragraphs.append(' '.join(current))
203
+
204
+ enhanced = '\n\n'.join(paragraphs)
205
+
206
+ return enhanced
207
+
208
+ @classmethod
209
+ def _add_anticipatory_offers(cls, response: str, query: str, context: Dict[str, Any]) -> str:
210
+ """
211
+ Add anticipatory offers - BUT IN ACTION-FIRST MODE
212
+
213
+ Instead of "Want me to X?", the agent should have ALREADY DONE X
214
+ So we SKIP adding asking phrases in action-first mode
215
+
216
+ DISABLED: User wants action-first, not conversation-first
217
+ """
218
+ # ACTION-FIRST MODE: Don't add asking phrases
219
+ # The agent should have already done the obvious next step
220
+ # If it didn't, that's a different problem to fix
221
+
222
+ return response # Return unchanged - no asking phrases added
223
+
224
+ @classmethod
225
+ def _add_personality(cls, response: str, query: str) -> str:
226
+ """Add personality touches to make it feel like a smart friend"""
227
+ # Check query sentiment
228
+ query_lower = query.lower()
229
+
230
+ # If user said thanks
231
+ if any(word in query_lower for word in ['thanks', 'thank you', 'appreciate']):
232
+ # Make response warmer
233
+ if 'you are welcome' in response.lower() or 'you\'re welcome' in response.lower():
234
+ response = re.sub(
235
+ r"you'?re? welcome\.?",
236
+ "Happy to help! Let me know if you need anything else.",
237
+ response,
238
+ flags=re.IGNORECASE
239
+ )
240
+
241
+ # If user seems excited (exclamation marks)
242
+ if '!' in query and len(query) < 50:
243
+ # Match energy slightly
244
+ if not '!' in response:
245
+ # Add one exclamation at the end if appropriate
246
+ response = response.rstrip('.') + '!'
247
+
248
+ # If response is very short and abrupt, make it friendlier
249
+ if len(response) < 20 and '?' not in response:
250
+ # Add friendly suffix
251
+ if not any(word in response.lower() for word in ['happy', 'glad', 'sure']):
252
+ response += " Let me know if you need anything else!"
253
+
254
+ return response
255
+
256
+
257
+ def enhance_style(response: str, query: str, context: Dict[str, Any] = None) -> str:
258
+ """Convenience function to enhance response style"""
259
+ return ResponseStyleEnhancer.enhance(response, query, context or {})