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.
- cite_agent/__init__.py +13 -13
- cite_agent/__version__.py +1 -1
- cite_agent/action_first_mode.py +150 -0
- cite_agent/adaptive_providers.py +413 -0
- cite_agent/archive_api_client.py +186 -0
- cite_agent/auth.py +0 -1
- cite_agent/auto_expander.py +70 -0
- cite_agent/cache.py +379 -0
- cite_agent/circuit_breaker.py +370 -0
- cite_agent/citation_network.py +377 -0
- cite_agent/cli.py +8 -16
- cite_agent/cli_conversational.py +113 -3
- cite_agent/confidence_calibration.py +381 -0
- cite_agent/deduplication.py +325 -0
- cite_agent/enhanced_ai_agent.py +689 -371
- cite_agent/error_handler.py +228 -0
- cite_agent/execution_safety.py +329 -0
- cite_agent/full_paper_reader.py +239 -0
- cite_agent/observability.py +398 -0
- cite_agent/offline_mode.py +348 -0
- cite_agent/paper_comparator.py +368 -0
- cite_agent/paper_summarizer.py +420 -0
- cite_agent/pdf_extractor.py +350 -0
- cite_agent/proactive_boundaries.py +266 -0
- cite_agent/quality_gate.py +442 -0
- cite_agent/request_queue.py +390 -0
- cite_agent/response_enhancer.py +257 -0
- cite_agent/response_formatter.py +458 -0
- cite_agent/response_pipeline.py +295 -0
- cite_agent/response_style_enhancer.py +259 -0
- cite_agent/self_healing.py +418 -0
- cite_agent/similarity_finder.py +524 -0
- cite_agent/streaming_ui.py +13 -9
- cite_agent/thinking_blocks.py +308 -0
- cite_agent/tool_orchestrator.py +416 -0
- cite_agent/trend_analyzer.py +540 -0
- cite_agent/unpaywall_client.py +226 -0
- {cite_agent-1.3.9.dist-info → cite_agent-1.4.3.dist-info}/METADATA +15 -1
- cite_agent-1.4.3.dist-info/RECORD +62 -0
- cite_agent-1.3.9.dist-info/RECORD +0 -32
- {cite_agent-1.3.9.dist-info → cite_agent-1.4.3.dist-info}/WHEEL +0 -0
- {cite_agent-1.3.9.dist-info → cite_agent-1.4.3.dist-info}/entry_points.txt +0 -0
- {cite_agent-1.3.9.dist-info → cite_agent-1.4.3.dist-info}/licenses/LICENSE +0 -0
- {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)
|