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
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"""LLM provider abstraction layer.
|
|
2
|
+
|
|
3
|
+
Defines the minimal Protocol that every provider must satisfy so the
|
|
4
|
+
agent loop can call any backend (Ollama, AriaSSE, DeepSeek, etc.)
|
|
5
|
+
without importing provider-specific code.
|
|
6
|
+
|
|
7
|
+
Usage
|
|
8
|
+
-----
|
|
9
|
+
Implement the protocol on any class or pass a coroutine that matches
|
|
10
|
+
``stream()``'s signature as a bare ``provider_fn`` callable.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import asyncio
|
|
15
|
+
from dataclasses import dataclass, field
|
|
16
|
+
from typing import AsyncGenerator, Optional, Protocol, runtime_checkable
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# ── Event types emitted by LLMProvider.stream() ──────────────────────────────
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True)
|
|
22
|
+
class LLMToken:
|
|
23
|
+
"""A single text token from the model."""
|
|
24
|
+
text: str
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True)
|
|
28
|
+
class LLMThinking:
|
|
29
|
+
"""One thinking/reasoning token (extended-thinking models)."""
|
|
30
|
+
content: str
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class LLMToolCall:
|
|
35
|
+
"""Model requested a tool call."""
|
|
36
|
+
tool: str
|
|
37
|
+
params: dict
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True)
|
|
41
|
+
class LLMToolResult:
|
|
42
|
+
"""Provider reported a tool execution result summary."""
|
|
43
|
+
tool: str
|
|
44
|
+
summary: str
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass(frozen=True)
|
|
48
|
+
class LLMStatus:
|
|
49
|
+
"""Provider emitted a streaming status update."""
|
|
50
|
+
state: str
|
|
51
|
+
message: str
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass(frozen=True)
|
|
55
|
+
class LLMDone:
|
|
56
|
+
"""Stream finished. Carries the aggregated result."""
|
|
57
|
+
response: str
|
|
58
|
+
tool_calls_pending: list = field(default_factory=list)
|
|
59
|
+
usage: dict = field(default_factory=dict)
|
|
60
|
+
provider: str = "unknown"
|
|
61
|
+
success: bool = True
|
|
62
|
+
cancelled: bool = False
|
|
63
|
+
error: str = ""
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# Union type for type-checkers
|
|
67
|
+
LLMEvent = LLMToken | LLMThinking | LLMToolCall | LLMToolResult | LLMStatus | LLMDone
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _resolve_ollama_stream():
|
|
71
|
+
"""Prefer the aria_cli rebound stream_ollama when available."""
|
|
72
|
+
import sys
|
|
73
|
+
|
|
74
|
+
aria_cli = sys.modules.get("aria_cli")
|
|
75
|
+
rebound = getattr(aria_cli, "stream_ollama", None) if aria_cli else None
|
|
76
|
+
if callable(rebound):
|
|
77
|
+
return rebound
|
|
78
|
+
from apps.cli.providers.llm.ollama_stream import stream_ollama
|
|
79
|
+
|
|
80
|
+
return stream_ollama
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
async def _stream_callback_provider(invoke, *, done_provider: str) -> AsyncGenerator[LLMEvent, None]:
|
|
84
|
+
"""Convert a callback-based provider coroutine into a real async event stream."""
|
|
85
|
+
|
|
86
|
+
queue: asyncio.Queue[LLMEvent] = asyncio.Queue()
|
|
87
|
+
|
|
88
|
+
def _on_token(tok: str) -> None:
|
|
89
|
+
queue.put_nowait(LLMToken(text=tok))
|
|
90
|
+
|
|
91
|
+
def _on_thinking(content: str) -> None:
|
|
92
|
+
queue.put_nowait(LLMThinking(content=content))
|
|
93
|
+
|
|
94
|
+
def _on_tool_call(tool: str, params: dict) -> None:
|
|
95
|
+
queue.put_nowait(LLMToolCall(tool=tool, params=params))
|
|
96
|
+
|
|
97
|
+
def _on_tool_result(tool: str, summary: str) -> None:
|
|
98
|
+
queue.put_nowait(LLMToolResult(tool=tool, summary=summary))
|
|
99
|
+
|
|
100
|
+
def _on_status(state: str, message: str) -> None:
|
|
101
|
+
queue.put_nowait(LLMStatus(state=state, message=message))
|
|
102
|
+
|
|
103
|
+
task = asyncio.create_task(
|
|
104
|
+
invoke(_on_token, _on_thinking, _on_tool_call, _on_tool_result, _on_status)
|
|
105
|
+
)
|
|
106
|
+
while not task.done() or not queue.empty():
|
|
107
|
+
try:
|
|
108
|
+
yield await asyncio.wait_for(queue.get(), timeout=0.05)
|
|
109
|
+
except asyncio.TimeoutError:
|
|
110
|
+
continue
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
result = await task
|
|
114
|
+
except Exception as exc:
|
|
115
|
+
yield LLMDone(response="", provider=done_provider, success=False, error=str(exc))
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
yield LLMDone(
|
|
119
|
+
response=result.get("response", ""),
|
|
120
|
+
tool_calls_pending=result.get("tool_calls_pending", []),
|
|
121
|
+
usage=result.get("usage", {}),
|
|
122
|
+
provider=result.get("provider", done_provider),
|
|
123
|
+
success=result.get("success", False),
|
|
124
|
+
cancelled=result.get("cancelled", False),
|
|
125
|
+
error=result.get("error", ""),
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# ── Protocol ─────────────────────────────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
@runtime_checkable
|
|
132
|
+
class LLMProvider(Protocol):
|
|
133
|
+
"""Minimal interface every LLM backend must implement.
|
|
134
|
+
|
|
135
|
+
``stream()`` is an async generator that yields ``LLMEvent`` objects.
|
|
136
|
+
The final event is always ``LLMDone``; callers may break early on
|
|
137
|
+
``LLMDone`` or consume the full stream.
|
|
138
|
+
|
|
139
|
+
Parameters
|
|
140
|
+
----------
|
|
141
|
+
messages:
|
|
142
|
+
Full conversation history (list of {"role": …, "content": …} dicts).
|
|
143
|
+
tools:
|
|
144
|
+
OpenAI-format function schema list; empty list disables tool calls.
|
|
145
|
+
cancel_event:
|
|
146
|
+
asyncio.Event that, when set, signals the provider to stop.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
async def stream(
|
|
150
|
+
self,
|
|
151
|
+
messages: list,
|
|
152
|
+
tools: list,
|
|
153
|
+
*,
|
|
154
|
+
cancel_event: Optional[asyncio.Event] = None,
|
|
155
|
+
) -> AsyncGenerator[LLMEvent, None]:
|
|
156
|
+
...
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
# ── Thin adapters (wrap existing callables as LLMProvider) ───────────────────
|
|
160
|
+
|
|
161
|
+
class OllamaProvider:
|
|
162
|
+
"""Wraps ``stream_ollama`` as an ``LLMProvider``.
|
|
163
|
+
|
|
164
|
+
Import lazily to avoid circular dependencies — ``stream_ollama`` lives in
|
|
165
|
+
the same providers package and rebinds globals from aria_cli at startup.
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
def __init__(
|
|
169
|
+
self,
|
|
170
|
+
ollama_url: str,
|
|
171
|
+
model: str,
|
|
172
|
+
*,
|
|
173
|
+
system_override: Optional[str] = None,
|
|
174
|
+
show_market_prefetch_status: bool = True,
|
|
175
|
+
) -> None:
|
|
176
|
+
self.ollama_url = ollama_url
|
|
177
|
+
self.model = model
|
|
178
|
+
self.system_override = system_override
|
|
179
|
+
self.show_market_prefetch_status = show_market_prefetch_status
|
|
180
|
+
|
|
181
|
+
async def stream(
|
|
182
|
+
self,
|
|
183
|
+
messages: list,
|
|
184
|
+
tools: list,
|
|
185
|
+
*,
|
|
186
|
+
cancel_event: Optional[asyncio.Event] = None,
|
|
187
|
+
) -> AsyncGenerator[LLMEvent, None]:
|
|
188
|
+
stream_ollama = _resolve_ollama_stream()
|
|
189
|
+
|
|
190
|
+
# Extract last user message as the prompt; the rest is history
|
|
191
|
+
history = [m for m in messages if not (m.get("role") == "user" and m is messages[-1])]
|
|
192
|
+
prompt = messages[-1].get("content", "") if messages else ""
|
|
193
|
+
|
|
194
|
+
async def _invoke(on_token, on_thinking, on_tool_call, on_tool_result, _on_status):
|
|
195
|
+
return await stream_ollama(
|
|
196
|
+
self.ollama_url,
|
|
197
|
+
prompt,
|
|
198
|
+
history,
|
|
199
|
+
model=self.model,
|
|
200
|
+
on_token=on_token,
|
|
201
|
+
on_thinking=on_thinking,
|
|
202
|
+
on_tool_call=on_tool_call,
|
|
203
|
+
on_tool_result=on_tool_result,
|
|
204
|
+
cancel_event=cancel_event,
|
|
205
|
+
enable_tools=bool(tools),
|
|
206
|
+
system_override=self.system_override,
|
|
207
|
+
show_market_prefetch_status=self.show_market_prefetch_status,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
async for event in _stream_callback_provider(_invoke, done_provider="ollama"):
|
|
211
|
+
yield event
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class AriaSSEProvider:
|
|
215
|
+
"""Wraps ``stream_chat`` (Aria cloud SSE) as an ``LLMProvider``."""
|
|
216
|
+
|
|
217
|
+
def __init__(
|
|
218
|
+
self,
|
|
219
|
+
api_url: str,
|
|
220
|
+
model: str,
|
|
221
|
+
*,
|
|
222
|
+
auth_token: Optional[str] = None,
|
|
223
|
+
thinking_mode: str = "auto",
|
|
224
|
+
user_context: Optional[dict] = None,
|
|
225
|
+
system_override: Optional[str] = None,
|
|
226
|
+
project_context: str = "",
|
|
227
|
+
) -> None:
|
|
228
|
+
self.api_url = api_url
|
|
229
|
+
self.model = model
|
|
230
|
+
self.auth_token = auth_token
|
|
231
|
+
self.thinking_mode = thinking_mode
|
|
232
|
+
self.user_context = user_context or {}
|
|
233
|
+
self.system_override = system_override
|
|
234
|
+
self.project_context = project_context
|
|
235
|
+
|
|
236
|
+
async def stream(
|
|
237
|
+
self,
|
|
238
|
+
messages: list,
|
|
239
|
+
tools: list,
|
|
240
|
+
*,
|
|
241
|
+
cancel_event: Optional[asyncio.Event] = None,
|
|
242
|
+
) -> AsyncGenerator[LLMEvent, None]:
|
|
243
|
+
from apps.cli.providers.llm.sse_stream import stream_chat
|
|
244
|
+
|
|
245
|
+
history = [m for m in messages if not (m.get("role") == "user" and m is messages[-1])]
|
|
246
|
+
prompt = messages[-1].get("content", "") if messages else ""
|
|
247
|
+
|
|
248
|
+
uctx = dict(self.user_context)
|
|
249
|
+
if self.system_override:
|
|
250
|
+
uctx["system_role_override"] = self.system_override
|
|
251
|
+
|
|
252
|
+
async def _invoke(on_token, on_thinking, on_tool_call, on_tool_result, on_status):
|
|
253
|
+
return await stream_chat(
|
|
254
|
+
self.api_url,
|
|
255
|
+
prompt,
|
|
256
|
+
history,
|
|
257
|
+
model=self.model,
|
|
258
|
+
thinking_mode=self.thinking_mode,
|
|
259
|
+
user_context=uctx or None,
|
|
260
|
+
auth_token=self.auth_token,
|
|
261
|
+
on_token=on_token,
|
|
262
|
+
on_thinking=on_thinking,
|
|
263
|
+
on_tool_call=on_tool_call,
|
|
264
|
+
on_tool_result=on_tool_result,
|
|
265
|
+
on_status=on_status,
|
|
266
|
+
cancel_event=cancel_event,
|
|
267
|
+
project_context=self.project_context,
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
async for event in _stream_callback_provider(_invoke, done_provider="aria_sse"):
|
|
271
|
+
yield event
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Provider routing + fallback DECISIONS for the chat loop (pure, testable).
|
|
2
|
+
|
|
3
|
+
Extracted from ``aria_cli.send_message`` as the keystone for the documented
|
|
4
|
+
runtime next step ("route the whole CLI tool loop through run_agent"). The
|
|
5
|
+
*decision* of which provider a round uses, and whether to fall back, is pure
|
|
6
|
+
logic; pulling it out of the streaming machinery lets it be unit-tested and
|
|
7
|
+
reused as a ``provider_fn`` selector without touching the live REPL path.
|
|
8
|
+
|
|
9
|
+
Routing rules (mirrors send_message):
|
|
10
|
+
• local_mode → always local Ollama
|
|
11
|
+
• cloud-named model ("provider/x") → cloud backend (AriaSSE)
|
|
12
|
+
• ollama-named model ("x:y") → cloud backend ONLY if backend_chat forces
|
|
13
|
+
it; otherwise skip the backend stub and go
|
|
14
|
+
straight to the local/cloud fallback chain
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from typing import Callable, Optional
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def is_cloud_model(model: str) -> bool:
|
|
23
|
+
"""Cloud models are provider-prefixed, e.g. ``openai/gpt-4.5``, ``anthropic/…``."""
|
|
24
|
+
return "/" in (model or "")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def is_ollama_model(model: str) -> bool:
|
|
28
|
+
"""Ollama models have no ``/`` (``gpt-oss:120b-cloud``, ``deepseek-r1:14b``)."""
|
|
29
|
+
return "/" not in (model or "")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def force_backend(config: dict, api_url: Optional[str]) -> bool:
|
|
33
|
+
"""backend_chat=True routes ALL chat through the self-hosted backend (which
|
|
34
|
+
proxies to its own Ollama + collects training data), requiring an api_url."""
|
|
35
|
+
return bool(config.get("backend_chat")) and bool(api_url)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def first_round_route(model: str, config: dict, api_url: Optional[str]) -> str:
|
|
39
|
+
"""Return where the first round's generation goes: ``ollama`` | ``cloud`` | ``skip``.
|
|
40
|
+
|
|
41
|
+
``skip`` means the backend would only return a stub for this (ollama-named)
|
|
42
|
+
model, so the round is skipped and the fallback chain runs directly.
|
|
43
|
+
"""
|
|
44
|
+
if config.get("local_mode", False):
|
|
45
|
+
return "ollama"
|
|
46
|
+
if is_cloud_model(model) or force_backend(config, api_url):
|
|
47
|
+
return "cloud"
|
|
48
|
+
return "skip"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def is_placeholder_response(
|
|
52
|
+
response: str,
|
|
53
|
+
token_count: int,
|
|
54
|
+
stub_detector: Optional[Callable[[str], bool]] = None,
|
|
55
|
+
) -> bool:
|
|
56
|
+
"""A 'successful' result that is actually empty / canned / a backend stub."""
|
|
57
|
+
resp = response or ""
|
|
58
|
+
if len(resp) < 20:
|
|
59
|
+
return True
|
|
60
|
+
if stub_detector is not None and stub_detector(resp):
|
|
61
|
+
return True
|
|
62
|
+
# Long "response" with ~no streamed tokens ⇒ canned backend reply, not a generation.
|
|
63
|
+
if token_count <= 2 and len(resp) > 80:
|
|
64
|
+
return True
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def should_fallback(route: str, result: dict, *, is_placeholder: bool) -> bool:
|
|
69
|
+
"""Whether to run the local/cloud fallback chain after the primary round.
|
|
70
|
+
|
|
71
|
+
Keyed on the *route* (not the model name), so a forced-backend round that
|
|
72
|
+
genuinely succeeded does NOT fall back — which is the bug-free version of the
|
|
73
|
+
old ``_should_fallback`` that keyed on ``is_ollama_model`` and could discard a
|
|
74
|
+
good backend answer (causing a re-run / hang).
|
|
75
|
+
"""
|
|
76
|
+
if route == "skip":
|
|
77
|
+
return True
|
|
78
|
+
if not result.get("success") and not result.get("cancelled"):
|
|
79
|
+
return True
|
|
80
|
+
return is_placeholder
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""LLM streaming providers."""
|