cite-agent 1.3.0__tar.gz → 1.3.1__tar.gz
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-1.3.0/cite_agent.egg-info → cite_agent-1.3.1}/PKG-INFO +1 -1
- cite_agent-1.3.1/cite_agent/__version__.py +1 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/enhanced_ai_agent.py +214 -173
- {cite_agent-1.3.0 → cite_agent-1.3.1/cite_agent.egg-info}/PKG-INFO +1 -1
- {cite_agent-1.3.0 → cite_agent-1.3.1}/setup.py +1 -1
- cite_agent-1.3.0/cite_agent/__version__.py +0 -1
- {cite_agent-1.3.0 → cite_agent-1.3.1}/LICENSE +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/MANIFEST.in +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/README.md +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/__init__.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/__main__.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/account_client.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/agent_backend_only.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/ascii_plotting.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/auth.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/backend_only_client.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/cli.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/cli_conversational.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/cli_enhanced.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/cli_workflow.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/dashboard.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/rate_limiter.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/session_manager.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/setup_config.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/streaming_ui.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/telemetry.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/ui.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/updater.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/web_search.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/workflow.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/workflow_integration.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent.egg-info/SOURCES.txt +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent.egg-info/dependency_links.txt +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent.egg-info/entry_points.txt +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent.egg-info/requires.txt +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent.egg-info/top_level.txt +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/docs/BETA_LAUNCH_CHECKLIST.md +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/docs/BETA_RELEASE_CHECKLIST.md +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/docs/ENHANCED_CAPABILITIES.md +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/docs/GROQ_RATE_LIMITS.md +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/docs/INSTALL.md +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/docs/PUBLISHING_PYPI.md +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/docs/SECURE_PACKAGING_GUIDE.md +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/docs/SECURITY_AUDIT.md +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/docs/USER_GETTING_STARTED.md +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/docs/playbooks/BETA_LAUNCH_PLAYBOOK.md +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/requirements.txt +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/setup.cfg +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/src/__init__.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/__init__.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/auth_service/__init__.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/auth_service/auth_manager.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/graph/__init__.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/graph/knowledge_graph.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/llm_service/__init__.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/llm_service/llm_manager.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/paper_service/__init__.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/paper_service/openalex.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/performance_service/__init__.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/performance_service/rust_performance.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/research_service/__init__.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/research_service/chatbot.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/research_service/citation_manager.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/research_service/context_manager.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/research_service/conversation_manager.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/research_service/critical_paper_detector.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/research_service/enhanced_research.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/research_service/enhanced_synthesizer.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/research_service/query_generator.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/research_service/synthesizer.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/search_service/__init__.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/search_service/indexer.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/search_service/search_engine.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/simple_enhanced_main.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/beta_launch_test_suite.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/enhanced/test_account_client.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/enhanced/test_archive_agent.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/enhanced/test_enhanced_agent_runtime.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/enhanced/test_reasoning_engine.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/enhanced/test_setup_config.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/enhanced/test_tool_framework.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/integration_test.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/test_truth_seeking_comprehensive.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/validation/test_accuracy_system.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/validation/test_agent_live.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/validation/test_backend_local.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/validation/test_cerebras_comparison.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/validation/test_improved_prompt.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/validation/test_qualitative_robustness.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/validation/test_qualitative_system.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/validation/test_truth_seeking_chinese.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/validation/test_truth_seeking_comprehensive.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/validation/test_truth_seeking_real.py +0 -0
- {cite_agent-1.3.0 → cite_agent-1.3.1}/tools/security_audit.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.3.1"
|
|
@@ -2594,23 +2594,125 @@ class EnhancedNocturnalAgent:
|
|
|
2594
2594
|
if workflow_response:
|
|
2595
2595
|
return workflow_response
|
|
2596
2596
|
|
|
2597
|
-
#
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
# Debug: Check what was detected
|
|
2597
|
+
# Initialize
|
|
2598
|
+
api_results = {}
|
|
2599
|
+
tools_used = []
|
|
2601
2600
|
debug_mode = os.getenv("NOCTURNAL_DEBUG", "").lower() == "1"
|
|
2601
|
+
|
|
2602
|
+
# ========================================================================
|
|
2603
|
+
# PRIORITY 1: SHELL PLANNING (Reasoning Layer - Runs FIRST for ALL modes)
|
|
2604
|
+
# ========================================================================
|
|
2605
|
+
# This determines USER INTENT before fetching any data
|
|
2606
|
+
# Prevents waste: "find cm522" won't trigger Archive API, "look into it" won't web search
|
|
2607
|
+
# Works in BOTH production and dev modes
|
|
2608
|
+
|
|
2609
|
+
shell_action = "none" # Will be: pwd|ls|find|none
|
|
2610
|
+
|
|
2611
|
+
# Quick check if query might need shell
|
|
2612
|
+
question_lower = request.question.lower()
|
|
2613
|
+
might_need_shell = any(word in question_lower for word in [
|
|
2614
|
+
'directory', 'folder', 'where', 'find', 'list', 'files', 'look', 'search', 'check', 'into'
|
|
2615
|
+
])
|
|
2616
|
+
|
|
2617
|
+
if might_need_shell and self.shell_session:
|
|
2618
|
+
# Ask LLM planner: What shell command should we run?
|
|
2619
|
+
planner_prompt = f"""You are a shell command planner. Determine what shell command to run.
|
|
2620
|
+
|
|
2621
|
+
User query: "{request.question}"
|
|
2622
|
+
Previous conversation: {json.dumps(self.conversation_history[-2:]) if self.conversation_history else "None"}
|
|
2623
|
+
|
|
2624
|
+
Respond ONLY with JSON:
|
|
2625
|
+
{{
|
|
2626
|
+
"action": "pwd|ls|find|none",
|
|
2627
|
+
"search_target": "cm522" (if find),
|
|
2628
|
+
"search_path": "~/Downloads" (if find),
|
|
2629
|
+
"target_path": "/full/path" (if ls on previous result)
|
|
2630
|
+
}}
|
|
2631
|
+
|
|
2632
|
+
Examples:
|
|
2633
|
+
"where am i?" → {{"action": "pwd"}}
|
|
2634
|
+
"what files here?" → {{"action": "ls"}}
|
|
2635
|
+
"find cm522 in downloads" → {{"action": "find", "search_target": "cm522", "search_path": "~/Downloads"}}
|
|
2636
|
+
"look into it" + Previous: "Found /path/to/dir" → {{"action": "ls", "target_path": "/path/to/dir"}}
|
|
2637
|
+
"Tesla revenue" → {{"action": "none"}}
|
|
2638
|
+
|
|
2639
|
+
JSON:"""
|
|
2640
|
+
|
|
2641
|
+
try:
|
|
2642
|
+
plan_response = await self.call_backend_query(
|
|
2643
|
+
query=planner_prompt,
|
|
2644
|
+
conversation_history=[],
|
|
2645
|
+
api_results={},
|
|
2646
|
+
tools_used=[]
|
|
2647
|
+
)
|
|
2648
|
+
|
|
2649
|
+
plan_text = plan_response.response.strip()
|
|
2650
|
+
if '```' in plan_text:
|
|
2651
|
+
plan_text = plan_text.split('```')[1].replace('json', '').strip()
|
|
2652
|
+
|
|
2653
|
+
plan = json.loads(plan_text)
|
|
2654
|
+
shell_action = plan.get("action", "none")
|
|
2655
|
+
|
|
2656
|
+
if debug_mode:
|
|
2657
|
+
print(f"🔍 SHELL PLAN: {plan}")
|
|
2658
|
+
|
|
2659
|
+
# Execute shell command based on plan
|
|
2660
|
+
if shell_action == "pwd":
|
|
2661
|
+
pwd_output = self.execute_command("pwd")
|
|
2662
|
+
api_results["shell_info"] = {"current_directory": pwd_output.strip()}
|
|
2663
|
+
tools_used.append("shell_execution")
|
|
2664
|
+
|
|
2665
|
+
elif shell_action == "ls":
|
|
2666
|
+
target = plan.get("target_path")
|
|
2667
|
+
if target:
|
|
2668
|
+
ls_output = self.execute_command(f"ls -lah {target}")
|
|
2669
|
+
api_results["shell_info"] = {
|
|
2670
|
+
"directory_contents": ls_output,
|
|
2671
|
+
"target_path": target
|
|
2672
|
+
}
|
|
2673
|
+
else:
|
|
2674
|
+
ls_output = self.execute_command("ls -lah")
|
|
2675
|
+
api_results["shell_info"] = {"directory_contents": ls_output}
|
|
2676
|
+
tools_used.append("shell_execution")
|
|
2677
|
+
|
|
2678
|
+
elif shell_action == "find":
|
|
2679
|
+
search_target = plan.get("search_target", "")
|
|
2680
|
+
search_path = plan.get("search_path", "~")
|
|
2681
|
+
if search_target:
|
|
2682
|
+
find_cmd = f"find {search_path} -maxdepth 4 -type d -iname '*{search_target}*' 2>/dev/null | head -20"
|
|
2683
|
+
find_output = self.execute_command(find_cmd)
|
|
2684
|
+
if debug_mode:
|
|
2685
|
+
print(f"🔍 FIND: {find_cmd}")
|
|
2686
|
+
print(f"🔍 OUTPUT: {repr(find_output)}")
|
|
2687
|
+
if find_output.strip():
|
|
2688
|
+
api_results["shell_info"] = {
|
|
2689
|
+
"search_results": f"Searched for '*{search_target}*' in {search_path}:\n{find_output}"
|
|
2690
|
+
}
|
|
2691
|
+
else:
|
|
2692
|
+
api_results["shell_info"] = {
|
|
2693
|
+
"search_results": f"No directories matching '{search_target}' found in {search_path}"
|
|
2694
|
+
}
|
|
2695
|
+
tools_used.append("shell_execution")
|
|
2696
|
+
|
|
2697
|
+
except Exception as e:
|
|
2698
|
+
if debug_mode:
|
|
2699
|
+
print(f"🔍 Shell planner failed: {e}, continuing without shell")
|
|
2700
|
+
shell_action = "none"
|
|
2701
|
+
|
|
2702
|
+
# ========================================================================
|
|
2703
|
+
# PRIORITY 2: DATA APIs (Only if shell didn't fully handle the query)
|
|
2704
|
+
# ========================================================================
|
|
2705
|
+
# If shell_action = pwd/ls/find, we might still want data APIs
|
|
2706
|
+
# But we skip vague queries to save tokens
|
|
2707
|
+
|
|
2708
|
+
# Analyze what data APIs are needed (only if not pure shell command)
|
|
2709
|
+
request_analysis = await self._analyze_request_type(request.question)
|
|
2602
2710
|
if debug_mode:
|
|
2603
2711
|
print(f"🔍 Request analysis: {request_analysis}")
|
|
2604
2712
|
|
|
2605
|
-
# Check if query is too vague - skip EXPENSIVE API calls to save tokens
|
|
2606
|
-
# But still allow web search (cheap and flexible)
|
|
2607
2713
|
is_vague = self._is_query_too_vague_for_apis(request.question)
|
|
2608
2714
|
if debug_mode and is_vague:
|
|
2609
|
-
print(f"🔍 Query
|
|
2610
|
-
|
|
2611
|
-
# Call appropriate APIs (Archive, FinSight) - BOTH production and dev mode
|
|
2612
|
-
api_results = {}
|
|
2613
|
-
tools_used = []
|
|
2715
|
+
print(f"🔍 Query is VAGUE - skipping expensive APIs")
|
|
2614
2716
|
|
|
2615
2717
|
# Skip Archive/FinSight if query is too vague, but still allow web search later
|
|
2616
2718
|
if not is_vague:
|
|
@@ -2629,202 +2731,141 @@ class EnhancedNocturnalAgent:
|
|
|
2629
2731
|
api_results["research"] = result
|
|
2630
2732
|
tools_used.append("archive_api")
|
|
2631
2733
|
|
|
2632
|
-
# FinSight API for financial data
|
|
2734
|
+
# FinSight API for financial data - Use LLM for ticker/metric extraction
|
|
2633
2735
|
if "finsight" in request_analysis.get("apis", []):
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
# Try common company name mappings
|
|
2637
|
-
question_lower = request.question.lower()
|
|
2638
|
-
if "apple" in question_lower:
|
|
2639
|
-
tickers = ["AAPL"]
|
|
2640
|
-
elif "tesla" in question_lower:
|
|
2641
|
-
tickers = ["TSLA"]
|
|
2642
|
-
elif "microsoft" in question_lower:
|
|
2643
|
-
tickers = ["MSFT"]
|
|
2644
|
-
elif "google" in question_lower or "alphabet" in question_lower:
|
|
2645
|
-
tickers = ["GOOGL"]
|
|
2646
|
-
|
|
2647
|
-
if debug_mode:
|
|
2648
|
-
print(f"🔍 Extracted tickers: {tickers}")
|
|
2649
|
-
|
|
2650
|
-
if tickers:
|
|
2651
|
-
# Detect what metric user is asking for
|
|
2652
|
-
question_lower = request.question.lower()
|
|
2653
|
-
metric = "revenue" # Default
|
|
2654
|
-
|
|
2655
|
-
if any(word in question_lower for word in ['market cap', 'marketcap', 'market value', 'valuation']):
|
|
2656
|
-
metric = "marketCap"
|
|
2657
|
-
elif any(word in question_lower for word in ['stock price', 'share price', 'current price', 'trading at']):
|
|
2658
|
-
metric = "price"
|
|
2659
|
-
elif 'profit' in question_lower and 'gross' not in question_lower:
|
|
2660
|
-
metric = "netIncome"
|
|
2661
|
-
elif 'earnings' in question_lower or 'eps' in question_lower:
|
|
2662
|
-
metric = "eps"
|
|
2663
|
-
elif any(word in question_lower for word in ['cash flow', 'cashflow']):
|
|
2664
|
-
metric = "freeCashFlow"
|
|
2665
|
-
|
|
2666
|
-
# Call FinSight with detected metric
|
|
2667
|
-
if debug_mode:
|
|
2668
|
-
print(f"🔍 Calling FinSight API: calc/{tickers[0]}/{metric}")
|
|
2669
|
-
financial_data = await self._call_finsight_api(f"calc/{tickers[0]}/{metric}")
|
|
2670
|
-
if debug_mode:
|
|
2671
|
-
print(f"🔍 FinSight returned: {list(financial_data.keys()) if financial_data else None}")
|
|
2672
|
-
if financial_data and "error" not in financial_data:
|
|
2673
|
-
api_results["financial"] = financial_data
|
|
2674
|
-
tools_used.append("finsight_api")
|
|
2675
|
-
else:
|
|
2676
|
-
if debug_mode and financial_data:
|
|
2677
|
-
print(f"🔍 FinSight error: {financial_data.get('error')}")
|
|
2678
|
-
|
|
2679
|
-
# Web Search fallback - ALWAYS available even for vague queries
|
|
2680
|
-
# Use for: market share, industry data, current events, prices, anything not in APIs
|
|
2681
|
-
if self.web_search:
|
|
2682
|
-
question_lower = request.question.lower()
|
|
2683
|
-
# Only search if query needs data and APIs didn't provide it
|
|
2684
|
-
needs_web_search = (
|
|
2685
|
-
('market share' in question_lower) or
|
|
2686
|
-
('market size' in question_lower) or
|
|
2687
|
-
('industry' in question_lower and not api_results.get('research')) or
|
|
2688
|
-
('price' in question_lower and ('today' in question_lower or 'current' in question_lower or 'now' in question_lower)) or
|
|
2689
|
-
('bitcoin' in question_lower or 'btc' in question_lower or 'crypto' in question_lower) or
|
|
2690
|
-
('exchange rate' in question_lower or 'forex' in question_lower) or
|
|
2691
|
-
(not api_results and 'latest' in question_lower) # Latest news/data
|
|
2692
|
-
)
|
|
2693
|
-
|
|
2694
|
-
if needs_web_search or (not api_results and len(request.question.split()) > 5):
|
|
2695
|
-
try:
|
|
2696
|
-
if debug_mode:
|
|
2697
|
-
print(f"🔍 Using web search for: {request.question[:50]}...")
|
|
2698
|
-
web_results = await self.web_search.search_web(request.question, num_results=3)
|
|
2699
|
-
if web_results and "results" in web_results:
|
|
2700
|
-
api_results["web_search"] = web_results
|
|
2701
|
-
tools_used.append("web_search")
|
|
2702
|
-
if debug_mode:
|
|
2703
|
-
print(f"🔍 Web search returned: {len(web_results.get('results', []))} results")
|
|
2704
|
-
except Exception as e:
|
|
2705
|
-
if debug_mode:
|
|
2706
|
-
print(f"🔍 Web search failed: {e}")
|
|
2707
|
-
|
|
2708
|
-
# PRODUCTION MODE: Use small LLM to plan shell commands (smarter than hardcoded patterns)
|
|
2709
|
-
if self.client is None:
|
|
2710
|
-
# Ask small LLM: What shell command should we run?
|
|
2711
|
-
planner_prompt = f"""You are a shell command planner. Given the user's query, determine what shell command(s) to run.
|
|
2736
|
+
# LLM extracts ticker + metric (more accurate than regex)
|
|
2737
|
+
finance_prompt = f"""Extract financial query details from user's question.
|
|
2712
2738
|
|
|
2713
2739
|
User query: "{request.question}"
|
|
2714
|
-
Previous conversation context: {json.dumps(self.conversation_history[-2:]) if self.conversation_history else "None"}
|
|
2715
2740
|
|
|
2716
2741
|
Respond with JSON:
|
|
2717
2742
|
{{
|
|
2718
|
-
"
|
|
2719
|
-
"
|
|
2720
|
-
"search_path": "~/Downloads or ~/Documents or ~ (if action=find)",
|
|
2721
|
-
"target_path": "/full/path (if referring to previous result)"
|
|
2743
|
+
"tickers": ["AAPL", "TSLA"] (stock symbols - infer from company names if needed),
|
|
2744
|
+
"metric": "revenue|marketCap|price|netIncome|eps|freeCashFlow|grossProfit"
|
|
2722
2745
|
}}
|
|
2723
2746
|
|
|
2724
2747
|
Examples:
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
Query: "what's the capital of France?" → {{"action": "none"}}
|
|
2748
|
+
- "Tesla revenue" → {{"tickers": ["TSLA"], "metric": "revenue"}}
|
|
2749
|
+
- "What's Apple worth?" → {{"tickers": ["AAPL"], "metric": "marketCap"}}
|
|
2750
|
+
- "tsla stock price" → {{"tickers": ["TSLA"], "metric": "price"}}
|
|
2751
|
+
- "Microsoft profit" → {{"tickers": ["MSFT"], "metric": "netIncome"}}
|
|
2730
2752
|
|
|
2731
2753
|
JSON:"""
|
|
2732
2754
|
|
|
2733
|
-
question_lower = request.question.lower()
|
|
2734
|
-
needs_shell = any(word in question_lower for word in [
|
|
2735
|
-
'directory', 'folder', 'where', 'find', 'list', 'files', 'look', 'search', 'check'
|
|
2736
|
-
])
|
|
2737
|
-
|
|
2738
|
-
if needs_shell and self.shell_session:
|
|
2739
2755
|
try:
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
query=planner_prompt,
|
|
2756
|
+
finance_response = await self.call_backend_query(
|
|
2757
|
+
query=finance_prompt,
|
|
2743
2758
|
conversation_history=[],
|
|
2744
2759
|
api_results={},
|
|
2745
2760
|
tools_used=[]
|
|
2746
2761
|
)
|
|
2747
2762
|
|
|
2748
|
-
# Parse JSON plan
|
|
2749
2763
|
import json as json_module
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
plan_text = plan_text.split('```')[1].replace('json', '').strip()
|
|
2764
|
+
finance_text = finance_response.response.strip()
|
|
2765
|
+
if '```' in finance_text:
|
|
2766
|
+
finance_text = finance_text.split('```')[1].replace('json', '').strip()
|
|
2754
2767
|
|
|
2755
|
-
|
|
2768
|
+
finance_plan = json_module.loads(finance_text)
|
|
2769
|
+
tickers = finance_plan.get("tickers", [])
|
|
2770
|
+
metric = finance_plan.get("metric", "revenue")
|
|
2756
2771
|
|
|
2757
|
-
debug_mode = os.getenv("NOCTURNAL_DEBUG", "").lower() == "1"
|
|
2758
2772
|
if debug_mode:
|
|
2759
|
-
print(f"🔍 LLM PLAN: {
|
|
2760
|
-
|
|
2761
|
-
api_results["shell_info"] = {}
|
|
2762
|
-
|
|
2763
|
-
# Execute based on plan
|
|
2764
|
-
action = plan.get("action", "none")
|
|
2773
|
+
print(f"🔍 LLM FINANCE PLAN: tickers={tickers}, metric={metric}")
|
|
2765
2774
|
|
|
2766
|
-
if
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
# List specific directory
|
|
2775
|
-
ls_output = self.execute_command(f"ls -lah {target}")
|
|
2776
|
-
else:
|
|
2777
|
-
# List current directory
|
|
2778
|
-
ls_output = self.execute_command("ls -lah")
|
|
2779
|
-
api_results["shell_info"]["directory_contents"] = ls_output
|
|
2780
|
-
if target:
|
|
2781
|
-
api_results["shell_info"]["target_path"] = target
|
|
2782
|
-
tools_used.append("shell_execution")
|
|
2783
|
-
|
|
2784
|
-
elif action == "find":
|
|
2785
|
-
search_target = plan.get("search_target", "")
|
|
2786
|
-
search_path = plan.get("search_path", "~")
|
|
2787
|
-
|
|
2788
|
-
if search_target:
|
|
2789
|
-
find_cmd = f"find {search_path} -maxdepth 4 -type d -iname '*{search_target}*' 2>/dev/null | head -20"
|
|
2790
|
-
find_output = self.execute_command(find_cmd)
|
|
2791
|
-
|
|
2792
|
-
if debug_mode:
|
|
2793
|
-
print(f"🔍 FIND EXECUTED: {find_cmd}")
|
|
2794
|
-
print(f"🔍 FIND OUTPUT: {repr(find_output)}")
|
|
2795
|
-
|
|
2796
|
-
if find_output.strip():
|
|
2797
|
-
api_results["shell_info"]["search_results"] = f"Searched for '*{search_target}*' in {search_path}:\n{find_output}"
|
|
2798
|
-
else:
|
|
2799
|
-
api_results["shell_info"]["search_results"] = f"No directories matching '{search_target}' found in {search_path}"
|
|
2800
|
-
tools_used.append("shell_execution")
|
|
2801
|
-
|
|
2802
|
-
# Always include current directory
|
|
2803
|
-
pwd_output = self.execute_command("pwd")
|
|
2804
|
-
api_results["shell_info"]["current_directory"] = pwd_output.strip()
|
|
2775
|
+
if tickers:
|
|
2776
|
+
# Call FinSight with extracted ticker + metric
|
|
2777
|
+
financial_data = await self._call_finsight_api(f"calc/{tickers[0]}/{metric}")
|
|
2778
|
+
if debug_mode:
|
|
2779
|
+
print(f"🔍 FinSight returned: {list(financial_data.keys()) if financial_data else None}")
|
|
2780
|
+
if financial_data and "error" not in financial_data:
|
|
2781
|
+
api_results["financial"] = financial_data
|
|
2782
|
+
tools_used.append("finsight_api")
|
|
2805
2783
|
|
|
2806
2784
|
except Exception as e:
|
|
2807
|
-
# LLM planner failed, skip shell execution
|
|
2808
|
-
debug_mode = os.getenv("NOCTURNAL_DEBUG", "").lower() == "1"
|
|
2809
2785
|
if debug_mode:
|
|
2810
|
-
print(f"🔍
|
|
2811
|
-
|
|
2786
|
+
print(f"🔍 Finance LLM extraction failed: {e}")
|
|
2787
|
+
|
|
2788
|
+
# ========================================================================
|
|
2789
|
+
# PRIORITY 3: WEB SEARCH (Fallback - only if shell didn't handle AND no data yet)
|
|
2790
|
+
# ========================================================================
|
|
2791
|
+
# Only web search if:
|
|
2792
|
+
# - Shell said "none" (not a directory/file operation)
|
|
2793
|
+
# - We don't have enough data from Archive/FinSight
|
|
2794
|
+
|
|
2795
|
+
if self.web_search and shell_action == "none":
|
|
2796
|
+
# Ask LLM: Should we web search for this?
|
|
2797
|
+
web_decision_prompt = f"""Should we use web search for this query?
|
|
2798
|
+
|
|
2799
|
+
User query: "{request.question}"
|
|
2800
|
+
Data already available: {list(api_results.keys())}
|
|
2801
|
+
Shell action: {shell_action}
|
|
2802
|
+
|
|
2803
|
+
Respond with JSON:
|
|
2804
|
+
{{
|
|
2805
|
+
"use_web_search": true/false,
|
|
2806
|
+
"reason": "why or why not"
|
|
2807
|
+
}}
|
|
2808
|
+
|
|
2809
|
+
Use web search for:
|
|
2810
|
+
- Market share/size (not in SEC filings)
|
|
2811
|
+
- Current prices (Bitcoin, commodities, real-time data)
|
|
2812
|
+
- Industry data, statistics
|
|
2813
|
+
- Recent events, news
|
|
2814
|
+
- Questions not answered by existing data
|
|
2815
|
+
|
|
2816
|
+
Don't use if:
|
|
2817
|
+
- Shell already handled it (pwd/ls/find)
|
|
2818
|
+
- Question answered by research/financial APIs
|
|
2819
|
+
- Pure opinion question
|
|
2820
|
+
|
|
2821
|
+
JSON:"""
|
|
2822
|
+
|
|
2823
|
+
try:
|
|
2824
|
+
web_decision_response = await self.call_backend_query(
|
|
2825
|
+
query=web_decision_prompt,
|
|
2826
|
+
conversation_history=[],
|
|
2827
|
+
api_results={},
|
|
2828
|
+
tools_used=[]
|
|
2829
|
+
)
|
|
2830
|
+
|
|
2831
|
+
import json as json_module
|
|
2832
|
+
decision_text = web_decision_response.response.strip()
|
|
2833
|
+
if '```' in decision_text:
|
|
2834
|
+
decision_text = decision_text.split('```')[1].replace('json', '').strip()
|
|
2835
|
+
|
|
2836
|
+
decision = json_module.loads(decision_text)
|
|
2837
|
+
needs_web_search = decision.get("use_web_search", False)
|
|
2838
|
+
|
|
2839
|
+
if debug_mode:
|
|
2840
|
+
print(f"🔍 WEB SEARCH DECISION: {needs_web_search}, reason: {decision.get('reason')}")
|
|
2841
|
+
|
|
2842
|
+
if needs_web_search:
|
|
2843
|
+
web_results = await self.web_search.search_web(request.question, num_results=3)
|
|
2844
|
+
if web_results and "results" in web_results:
|
|
2845
|
+
api_results["web_search"] = web_results
|
|
2846
|
+
tools_used.append("web_search")
|
|
2847
|
+
if debug_mode:
|
|
2848
|
+
print(f"🔍 Web search returned: {len(web_results.get('results', []))} results")
|
|
2812
2849
|
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2850
|
+
except Exception as e:
|
|
2851
|
+
if debug_mode:
|
|
2852
|
+
print(f"🔍 Web search decision failed: {e}")
|
|
2853
|
+
|
|
2854
|
+
# PRODUCTION MODE: Call backend LLM with all gathered data
|
|
2855
|
+
if self.client is None:
|
|
2856
|
+
# DEBUG: Log what we're sending
|
|
2857
|
+
if debug_mode and api_results.get("shell_info"):
|
|
2858
|
+
print(f"🔍 SENDING TO BACKEND: shell_info keys = {list(api_results.get('shell_info', {}).keys())}")
|
|
2818
2859
|
|
|
2819
2860
|
# Call backend and UPDATE CONVERSATION HISTORY
|
|
2820
2861
|
response = await self.call_backend_query(
|
|
2821
2862
|
query=request.question,
|
|
2822
2863
|
conversation_history=self.conversation_history[-10:],
|
|
2823
|
-
api_results=api_results,
|
|
2824
|
-
tools_used=tools_used
|
|
2864
|
+
api_results=api_results,
|
|
2865
|
+
tools_used=tools_used
|
|
2825
2866
|
)
|
|
2826
2867
|
|
|
2827
|
-
# CRITICAL: Save to conversation history
|
|
2868
|
+
# CRITICAL: Save to conversation history
|
|
2828
2869
|
self.conversation_history.append({"role": "user", "content": request.question})
|
|
2829
2870
|
self.conversation_history.append({"role": "assistant", "content": response.response})
|
|
2830
2871
|
|
|
@@ -7,7 +7,7 @@ long_description = readme_path.read_text() if readme_path.exists() else "Termina
|
|
|
7
7
|
|
|
8
8
|
setup(
|
|
9
9
|
name="cite-agent",
|
|
10
|
-
version="1.3.
|
|
10
|
+
version="1.3.1",
|
|
11
11
|
author="Cite-Agent Team",
|
|
12
12
|
author_email="contact@citeagent.dev",
|
|
13
13
|
description="Terminal AI assistant for academic research with citation verification",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.3.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/research_service/critical_paper_detector.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|