cite-agent 1.2.13__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.2.13/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.2.13 → cite_agent-1.3.1}/cite_agent/enhanced_ai_agent.py +227 -216
- {cite_agent-1.2.13 → cite_agent-1.3.1/cite_agent.egg-info}/PKG-INFO +1 -1
- {cite_agent-1.2.13 → cite_agent-1.3.1}/setup.py +1 -1
- cite_agent-1.2.13/cite_agent/__version__.py +0 -1
- {cite_agent-1.2.13 → cite_agent-1.3.1}/LICENSE +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/MANIFEST.in +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/README.md +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/cite_agent/__init__.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/cite_agent/__main__.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/cite_agent/account_client.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/cite_agent/agent_backend_only.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/cite_agent/ascii_plotting.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/cite_agent/auth.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/cite_agent/backend_only_client.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/cite_agent/cli.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/cite_agent/cli_conversational.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/cite_agent/cli_enhanced.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/cite_agent/cli_workflow.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/cite_agent/dashboard.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/cite_agent/rate_limiter.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/cite_agent/session_manager.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/cite_agent/setup_config.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/cite_agent/streaming_ui.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/cite_agent/telemetry.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/cite_agent/ui.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/cite_agent/updater.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/cite_agent/web_search.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/cite_agent/workflow.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/cite_agent/workflow_integration.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/cite_agent.egg-info/SOURCES.txt +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/cite_agent.egg-info/dependency_links.txt +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/cite_agent.egg-info/entry_points.txt +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/cite_agent.egg-info/requires.txt +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/cite_agent.egg-info/top_level.txt +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/docs/BETA_LAUNCH_CHECKLIST.md +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/docs/BETA_RELEASE_CHECKLIST.md +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/docs/ENHANCED_CAPABILITIES.md +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/docs/GROQ_RATE_LIMITS.md +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/docs/INSTALL.md +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/docs/PUBLISHING_PYPI.md +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/docs/SECURE_PACKAGING_GUIDE.md +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/docs/SECURITY_AUDIT.md +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/docs/USER_GETTING_STARTED.md +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/docs/playbooks/BETA_LAUNCH_PLAYBOOK.md +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/requirements.txt +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/setup.cfg +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/src/__init__.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/src/services/__init__.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/src/services/auth_service/__init__.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/src/services/auth_service/auth_manager.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/src/services/graph/__init__.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/src/services/graph/knowledge_graph.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/src/services/llm_service/__init__.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/src/services/llm_service/llm_manager.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/src/services/paper_service/__init__.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/src/services/paper_service/openalex.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/src/services/performance_service/__init__.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/src/services/performance_service/rust_performance.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/src/services/research_service/__init__.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/src/services/research_service/chatbot.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/src/services/research_service/citation_manager.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/src/services/research_service/context_manager.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/src/services/research_service/conversation_manager.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/src/services/research_service/critical_paper_detector.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/src/services/research_service/enhanced_research.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/src/services/research_service/enhanced_synthesizer.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/src/services/research_service/query_generator.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/src/services/research_service/synthesizer.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/src/services/search_service/__init__.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/src/services/search_service/indexer.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/src/services/search_service/search_engine.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/src/services/simple_enhanced_main.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/tests/beta_launch_test_suite.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/tests/enhanced/test_account_client.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/tests/enhanced/test_archive_agent.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/tests/enhanced/test_enhanced_agent_runtime.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/tests/enhanced/test_reasoning_engine.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/tests/enhanced/test_setup_config.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/tests/enhanced/test_tool_framework.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/tests/integration_test.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/tests/test_truth_seeking_comprehensive.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/tests/validation/test_accuracy_system.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/tests/validation/test_agent_live.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/tests/validation/test_backend_local.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/tests/validation/test_cerebras_comparison.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/tests/validation/test_improved_prompt.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/tests/validation/test_qualitative_robustness.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/tests/validation/test_qualitative_system.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/tests/validation/test_truth_seeking_chinese.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/tests/validation/test_truth_seeking_comprehensive.py +0 -0
- {cite_agent-1.2.13 → cite_agent-1.3.1}/tests/validation/test_truth_seeking_real.py +0 -0
- {cite_agent-1.2.13 → 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,232 +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
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2736
|
+
# LLM extracts ticker + metric (more accurate than regex)
|
|
2737
|
+
finance_prompt = f"""Extract financial query details from user's question.
|
|
2738
|
+
|
|
2739
|
+
User query: "{request.question}"
|
|
2740
|
+
|
|
2741
|
+
Respond with JSON:
|
|
2742
|
+
{{
|
|
2743
|
+
"tickers": ["AAPL", "TSLA"] (stock symbols - infer from company names if needed),
|
|
2744
|
+
"metric": "revenue|marketCap|price|netIncome|eps|freeCashFlow|grossProfit"
|
|
2745
|
+
}}
|
|
2746
|
+
|
|
2747
|
+
Examples:
|
|
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"}}
|
|
2752
|
+
|
|
2753
|
+
JSON:"""
|
|
2754
|
+
|
|
2755
|
+
try:
|
|
2756
|
+
finance_response = await self.call_backend_query(
|
|
2757
|
+
query=finance_prompt,
|
|
2758
|
+
conversation_history=[],
|
|
2759
|
+
api_results={},
|
|
2760
|
+
tools_used=[]
|
|
2761
|
+
)
|
|
2762
|
+
|
|
2763
|
+
import json as json_module
|
|
2764
|
+
finance_text = finance_response.response.strip()
|
|
2765
|
+
if '```' in finance_text:
|
|
2766
|
+
finance_text = finance_text.split('```')[1].replace('json', '').strip()
|
|
2654
2767
|
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
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"
|
|
2768
|
+
finance_plan = json_module.loads(finance_text)
|
|
2769
|
+
tickers = finance_plan.get("tickers", [])
|
|
2770
|
+
metric = finance_plan.get("metric", "revenue")
|
|
2665
2771
|
|
|
2666
|
-
# Call FinSight with detected metric
|
|
2667
2772
|
if debug_mode:
|
|
2668
|
-
print(f"🔍
|
|
2669
|
-
|
|
2773
|
+
print(f"🔍 LLM FINANCE PLAN: tickers={tickers}, metric={metric}")
|
|
2774
|
+
|
|
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")
|
|
2783
|
+
|
|
2784
|
+
except Exception as e:
|
|
2670
2785
|
if debug_mode:
|
|
2671
|
-
print(f"🔍
|
|
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')}")
|
|
2786
|
+
print(f"🔍 Finance LLM extraction failed: {e}")
|
|
2678
2787
|
|
|
2679
|
-
#
|
|
2680
|
-
#
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
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:
|
|
2698
2843
|
web_results = await self.web_search.search_web(request.question, num_results=3)
|
|
2699
2844
|
if web_results and "results" in web_results:
|
|
2700
2845
|
api_results["web_search"] = web_results
|
|
2701
2846
|
tools_used.append("web_search")
|
|
2702
2847
|
if debug_mode:
|
|
2703
2848
|
print(f"🔍 Web search returned: {len(web_results.get('results', []))} results")
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2849
|
+
|
|
2850
|
+
except Exception as e:
|
|
2851
|
+
if debug_mode:
|
|
2852
|
+
print(f"🔍 Web search decision failed: {e}")
|
|
2707
2853
|
|
|
2708
|
-
# PRODUCTION MODE:
|
|
2854
|
+
# PRODUCTION MODE: Call backend LLM with all gathered data
|
|
2709
2855
|
if self.client is None:
|
|
2710
|
-
#
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
# Basic info queries
|
|
2714
|
-
needs_shell_info = any(phrase in question_lower for phrase in [
|
|
2715
|
-
'directory', 'folder', 'where am i', 'pwd', 'current location',
|
|
2716
|
-
'list files', 'what files', 'ls', 'files in', 'show files',
|
|
2717
|
-
'data files', 'csv files', 'check if file', 'file exists'
|
|
2718
|
-
])
|
|
2719
|
-
|
|
2720
|
-
# Fuzzy search queries (find similar directories/files)
|
|
2721
|
-
needs_find = any(phrase in question_lower for phrase in [
|
|
2722
|
-
'looking for', 'find', 'search for', 'similar to',
|
|
2723
|
-
'go to', 'cd to', 'navigate to', 'or something', 'forgot the name',
|
|
2724
|
-
'look into', 'check what', 'what\'s in'
|
|
2725
|
-
])
|
|
2726
|
-
|
|
2727
|
-
if (needs_shell_info or needs_find) and self.shell_session:
|
|
2728
|
-
# Execute exploration commands
|
|
2729
|
-
try:
|
|
2730
|
-
api_results["shell_info"] = {}
|
|
2731
|
-
|
|
2732
|
-
# Always include current location
|
|
2733
|
-
pwd_output = self.execute_command("pwd")
|
|
2734
|
-
api_results["shell_info"]["current_directory"] = pwd_output.strip()
|
|
2735
|
-
|
|
2736
|
-
if needs_shell_info and not needs_find:
|
|
2737
|
-
# Just list current directory
|
|
2738
|
-
ls_output = self.execute_command("ls -lah")
|
|
2739
|
-
api_results["shell_info"]["directory_contents"] = ls_output
|
|
2740
|
-
|
|
2741
|
-
if needs_find:
|
|
2742
|
-
# Smart search: extract directory name and location hints
|
|
2743
|
-
import re
|
|
2744
|
-
|
|
2745
|
-
# Check if user is referring to previous context ("it", "there")
|
|
2746
|
-
has_pronoun = any(word in question_lower for word in ['it', 'there', 'that folder', 'that directory'])
|
|
2747
|
-
pronoun_resolved = False
|
|
2748
|
-
|
|
2749
|
-
if has_pronoun and len(self.conversation_history) > 0:
|
|
2750
|
-
# Look for directory path in last assistant message
|
|
2751
|
-
last_assistant = None
|
|
2752
|
-
for msg in reversed(self.conversation_history):
|
|
2753
|
-
if msg.get('role') == 'assistant':
|
|
2754
|
-
last_assistant = msg.get('content', '')
|
|
2755
|
-
break
|
|
2756
|
-
|
|
2757
|
-
if last_assistant:
|
|
2758
|
-
# Extract paths like /home/user/Downloads/cm522-main
|
|
2759
|
-
paths = re.findall(r'(/[\w/.-]+)', last_assistant)
|
|
2760
|
-
if paths:
|
|
2761
|
-
# List contents of the first path found
|
|
2762
|
-
target_path = paths[0]
|
|
2763
|
-
ls_output = self.execute_command(f"ls -lah {target_path}")
|
|
2764
|
-
api_results["shell_info"]["directory_contents"] = ls_output
|
|
2765
|
-
api_results["shell_info"]["target_path"] = target_path
|
|
2766
|
-
tools_used.append("shell_execution")
|
|
2767
|
-
pronoun_resolved = True
|
|
2768
|
-
|
|
2769
|
-
# Generic search if no pronoun or pronoun not resolved
|
|
2770
|
-
if not pronoun_resolved:
|
|
2771
|
-
# SMART EXTRACTION: Use pattern matching + common sense
|
|
2772
|
-
import re
|
|
2773
|
-
|
|
2774
|
-
# Strategy: Look for quoted strings or alphanumeric codes
|
|
2775
|
-
# Priority 1: Quoted strings ("cm522", 'my_folder')
|
|
2776
|
-
quoted = re.findall(r'["\']([^"\']+)["\']', request.question)
|
|
2777
|
-
|
|
2778
|
-
if quoted:
|
|
2779
|
-
search_terms = quoted
|
|
2780
|
-
else:
|
|
2781
|
-
# Priority 2: Alphanumeric codes/IDs (cm522, hw03, proj_2024)
|
|
2782
|
-
# Pattern: letters + numbers mixed, or underscores/dashes
|
|
2783
|
-
codes = re.findall(r'\b([a-zA-Z]*\d+[a-zA-Z0-9_-]*|[a-zA-Z0-9]*[_-]+[a-zA-Z0-9]+)\b', request.question)
|
|
2784
|
-
|
|
2785
|
-
# Priority 3: Capitalize words (likely proper nouns: GitHub, MyProject)
|
|
2786
|
-
capitalized = re.findall(r'\b([A-Z][a-zA-Z0-9_-]+)\b', request.question)
|
|
2787
|
-
|
|
2788
|
-
# Priority 4: Long words (≥ 6 chars, likely meaningful)
|
|
2789
|
-
long_words = re.findall(r'\b([a-zA-Z]{6,})\b', request.question)
|
|
2790
|
-
|
|
2791
|
-
# Combine and dedupe
|
|
2792
|
-
search_terms = list(dict.fromkeys(codes + capitalized + long_words))
|
|
2793
|
-
|
|
2794
|
-
# Filter out common words
|
|
2795
|
-
common = {
|
|
2796
|
-
'looking', 'folder', 'directory', 'called', 'something',
|
|
2797
|
-
'downloads', 'documents', 'computer', 'somewhere'
|
|
2798
|
-
}
|
|
2799
|
-
search_terms = [t for t in search_terms if t.lower() not in common][:2]
|
|
2800
|
-
|
|
2801
|
-
debug_mode = os.getenv("NOCTURNAL_DEBUG", "").lower() == "1"
|
|
2802
|
-
if debug_mode:
|
|
2803
|
-
print(f"🔍 EXTRACTED SEARCH TERMS: {search_terms}")
|
|
2804
|
-
|
|
2805
|
-
if not search_terms:
|
|
2806
|
-
search_terms = [''] # Empty search to show "no target found"
|
|
2807
|
-
|
|
2808
|
-
# Detect location hints
|
|
2809
|
-
search_path = "~" # Default to home
|
|
2810
|
-
if 'downloads' in question_lower:
|
|
2811
|
-
search_path = "~/Downloads"
|
|
2812
|
-
elif 'documents' in question_lower:
|
|
2813
|
-
search_path = "~/Documents"
|
|
2814
|
-
|
|
2815
|
-
search_results = []
|
|
2816
|
-
|
|
2817
|
-
for name in search_terms:
|
|
2818
|
-
if not name:
|
|
2819
|
-
continue
|
|
2820
|
-
|
|
2821
|
-
# Search with increasing depth
|
|
2822
|
-
find_cmd = f"find {search_path} -maxdepth 4 -type d -iname '*{name}*' 2>/dev/null | head -20"
|
|
2823
|
-
find_output = self.execute_command(find_cmd)
|
|
2824
|
-
|
|
2825
|
-
debug_mode = os.getenv("NOCTURNAL_DEBUG", "").lower() == "1"
|
|
2826
|
-
if debug_mode:
|
|
2827
|
-
print(f"🔍 FIND EXECUTED: {find_cmd}")
|
|
2828
|
-
print(f"🔍 FIND OUTPUT: {repr(find_output)}")
|
|
2829
|
-
|
|
2830
|
-
if find_output.strip():
|
|
2831
|
-
search_results.append(f"Searched for '*{name}*' in {search_path}:\n{find_output}")
|
|
2832
|
-
|
|
2833
|
-
if search_results:
|
|
2834
|
-
api_results["shell_info"]["search_results"] = "\n\n".join(search_results)
|
|
2835
|
-
else:
|
|
2836
|
-
api_results["shell_info"]["search_results"] = f"No directories found matching query in {search_path}"
|
|
2837
|
-
|
|
2838
|
-
tools_used.append("shell_execution")
|
|
2839
|
-
except Exception as e:
|
|
2840
|
-
if debug_mode:
|
|
2841
|
-
print(f"🔍 Shell execution failed: {e}")
|
|
2842
|
-
|
|
2843
|
-
# DEBUG: Log exactly what we're sending to backend
|
|
2844
|
-
debug_mode = os.getenv("NOCTURNAL_DEBUG", "").lower() == "1"
|
|
2845
|
-
if debug_mode and api_results.get("shell_info", {}).get("search_results"):
|
|
2846
|
-
print(f"🔍 SENDING TO BACKEND:")
|
|
2847
|
-
print(f"🔍 shell_info.search_results = {repr(api_results['shell_info']['search_results'])}")
|
|
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())}")
|
|
2848
2859
|
|
|
2849
2860
|
# Call backend and UPDATE CONVERSATION HISTORY
|
|
2850
2861
|
response = await self.call_backend_query(
|
|
2851
2862
|
query=request.question,
|
|
2852
2863
|
conversation_history=self.conversation_history[-10:],
|
|
2853
|
-
api_results=api_results,
|
|
2854
|
-
tools_used=tools_used
|
|
2864
|
+
api_results=api_results,
|
|
2865
|
+
tools_used=tools_used
|
|
2855
2866
|
)
|
|
2856
2867
|
|
|
2857
|
-
# CRITICAL: Save to conversation history
|
|
2868
|
+
# CRITICAL: Save to conversation history
|
|
2858
2869
|
self.conversation_history.append({"role": "user", "content": request.question})
|
|
2859
2870
|
self.conversation_history.append({"role": "assistant", "content": response.response})
|
|
2860
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.
|
|
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.2.13"
|
|
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.2.13 → cite_agent-1.3.1}/src/services/research_service/conversation_manager.py
RENAMED
|
File without changes
|
{cite_agent-1.2.13 → cite_agent-1.3.1}/src/services/research_service/critical_paper_detector.py
RENAMED
|
File without changes
|
|
File without changes
|
{cite_agent-1.2.13 → cite_agent-1.3.1}/src/services/research_service/enhanced_synthesizer.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
|