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,308 @@
1
+ """
2
+ Thinking Blocks - Visible Reasoning Process
3
+ Shows users the internal reasoning like Claude does
4
+
5
+ This is the "why" behind responses, not just the "what"
6
+ """
7
+
8
+ import logging
9
+ from typing import Dict, Any, List, Optional
10
+ from dataclasses import dataclass
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ @dataclass
16
+ class ThinkingStep:
17
+ """A single step in the reasoning process"""
18
+ step_number: int
19
+ thought: str
20
+ reasoning_type: str # analysis, planning, verification, decision
21
+ confidence: float # 0.0-1.0
22
+
23
+
24
+ class ThinkingBlockGenerator:
25
+ """
26
+ Generates visible thinking blocks to show reasoning process
27
+
28
+ Similar to Claude's <thinking> tags, but formatted for terminal output
29
+ """
30
+
31
+ @classmethod
32
+ async def generate_thinking(
33
+ cls,
34
+ query: str,
35
+ context: Dict[str, Any],
36
+ llm_client: Optional[Any] = None
37
+ ) -> List[ThinkingStep]:
38
+ """
39
+ Generate thinking steps for a query
40
+
41
+ Args:
42
+ query: User's question
43
+ context: Available context (tools, data, history)
44
+ llm_client: LLM client to generate thinking (optional - can be rule-based too)
45
+
46
+ Returns:
47
+ List of thinking steps showing reasoning process
48
+ """
49
+ steps = []
50
+
51
+ # Step 1: Understand the query
52
+ understanding_step = cls._analyze_query(query, context)
53
+ steps.append(understanding_step)
54
+
55
+ # Step 2: Plan approach
56
+ planning_step = cls._plan_approach(query, context, understanding_step)
57
+ steps.append(planning_step)
58
+
59
+ # Step 3: Identify what tools/data needed
60
+ tools_step = cls._identify_tools_needed(query, context, planning_step)
61
+ steps.append(tools_step)
62
+
63
+ # Step 4: Consider potential issues
64
+ issues_step = cls._consider_issues(query, context)
65
+ if issues_step:
66
+ steps.append(issues_step)
67
+
68
+ return steps
69
+
70
+ @classmethod
71
+ def _analyze_query(cls, query: str, context: Dict[str, Any]) -> ThinkingStep:
72
+ """Analyze what the user is really asking"""
73
+ query_lower = query.lower()
74
+
75
+ # Determine query type
76
+ if any(word in query_lower for word in ['find', 'search', 'look for']):
77
+ thought = f"User wants me to search for something. Need to determine: search where (files/papers/data)?"
78
+
79
+ elif any(word in query_lower for word in ['list', 'show', 'what']):
80
+ thought = f"User wants information listed. Need to figure out: what kind of listing?"
81
+
82
+ elif any(word in query_lower for word in ['how', 'why', 'explain']):
83
+ thought = f"User wants an explanation. Need to: understand the topic and provide clear reasoning."
84
+
85
+ elif any(word in query_lower for word in ['compare', 'versus', 'vs']):
86
+ thought = f"User wants a comparison. Need to: identify what's being compared and on what metric."
87
+
88
+ elif any(word in query_lower for word in ['bug', 'error', 'fix', 'wrong']):
89
+ thought = f"User has a problem that needs fixing. Need to: identify the issue and suggest a solution."
90
+
91
+ else:
92
+ thought = f"User's query is: '{query}'. Need to understand the intent first."
93
+
94
+ return ThinkingStep(
95
+ step_number=1,
96
+ thought=thought,
97
+ reasoning_type="analysis",
98
+ confidence=0.8
99
+ )
100
+
101
+ @classmethod
102
+ def _plan_approach(
103
+ cls,
104
+ query: str,
105
+ context: Dict[str, Any],
106
+ understanding: ThinkingStep
107
+ ) -> ThinkingStep:
108
+ """Plan how to approach answering this query"""
109
+ query_lower = query.lower()
110
+
111
+ # Determine best approach based on understanding
112
+ if 'search' in understanding.thought.lower():
113
+ # Search query
114
+ if any(word in query_lower for word in ['paper', 'research', 'study']):
115
+ thought = "Best approach: Use Archive API to search academic papers."
116
+
117
+ elif any(word in query_lower for word in ['file', 'directory', 'code']):
118
+ thought = "Best approach: Use shell commands (find/grep) to search the filesystem."
119
+
120
+ elif any(word in query_lower for word in ['company', 'revenue', 'stock']):
121
+ thought = "Best approach: Use FinSight API to get financial data."
122
+
123
+ else:
124
+ thought = "Best approach: Try web search first, fallback to file search if needed."
125
+
126
+ elif 'list' in understanding.thought.lower():
127
+ # Listing query
128
+ if 'file' in query_lower or 'directory' in query_lower:
129
+ thought = "Best approach: Execute 'ls' or 'find' command to list files."
130
+
131
+ else:
132
+ thought = "Best approach: Determine what needs listing, then use appropriate tool."
133
+
134
+ elif 'comparison' in understanding.thought.lower():
135
+ # Comparison query
136
+ thought = "Best approach: Gather data for both items, then create comparison."
137
+
138
+ elif 'problem' in understanding.thought.lower():
139
+ # Debugging query
140
+ thought = "Best approach: Read the relevant code/file, identify issue, suggest fix."
141
+
142
+ else:
143
+ # Generic
144
+ thought = "Best approach: Gather necessary information, then synthesize a clear answer."
145
+
146
+ return ThinkingStep(
147
+ step_number=2,
148
+ thought=thought,
149
+ reasoning_type="planning",
150
+ confidence=0.85
151
+ )
152
+
153
+ @classmethod
154
+ def _identify_tools_needed(
155
+ cls,
156
+ query: str,
157
+ context: Dict[str, Any],
158
+ plan: ThinkingStep
159
+ ) -> ThinkingStep:
160
+ """Identify what tools/APIs are needed"""
161
+ plan_lower = plan.thought.lower()
162
+
163
+ needed_tools = []
164
+
165
+ if 'archive api' in plan_lower:
166
+ needed_tools.append("Archive API (for papers)")
167
+
168
+ if 'finsight api' in plan_lower:
169
+ needed_tools.append("FinSight API (for financial data)")
170
+
171
+ if 'shell' in plan_lower or 'command' in plan_lower:
172
+ needed_tools.append("Shell commands (for file operations)")
173
+
174
+ if 'web search' in plan_lower:
175
+ needed_tools.append("Web search")
176
+
177
+ if needed_tools:
178
+ tools_list = ", ".join(needed_tools)
179
+ thought = f"Tools I'll need: {tools_list}"
180
+ else:
181
+ thought = "I can answer this directly without external tools."
182
+
183
+ return ThinkingStep(
184
+ step_number=3,
185
+ thought=thought,
186
+ reasoning_type="planning",
187
+ confidence=0.9
188
+ )
189
+
190
+ @classmethod
191
+ def _consider_issues(
192
+ cls,
193
+ query: str,
194
+ context: Dict[str, Any]
195
+ ) -> Optional[ThinkingStep]:
196
+ """Consider potential issues or ambiguities"""
197
+ query_lower = query.lower()
198
+
199
+ # Check for ambiguities
200
+ if len(query.split()) < 3:
201
+ return ThinkingStep(
202
+ step_number=4,
203
+ thought="Query is very short - might be ambiguous. Will try to infer from context, or ask for clarification if needed.",
204
+ reasoning_type="verification",
205
+ confidence=0.6
206
+ )
207
+
208
+ # Check for pronouns without clear referents
209
+ pronouns = ['it', 'that', 'those', 'this', 'them']
210
+ has_pronoun = any(pronoun in query_lower.split() for pronoun in pronouns)
211
+
212
+ if has_pronoun and not context.get('conversation_history'):
213
+ return ThinkingStep(
214
+ step_number=4,
215
+ thought="Query uses pronouns but I don't have conversation context. Might need to ask what they're referring to.",
216
+ reasoning_type="verification",
217
+ confidence=0.5
218
+ )
219
+
220
+ return None
221
+
222
+ @classmethod
223
+ def format_for_display(cls, thinking_steps: List[ThinkingStep], show_full: bool = False) -> str:
224
+ """
225
+ Format thinking steps for terminal display
226
+
227
+ Args:
228
+ thinking_steps: List of thinking steps
229
+ show_full: If True, show all details. If False, show compact version.
230
+
231
+ Returns:
232
+ Formatted string for display
233
+ """
234
+ if not thinking_steps:
235
+ return ""
236
+
237
+ lines = []
238
+
239
+ if show_full:
240
+ lines.append("\n💭 **Thinking Process:**\n")
241
+ for step in thinking_steps:
242
+ lines.append(f"{step.step_number}. {step.thought}")
243
+
244
+ else:
245
+ # Compact version - just show the key thoughts
246
+ lines.append("\n💭 *Thinking:* " + thinking_steps[0].thought)
247
+
248
+ return "\n".join(lines)
249
+
250
+ @classmethod
251
+ def should_show_thinking(cls, query: str, context: Dict[str, Any]) -> bool:
252
+ """
253
+ Determine if we should show thinking blocks for this query
254
+
255
+ Show thinking for:
256
+ - Complex queries (> 10 words)
257
+ - Ambiguous queries
258
+ - Queries that require multi-step reasoning
259
+ - Debugging/analysis queries
260
+
261
+ Don't show for:
262
+ - Simple greetings
263
+ - Very short queries
264
+ - Trivial questions
265
+ """
266
+ query_lower = query.lower()
267
+
268
+ # Don't show for greetings
269
+ if any(word in query_lower for word in ['hi', 'hey', 'hello', 'thanks']):
270
+ if len(query.split()) <= 3:
271
+ return False
272
+
273
+ # Show for complex queries
274
+ if len(query.split()) > 10:
275
+ return True
276
+
277
+ # Show for debugging queries
278
+ if any(word in query_lower for word in ['bug', 'error', 'fix', 'wrong', 'broken']):
279
+ return True
280
+
281
+ # Show for comparison queries
282
+ if any(word in query_lower for word in ['compare', 'versus', 'vs', 'difference']):
283
+ return True
284
+
285
+ # Show for analysis queries
286
+ if any(word in query_lower for word in ['analyze', 'explain', 'why', 'how does']):
287
+ return True
288
+
289
+ # Default: don't show for simple queries
290
+ return False
291
+
292
+
293
+ # Convenience function
294
+ async def generate_and_format_thinking(
295
+ query: str,
296
+ context: Dict[str, Any],
297
+ show_full: bool = False
298
+ ) -> str:
299
+ """
300
+ Generate thinking and format for display in one call
301
+
302
+ Returns empty string if thinking should not be shown
303
+ """
304
+ if not ThinkingBlockGenerator.should_show_thinking(query, context):
305
+ return ""
306
+
307
+ steps = await ThinkingBlockGenerator.generate_thinking(query, context)
308
+ return ThinkingBlockGenerator.format_for_display(steps, show_full)