aria-code 4.1.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.
- agents/__init__.py +32 -0
- agents/base.py +190 -0
- agents/deep/__init__.py +37 -0
- agents/deep/calibration_loop.py +144 -0
- agents/deep/critic.py +125 -0
- agents/deep/deepen.py +193 -0
- agents/deep/models.py +149 -0
- agents/deep/pipeline.py +164 -0
- agents/deep/quant_fusion.py +192 -0
- agents/deep/themes.py +95 -0
- agents/deep/tiers.py +106 -0
- agents/financial/__init__.py +10 -0
- agents/financial/catalyst.py +279 -0
- agents/financial/debate.py +145 -0
- agents/financial/earnings.py +303 -0
- agents/financial/fundamental.py +159 -0
- agents/financial/macro.py +99 -0
- agents/financial/news.py +207 -0
- agents/financial/risk.py +132 -0
- agents/financial/sector.py +279 -0
- agents/financial/synthesis.py +274 -0
- agents/financial/technical.py +258 -0
- agents/portfolio_agent.py +333 -0
- agents/realty/__init__.py +62 -0
- agents/realty/asset_diagnosis.py +150 -0
- agents/realty/business_match.py +165 -0
- agents/realty/cashflow_verify.py +208 -0
- agents/realty/contract_rules.py +209 -0
- agents/realty/energy_anomaly.py +188 -0
- agents/realty/exit_settlement.py +207 -0
- agents/realty/fulfillment_risk.py +205 -0
- agents/realty/ops_optimize.py +159 -0
- agents/realty/revenue_share.py +214 -0
- agents/registry.py +144 -0
- agents/sports/__init__.py +0 -0
- agents/sports/football_agent.py +169 -0
- agents/team.py +289 -0
- aliyun_data_client.py +660 -0
- apps/README.md +12 -0
- apps/__init__.py +2 -0
- apps/channels/README.md +15 -0
- apps/cli/README.md +13 -0
- apps/cli/__init__.py +2 -0
- apps/cli/bootstrap.py +99 -0
- apps/cli/codegen_paths.py +29 -0
- apps/cli/commands/__init__.py +16 -0
- apps/cli/commands/analysis_cmds.py +288 -0
- apps/cli/commands/backtest_cmds.py +1887 -0
- apps/cli/commands/broker_cmds.py +1154 -0
- apps/cli/commands/business_workflow_cmds.py +289 -0
- apps/cli/commands/catalog.py +84 -0
- apps/cli/commands/data_cmds.py +405 -0
- apps/cli/commands/diagnostic_cmds.py +179 -0
- apps/cli/commands/diagnostic_ops_cmds.py +696 -0
- apps/cli/commands/finance_render.py +12 -0
- apps/cli/commands/market.py +399 -0
- apps/cli/commands/market_cmds.py +1276 -0
- apps/cli/commands/market_context.py +425 -0
- apps/cli/commands/market_render.py +7 -0
- apps/cli/commands/model_cmds.py +1579 -0
- apps/cli/commands/ops_cmds.py +668 -0
- apps/cli/commands/portfolio_cmds.py +962 -0
- apps/cli/commands/report.py +377 -0
- apps/cli/commands/scaffold_templates.py +617 -0
- apps/cli/commands/session_cmds.py +179 -0
- apps/cli/commands/session_ux_cmds.py +280 -0
- apps/cli/commands/team.py +588 -0
- apps/cli/commands/team_render.py +8 -0
- apps/cli/commands/ui_cmds.py +358 -0
- apps/cli/commands/workflow_cmds.py +279 -0
- apps/cli/commands/workspace_cmds.py +1414 -0
- apps/cli/config_paths.py +70 -0
- apps/cli/config_store.py +61 -0
- apps/cli/deterministic.py +122 -0
- apps/cli/direct.py +48 -0
- apps/cli/github_app_auth.py +135 -0
- apps/cli/handlers/__init__.py +11 -0
- apps/cli/handlers/broker_handlers.py +122 -0
- apps/cli/handlers/chart_handlers.py +1309 -0
- apps/cli/handlers/market_handlers.py +2509 -0
- apps/cli/handlers/realty_handlers.py +114 -0
- apps/cli/handlers/strategy_advice.py +82 -0
- apps/cli/hooks.py +180 -0
- apps/cli/i18n.py +284 -0
- apps/cli/intent.py +136 -0
- apps/cli/intent_router.py +217 -0
- apps/cli/lifecycle_hooks.py +48 -0
- apps/cli/main.py +29 -0
- apps/cli/market_metadata.py +135 -0
- apps/cli/market_universe.py +265 -0
- apps/cli/message_processing.py +257 -0
- apps/cli/plan_mode.py +139 -0
- apps/cli/plotly_html.py +15 -0
- apps/cli/prediction_feedback.py +202 -0
- apps/cli/preflight.py +497 -0
- apps/cli/project_aria.py +60 -0
- apps/cli/prompts/__init__.py +0 -0
- apps/cli/prompts/coding.py +658 -0
- apps/cli/prompts/system_prompts.py +531 -0
- apps/cli/prompts/ui.py +434 -0
- apps/cli/providers/__init__.py +1 -0
- apps/cli/providers/base.py +271 -0
- apps/cli/providers/chat_routing.py +80 -0
- apps/cli/providers/llm/__init__.py +1 -0
- apps/cli/providers/llm/ollama_stream.py +1170 -0
- apps/cli/providers/llm/sse_stream.py +216 -0
- apps/cli/providers/runtime_bridge.py +185 -0
- apps/cli/runtime_consumer.py +489 -0
- apps/cli/session_export.py +87 -0
- apps/cli/session_jsonl.py +207 -0
- apps/cli/session_store.py +112 -0
- apps/cli/todo_tracker.py +190 -0
- apps/cli/tools/__init__.py +40 -0
- apps/cli/tools/context.py +46 -0
- apps/cli/tools/file_tools.py +112 -0
- apps/cli/tools/market_tools.py +549 -0
- apps/cli/tools/notebook_tools.py +111 -0
- apps/cli/tools/system_tools.py +669 -0
- apps/cli/tools/write_tools.py +715 -0
- apps/cli/tradingview_bridge.py +434 -0
- apps/cli/update_check.py +152 -0
- apps/cli/utils/__init__.py +0 -0
- apps/cli/utils/market_detect.py +1578 -0
- apps/daemon/README.md +14 -0
- apps/vscode/README.md +115 -0
- apps/vscode/package.json +70 -0
- aria_cli.py +11636 -0
- aria_code-4.1.3.dist-info/METADATA +952 -0
- aria_code-4.1.3.dist-info/RECORD +284 -0
- aria_code-4.1.3.dist-info/WHEEL +5 -0
- aria_code-4.1.3.dist-info/entry_points.txt +2 -0
- aria_code-4.1.3.dist-info/licenses/LICENSE +121 -0
- aria_code-4.1.3.dist-info/top_level.txt +50 -0
- aria_daemon.py +1295 -0
- aria_feishu_bot.py +1359 -0
- aria_relay_client.py +182 -0
- aria_relay_server.py +405 -0
- aria_telegram_bot.py +202 -0
- ariarc.py +328 -0
- artifacts.py +491 -0
- backtest_report.py +472 -0
- brokers/__init__.py +72 -0
- brokers/base.py +207 -0
- brokers/capabilities.py +264 -0
- brokers/cn/__init__.py +10 -0
- brokers/cn/easytrader_broker.py +193 -0
- brokers/cn/futu_broker.py +194 -0
- brokers/cn/longbridge_broker.py +190 -0
- brokers/cn/tiger_broker.py +196 -0
- brokers/cn/xtquant_broker.py +175 -0
- brokers/config.py +364 -0
- brokers/intl/__init__.py +5 -0
- brokers/intl/alpaca_broker.py +183 -0
- brokers/intl/ibkr_broker.py +215 -0
- brokers/intl/webull_broker.py +156 -0
- brokers/paper_broker.py +259 -0
- brokers/planning.py +296 -0
- brokers/registry.py +181 -0
- brokers/trading.py +237 -0
- change_store.py +127 -0
- command_safety.py +19 -0
- computer_use_tools.py +504 -0
- dashboard_generator.py +578 -0
- data_analysis_tools.py +808 -0
- data_cleaner.py +483 -0
- data_service.py +481 -0
- datasources/__init__.py +23 -0
- datasources/base.py +166 -0
- datasources/router.py +221 -0
- datasources/sources/__init__.py +15 -0
- datasources/sources/akshare_source.py +269 -0
- datasources/sources/alpha_vantage_source.py +202 -0
- datasources/sources/edgar_source.py +218 -0
- datasources/sources/finnhub_source.py +197 -0
- datasources/sources/fred_source.py +219 -0
- datasources/sources/tushare_source.py +141 -0
- datasources/sources/web_scraper_source.py +278 -0
- datasources/sources/world_bank_source.py +205 -0
- datasources/sources/yfinance_source.py +152 -0
- demo_player.py +204 -0
- doctor.py +508 -0
- file_analysis_tools.py +734 -0
- finance_formulas.py +389 -0
- football_data_client.py +1670 -0
- intent_classifier.py +358 -0
- local_finance_tools.py +3221 -0
- local_llm_provider.py +552 -0
- macro_tools.py +368 -0
- market_data_client.py +1899 -0
- mcp_client.py +506 -0
- memory_manager.py +245 -0
- model_capability.py +416 -0
- notification_tools.py +248 -0
- packages/__init__.py +23 -0
- packages/aria_agents/__init__.py +5 -0
- packages/aria_agents/manifest.py +69 -0
- packages/aria_core/__init__.py +34 -0
- packages/aria_core/architecture.py +192 -0
- packages/aria_core/export.py +124 -0
- packages/aria_core/manifest.py +65 -0
- packages/aria_infra/__init__.py +15 -0
- packages/aria_infra/arthera.py +52 -0
- packages/aria_infra/doctor.py +246 -0
- packages/aria_infra/product.py +37 -0
- packages/aria_mcp/__init__.py +25 -0
- packages/aria_mcp/bridge.py +38 -0
- packages/aria_mcp/config.py +97 -0
- packages/aria_mcp/tools.py +61 -0
- packages/aria_sdk/__init__.py +19 -0
- packages/aria_sdk/client.py +396 -0
- packages/aria_sdk/providers.py +70 -0
- packages/aria_sdk/streaming.py +73 -0
- packages/aria_sdk/types.py +86 -0
- packages/aria_services/__init__.py +55 -0
- packages/aria_services/context.py +258 -0
- packages/aria_services/data.py +11 -0
- packages/aria_services/provider_health.py +189 -0
- packages/aria_services/registry.py +213 -0
- packages/aria_services/usage.py +138 -0
- packages/aria_skills/__init__.py +5 -0
- packages/aria_skills/registry.py +59 -0
- packages/aria_tools/__init__.py +5 -0
- packages/aria_tools/registry.py +128 -0
- packages/quant_engine/__init__.py +6 -0
- packages/quant_engine/sports/__init__.py +72 -0
- packages/quant_engine/sports/calibrator.py +353 -0
- packages/quant_engine/sports/dixon_coles.py +234 -0
- packages/quant_engine/sports/elo.py +299 -0
- packages/quant_engine/sports/form.py +188 -0
- packages/quant_engine/sports/h2h.py +195 -0
- packages/quant_engine/sports/ml_model.py +354 -0
- packages/quant_engine/sports/predictor.py +311 -0
- packages/quant_engine/sports/tracker.py +664 -0
- packages/quant_engine/stochastic/__init__.py +27 -0
- packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
- packages/quant_engine/stochastic/ito_calculus.py +477 -0
- packages/quant_engine/stochastic/kelly_criterion.py +181 -0
- packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
- packages/quant_engine/stochastic/options_pricing.py +573 -0
- packages/quant_engine/stochastic/stochastic_processes.py +90 -0
- plan_utils.py +194 -0
- plugin_loader.py +328 -0
- portfolio_ledger.py +262 -0
- privacy/__init__.py +5 -0
- privacy/feedback.py +123 -0
- project_tools.py +525 -0
- providers/__init__.py +30 -0
- providers/llm/__init__.py +19 -0
- providers/llm/anthropic.py +184 -0
- providers/llm/base.py +139 -0
- providers/llm/ollama.py +128 -0
- providers/llm/openai_compat.py +282 -0
- providers/llm/registry.py +358 -0
- realty_data_tools.py +659 -0
- report_generator.py +1314 -0
- runtime/__init__.py +103 -0
- runtime/agent_loop.py +1183 -0
- runtime/approval.py +51 -0
- runtime/events.py +102 -0
- runtime/gateway.py +128 -0
- runtime/lsp.py +346 -0
- runtime/subagent.py +258 -0
- runtime/tool_executor.py +104 -0
- runtime/tool_policy.py +106 -0
- safety/__init__.py +21 -0
- safety/permissions.py +275 -0
- setup_wizard.py +653 -0
- strategy_vault.py +420 -0
- ui/__init__.py +100 -0
- ui/banner.py +310 -0
- ui/completer.py +391 -0
- ui/console.py +271 -0
- ui/image_render.py +243 -0
- ui/input_box.py +376 -0
- ui/picker.py +195 -0
- ui/render/__init__.py +11 -0
- ui/render/finance.py +1480 -0
- ui/render/market.py +225 -0
- ui/render/output.py +681 -0
- ui/render/team.py +346 -0
- ui/robot.py +235 -0
- workspace/__init__.py +6 -0
- workspace/files.py +170 -0
- workspace/verify.py +113 -0
intent_classifier.py
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
"""
|
|
2
|
+
intent_classifier.py — Aria intent classification using Prelude model or keyword fallback.
|
|
3
|
+
|
|
4
|
+
Two-tier design:
|
|
5
|
+
Tier 1 (fast, accurate): aria-prelude via Ollama — the model trained specifically
|
|
6
|
+
for intent routing (adapters: intent-route-control,
|
|
7
|
+
intent-rag_gate, intent-upgrade_gate, intent-clarify).
|
|
8
|
+
Tier 2 (instant, offline): keyword regex — exact same logic as before, used when
|
|
9
|
+
Ollama / aria-prelude is not available.
|
|
10
|
+
|
|
11
|
+
Intent labels (matching CODING_SYSTEM_PROMPT routing in aria_cli.py):
|
|
12
|
+
"coding" → code generation, backtest scripts, chart scripts
|
|
13
|
+
"analysis" → stock/macro analysis, technical/fundamental research
|
|
14
|
+
"realtime" → live price / quote / market data queries (needs tool)
|
|
15
|
+
"general" → conceptual / educational finance questions (no tools needed)
|
|
16
|
+
"finance" → default finance chat with tool access
|
|
17
|
+
|
|
18
|
+
Usage::
|
|
19
|
+
|
|
20
|
+
from intent_classifier import classify_intent, INTENT_CODING, INTENT_ANALYSIS
|
|
21
|
+
|
|
22
|
+
intent = await classify_intent_async("写一个 AAPL 动量策略", ollama_url)
|
|
23
|
+
# → "coding"
|
|
24
|
+
|
|
25
|
+
intent = classify_intent_sync("什么是夏普比率")
|
|
26
|
+
# → "general"
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import asyncio
|
|
32
|
+
import json
|
|
33
|
+
import urllib.request
|
|
34
|
+
from typing import Optional
|
|
35
|
+
|
|
36
|
+
# ── Intent constants ──────────────────────────────────────────────────────────
|
|
37
|
+
INTENT_CODING = "coding"
|
|
38
|
+
INTENT_ANALYSIS = "analysis"
|
|
39
|
+
INTENT_REALTIME = "realtime"
|
|
40
|
+
INTENT_GENERAL = "general"
|
|
41
|
+
INTENT_FINANCE = "finance" # default / catch-all
|
|
42
|
+
|
|
43
|
+
# ── Prelude model name ────────────────────────────────────────────────────────
|
|
44
|
+
_PRELUDE_MODEL = "aria-prelude"
|
|
45
|
+
_PRELUDE_TIMEOUT = 3.0 # seconds — must be fast; fallback on timeout
|
|
46
|
+
|
|
47
|
+
# ── Prelude system prompt (mirrors the adapter training format) ───────────────
|
|
48
|
+
_PRELUDE_SYSTEM = (
|
|
49
|
+
"You are an intent classifier for a quantitative finance AI assistant.\n"
|
|
50
|
+
"Classify the user message into EXACTLY ONE of these labels:\n"
|
|
51
|
+
" coding — code generation, script writing, backtest, chart plotting\n"
|
|
52
|
+
" analysis — stock analysis, market research, technical/fundamental analysis\n"
|
|
53
|
+
" realtime — live price, current quote, today's market data\n"
|
|
54
|
+
" general — conceptual/educational finance question, no live data needed\n"
|
|
55
|
+
" finance — other finance chat, portfolio, risk, strategy discussion\n"
|
|
56
|
+
"Reply with ONLY the label word, nothing else."
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# ── Keyword fallback (tier-2, instant) ───────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
_CODING_KW = (
|
|
63
|
+
"write", "generate", "create", "script", "code", "plot", "backtest",
|
|
64
|
+
"策略", "代码", "回测", "编写", "生成", "k线", "k-line", "kline",
|
|
65
|
+
"python", "dashboard", "写一个", "生成代码", "写代码", "编写代码",
|
|
66
|
+
"analyze and save", "analysis script",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
_VISUAL_ARTIFACT_KW = (
|
|
70
|
+
"图表", "走势图", "k线图", "k线", "k-line", "kline", "candlestick",
|
|
71
|
+
"chart", "plot", "dashboard", "看板", "晨报", "日报", "周报", "月报",
|
|
72
|
+
"report", "热力图", "heatmap",
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
_VISUAL_MARKET_CONTEXT_KW = (
|
|
76
|
+
"股票", "股价", "行情", "市场", "美股", "港股", "a股", "指数",
|
|
77
|
+
"持仓", "portfolio", "回测", "财报", "earnings", "基金", "etf",
|
|
78
|
+
"资产", "组合", "市场数据", "market data",
|
|
79
|
+
)
|
|
80
|
+
_ANALYSIS_KW = (
|
|
81
|
+
"analyze", "analysis", "分析", "研究", "评估", "研判",
|
|
82
|
+
"技术面", "基本面", "走势", "趋势", "行情",
|
|
83
|
+
"stock analysis", "technical analysis", "fundamental",
|
|
84
|
+
"valuation", "estimate", "outlook", "投资建议", "买入", "卖出",
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Topics that must NOT be classified as stock technical "analysis" even if they
|
|
88
|
+
# contain analysis keywords. They get routed to "finance" (general chat) instead,
|
|
89
|
+
# because the stock-analysis prompt requires injected market data that doesn't
|
|
90
|
+
# exist for real estate or pure macroeconomic questions.
|
|
91
|
+
_NON_STOCK_ANALYSIS_TOPICS = (
|
|
92
|
+
# Real-estate
|
|
93
|
+
"房价", "楼市", "房产", "房地产", "租金", "二手房", "折旧价", "商铺",
|
|
94
|
+
# Pure macro — "宏观角度分析" should be finance chat, not stock template
|
|
95
|
+
"宏观", "宏观经济", "宏观政策", "宏观角度",
|
|
96
|
+
"货币政策", "财政政策", "gdp", "通胀", "通货膨胀", "cpi", "ppi",
|
|
97
|
+
# Non-chartable commodities / currencies
|
|
98
|
+
"黄金走势", "原油走势", "汇率走势", "美元指数",
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Pure macro/conceptual topics — no live market data needed, route to GENERAL
|
|
102
|
+
# (no tools invoked). More specific than _NON_STOCK_ANALYSIS_TOPICS: real-estate
|
|
103
|
+
# still goes to FINANCE because the user might want live data, but "宏观角度" is
|
|
104
|
+
# clearly a discussion question, not a quote lookup.
|
|
105
|
+
_MACRO_GENERAL_TOPICS = (
|
|
106
|
+
"宏观", "宏观经济", "宏观政策", "宏观角度", "宏观分析",
|
|
107
|
+
"货币政策", "财政政策", "gdp", "通胀", "通货膨胀", "cpi", "ppi",
|
|
108
|
+
"值得投资吗", "应该投资吗", "是否值得", "投资逻辑",
|
|
109
|
+
"长期展望", "未来前景", "宏观前景",
|
|
110
|
+
# Interest rate / bond macro concepts
|
|
111
|
+
"利率", "加息", "降息", "美联储政策", "央行政策",
|
|
112
|
+
"债券市场", "利差", "收益率曲线", "国债收益",
|
|
113
|
+
# Structural/sector macro
|
|
114
|
+
"产业政策", "行业监管", "政策影响", "监管政策",
|
|
115
|
+
)
|
|
116
|
+
_REALTIME_KW = (
|
|
117
|
+
"今天", "today", "现在", "now", "current", "latest", "最新",
|
|
118
|
+
"市值", "price", "股价", "quote", "行情", "涨跌", "涨幅",
|
|
119
|
+
"market cap", "how much", "what is the price",
|
|
120
|
+
"是多少", "多少钱", "多少点", "多少美元", "多少港元",
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# Question words that, when combined with finance concept terms, mean "explain X"
|
|
124
|
+
# rather than "look up X" — should route to general, not finance.
|
|
125
|
+
_QUESTION_PREFIX = (
|
|
126
|
+
"什么是", "什么叫", "how does", "what is", "what are",
|
|
127
|
+
"explain", "define", "解释", "定义", "概念", "原理", "介绍",
|
|
128
|
+
"是什么", "为什么", "区别", "difference", "如何理解", "怎么理解",
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# Finance-metric terms that combined with realtime words → live lookup
|
|
132
|
+
_METRIC_KW = (
|
|
133
|
+
"pe", "pb", "ps", "市盈率", "市净率", "市销率",
|
|
134
|
+
"eps", "净利润", "营收", "市值", "股息", "分红",
|
|
135
|
+
"ebitda", "利润率", "毛利率", "roe", "roa",
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# File path extensions — presence means it's a document/code task, not stock analysis
|
|
139
|
+
_FILE_EXT_RE = r'\S+\.(?:docx|pdf|xlsx|pptx|txt|csv|json|py|md|log)\b'
|
|
140
|
+
|
|
141
|
+
# Specific financial entity signals — must be present for "分析" to route as stock analysis.
|
|
142
|
+
# Keep to SPECIFIC company names and ticker symbols only.
|
|
143
|
+
# Generic market categories ("美股", "债券", "股市") must NOT be here — they appear in
|
|
144
|
+
# macro conceptual questions and would incorrectly block the → general route.
|
|
145
|
+
_FIN_ENTITY_KW = (
|
|
146
|
+
# Company names (CN)
|
|
147
|
+
"苹果", "谷歌", "英伟达", "微软", "特斯拉", "亚马逊", "腾讯",
|
|
148
|
+
"阿里", "百度", "比亚迪", "茅台", "招商银行", "中国平安", "恒生指数",
|
|
149
|
+
"华为", "小米", "美团", "京东", "字节", "滴滴",
|
|
150
|
+
# Company names (EN)
|
|
151
|
+
"apple", "google", "nvidia", "microsoft", "tesla", "amazon",
|
|
152
|
+
"meta", "netflix", "palantir", "snowflake",
|
|
153
|
+
# Common tickers (lowercase)
|
|
154
|
+
"aapl", "nvda", "msft", "tsla", "amzn", "googl", "meta", "baba",
|
|
155
|
+
"spy", "qqq", "iwm", "dia", "gld", "uso",
|
|
156
|
+
# Specific crypto coins (named assets, not generic "加密货币")
|
|
157
|
+
"比特币", "以太坊", "bitcoin", "ethereum", "btc", "eth", "sol", "bnb",
|
|
158
|
+
# Named indices (specific, not generic "指数")
|
|
159
|
+
"纳斯达克", "标普500", "道琼斯", "沪深300", "中证500",
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
# Broader market terms (generic) — used only to check for stock-analysis context,
|
|
163
|
+
# NOT used to block macro-general classification (avoid false negatives on conceptual Qs)
|
|
164
|
+
_MARKET_GENERAL_KW = (
|
|
165
|
+
"股票", "股市", "股价", "美股", "港股", "a股",
|
|
166
|
+
"etf", "基金", "指数", "期货", "期权", "加密", "数字货币",
|
|
167
|
+
)
|
|
168
|
+
_GENERAL_KW = (
|
|
169
|
+
"什么是", "what is", "what are", "how does", "explain", "define",
|
|
170
|
+
"解释", "定义", "概念", "原理", "介绍", "怎么", "如何理解",
|
|
171
|
+
"是什么", "为什么", "区别", "difference between",
|
|
172
|
+
"tell me about", "describe", "how to", "举例", "example",
|
|
173
|
+
)
|
|
174
|
+
_FINANCE_CONCEPT_KW = (
|
|
175
|
+
"dcf", "pe", "pb", "ps", "ev", "ebitda", "wacc", "capm",
|
|
176
|
+
"beta", "alpha", "sharpe", "sortino", "var", "cvar", "drawdown",
|
|
177
|
+
"black-scholes", "期权", "期货", "衍生品", "套利",
|
|
178
|
+
"量化", "quant", "回测", "因子", "ic值", "ir值",
|
|
179
|
+
"market cap", "市盈率", "市净率", "净利润", "估值", "valuation",
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def is_visual_market_artifact_request(message: str) -> bool:
|
|
184
|
+
"""Return True for finance-adjacent visual artifact requests.
|
|
185
|
+
|
|
186
|
+
These should prefer chart/dashboard/report workflows instead of generic
|
|
187
|
+
market-data prefetch.
|
|
188
|
+
"""
|
|
189
|
+
low = message.lower().strip()
|
|
190
|
+
if low.startswith(("/chart", "/dashboard", "/report")):
|
|
191
|
+
return True
|
|
192
|
+
if not any(k in low for k in _VISUAL_ARTIFACT_KW):
|
|
193
|
+
return False
|
|
194
|
+
if any(k in low for k in _VISUAL_MARKET_CONTEXT_KW):
|
|
195
|
+
return True
|
|
196
|
+
if any(k in low for k in _MARKET_GENERAL_KW):
|
|
197
|
+
return True
|
|
198
|
+
if any(e in low for e in _FIN_ENTITY_KW):
|
|
199
|
+
return True
|
|
200
|
+
return any(k in low for k in ("公司", "集团", "股份", "科技", "银行", "证券", "能源", "汽车"))
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def classify_intent_sync(message: str) -> str:
|
|
204
|
+
"""
|
|
205
|
+
Tier-2 keyword-based classification (synchronous, always available).
|
|
206
|
+
Returns one of the INTENT_* constants.
|
|
207
|
+
"""
|
|
208
|
+
import re as _re
|
|
209
|
+
low = message.lower().strip()
|
|
210
|
+
|
|
211
|
+
if is_visual_market_artifact_request(message):
|
|
212
|
+
return INTENT_CODING
|
|
213
|
+
|
|
214
|
+
# Bug ⑥ — file path present → document/code task, never stock analysis template
|
|
215
|
+
if _re.search(_FILE_EXT_RE, low):
|
|
216
|
+
if any(k in low for k in _CODING_KW):
|
|
217
|
+
return INTENT_CODING # "帮我写一个解析这个 csv 的脚本"
|
|
218
|
+
return INTENT_FINANCE # "分析这个文件的可行性 loads/x.docx"
|
|
219
|
+
|
|
220
|
+
has_coding = any(k in low for k in _CODING_KW)
|
|
221
|
+
has_realtime = any(k in low for k in _REALTIME_KW)
|
|
222
|
+
has_question = any(q in low for q in _QUESTION_PREFIX)
|
|
223
|
+
|
|
224
|
+
# Coding intent — but skip if phrased as a conceptual question ("X的核心是什么")
|
|
225
|
+
if has_coding and not has_question:
|
|
226
|
+
return INTENT_CODING
|
|
227
|
+
|
|
228
|
+
# Bug ⑤ — "metric是多少/how much" → realtime lookup, not finance concept chat
|
|
229
|
+
if any(m in low for m in _METRIC_KW) and any(k in low for k in _REALTIME_KW):
|
|
230
|
+
return INTENT_REALTIME
|
|
231
|
+
|
|
232
|
+
# Live-data terms must win over broad analysis words. "分析苹果今天的市场"
|
|
233
|
+
# contains both "分析" and "今天"; routing it as generic analysis lets the
|
|
234
|
+
# model answer from memory instead of using market data.
|
|
235
|
+
if has_realtime:
|
|
236
|
+
return INTENT_REALTIME
|
|
237
|
+
|
|
238
|
+
# Bug ② — "什么是X" + finance concept → general (explain, not look up)
|
|
239
|
+
if any(q in low for q in _QUESTION_PREFIX) and any(k in low for k in _FINANCE_CONCEPT_KW):
|
|
240
|
+
return INTENT_GENERAL
|
|
241
|
+
|
|
242
|
+
# Stock/market analysis — but only if a financial entity is present.
|
|
243
|
+
if any(k in low for k in _ANALYSIS_KW):
|
|
244
|
+
# Bug ③ — macro topics check (also works independently below)
|
|
245
|
+
if any(t in low for t in _MACRO_GENERAL_TOPICS):
|
|
246
|
+
return INTENT_GENERAL
|
|
247
|
+
if any(t in low for t in _NON_STOCK_ANALYSIS_TOPICS):
|
|
248
|
+
return INTENT_FINANCE
|
|
249
|
+
# Bug ① — "分析" without a financial entity = general task (project, doc, etc.)
|
|
250
|
+
if not any(e in low for e in _FIN_ENTITY_KW):
|
|
251
|
+
# Check for uppercase ticker pattern (e.g. AAPL, BTC)
|
|
252
|
+
if not _re.search(r'\b[A-Z]{2,5}\b', message):
|
|
253
|
+
return INTENT_FINANCE
|
|
254
|
+
# Bug ④ — recommendation phrasing ("应该买入吗") → finance chat, not chart analysis
|
|
255
|
+
_rec_phrases = ("应该", "是否值得", "要不要", "该不该", "值不值", "建议买", "建议卖")
|
|
256
|
+
if any(p in low for p in _rec_phrases):
|
|
257
|
+
return INTENT_FINANCE
|
|
258
|
+
return INTENT_ANALYSIS
|
|
259
|
+
|
|
260
|
+
# Bug ③ (standalone) — macro conceptual topics, unless a SPECIFIC entity is named.
|
|
261
|
+
# Generic market terms like "股市"/"美股" don't block this — "宏观经济对美股的影响"
|
|
262
|
+
# is still a macro discussion, not a specific stock query.
|
|
263
|
+
if any(t in low for t in _MACRO_GENERAL_TOPICS):
|
|
264
|
+
_has_specific = (
|
|
265
|
+
any(e in low for e in _FIN_ENTITY_KW)
|
|
266
|
+
or _re.search(r'\b[A-Z]{2,5}\b', message)
|
|
267
|
+
)
|
|
268
|
+
if not _has_specific:
|
|
269
|
+
return INTENT_GENERAL
|
|
270
|
+
|
|
271
|
+
# Finance concept terms → keep full finance context, not general
|
|
272
|
+
if any(k in low for k in _FINANCE_CONCEPT_KW):
|
|
273
|
+
return INTENT_FINANCE
|
|
274
|
+
|
|
275
|
+
if any(k in low for k in _GENERAL_KW):
|
|
276
|
+
return INTENT_GENERAL
|
|
277
|
+
|
|
278
|
+
return INTENT_FINANCE
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _prelude_available(ollama_url: str) -> bool:
|
|
282
|
+
"""Quick sync check: is aria-prelude loaded in Ollama?"""
|
|
283
|
+
try:
|
|
284
|
+
with urllib.request.urlopen(
|
|
285
|
+
ollama_url.rstrip("/") + "/api/tags", timeout=1
|
|
286
|
+
) as r:
|
|
287
|
+
data = json.loads(r.read())
|
|
288
|
+
models = [m["name"] for m in data.get("models", [])]
|
|
289
|
+
return any(m.startswith(_PRELUDE_MODEL) for m in models)
|
|
290
|
+
except Exception:
|
|
291
|
+
return False
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
async def classify_intent_async(
|
|
295
|
+
message: str,
|
|
296
|
+
ollama_url: str = "http://localhost:11434",
|
|
297
|
+
*,
|
|
298
|
+
timeout: float = _PRELUDE_TIMEOUT,
|
|
299
|
+
) -> str:
|
|
300
|
+
"""
|
|
301
|
+
Tier-1 classification using aria-prelude via Ollama.
|
|
302
|
+
Falls back to tier-2 keyword classification on any error or timeout.
|
|
303
|
+
"""
|
|
304
|
+
try:
|
|
305
|
+
import aiohttp
|
|
306
|
+
except ImportError:
|
|
307
|
+
return classify_intent_sync(message)
|
|
308
|
+
|
|
309
|
+
if is_visual_market_artifact_request(message):
|
|
310
|
+
return INTENT_CODING
|
|
311
|
+
|
|
312
|
+
if not _prelude_available(ollama_url):
|
|
313
|
+
return classify_intent_sync(message)
|
|
314
|
+
|
|
315
|
+
payload = {
|
|
316
|
+
"model": _PRELUDE_MODEL,
|
|
317
|
+
"messages": [
|
|
318
|
+
{"role": "system", "content": _PRELUDE_SYSTEM},
|
|
319
|
+
{"role": "user", "content": message},
|
|
320
|
+
],
|
|
321
|
+
"stream": False,
|
|
322
|
+
"options": {"num_predict": 8, "temperature": 0.0},
|
|
323
|
+
}
|
|
324
|
+
url = ollama_url.rstrip("/") + "/api/chat"
|
|
325
|
+
try:
|
|
326
|
+
async with aiohttp.ClientSession() as sess:
|
|
327
|
+
async with sess.post(
|
|
328
|
+
url, json=payload,
|
|
329
|
+
timeout=aiohttp.ClientTimeout(total=timeout),
|
|
330
|
+
) as resp:
|
|
331
|
+
if resp.status != 200:
|
|
332
|
+
return classify_intent_sync(message)
|
|
333
|
+
data = await resp.json()
|
|
334
|
+
raw = data.get("message", {}).get("content", "").strip().lower()
|
|
335
|
+
# Accept only known labels
|
|
336
|
+
for label in (INTENT_CODING, INTENT_ANALYSIS, INTENT_REALTIME,
|
|
337
|
+
INTENT_GENERAL, INTENT_FINANCE):
|
|
338
|
+
if label in raw:
|
|
339
|
+
if is_visual_market_artifact_request(message):
|
|
340
|
+
return INTENT_CODING
|
|
341
|
+
# Post-override: even if the prelude model says "analysis" or "finance",
|
|
342
|
+
# non-stock topics must not get the stock-analysis template.
|
|
343
|
+
if label == INTENT_ANALYSIS:
|
|
344
|
+
low_msg = message.lower()
|
|
345
|
+
if any(t in low_msg for t in _MACRO_GENERAL_TOPICS):
|
|
346
|
+
return INTENT_GENERAL # macro → educational, no tools
|
|
347
|
+
if any(t in low_msg for t in _NON_STOCK_ANALYSIS_TOPICS):
|
|
348
|
+
return INTENT_FINANCE # real-estate etc. → finance chat
|
|
349
|
+
if label == INTENT_FINANCE:
|
|
350
|
+
# Even a "finance" label from the model shouldn't invoke live tools
|
|
351
|
+
# for pure macro conceptual questions.
|
|
352
|
+
low_msg = message.lower()
|
|
353
|
+
if any(t in low_msg for t in _MACRO_GENERAL_TOPICS):
|
|
354
|
+
return INTENT_GENERAL
|
|
355
|
+
return label
|
|
356
|
+
except Exception:
|
|
357
|
+
pass
|
|
358
|
+
return classify_intent_sync(message)
|