Skip to content

Commit 03262f9

Browse files
style: apply ruff format to all source files
Auto-formatted 19 files to satisfy 'ruff format --check' CI step. No logic changes — whitespace, line length, and import spacing only. Co-Authored-By: AdaL <adal@sylph.ai>
1 parent 98b77b7 commit 03262f9

19 files changed

Lines changed: 397 additions & 250 deletions

src/agents/base_agent.py

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
from typing import Any
2828

2929
import json
30-
import os
3130
import re
3231
from pathlib import Path
3332
from typing import TypeVar
@@ -107,7 +106,7 @@ def _extract_json(raw: str) -> str:
107106
raw = raw.strip()
108107
if raw.startswith("```"):
109108
# handles ```json, ```JSON, ``` (no lang tag)
110-
raw = raw[3:] # strip opening ```
109+
raw = raw[3:] # strip opening ```
111110
if raw.startswith("json") or raw.startswith("JSON"):
112111
raw = raw[4:]
113112
# strip closing ```
@@ -136,7 +135,10 @@ def call(self, input: adal.GeneratorOutput, extra_fields: dict | None = None) ->
136135
if self.model_class.__name__ == "InvestmentThesis":
137136
data["reasoning_blocks"] = []
138137
# Clamp time_horizon_days to minimum 1 (schema requires gt=0)
139-
if data.get("time_horizon_days") is not None and int(data.get("time_horizon_days", 1)) < 1:
138+
if (
139+
data.get("time_horizon_days") is not None
140+
and int(data.get("time_horizon_days", 1)) < 1
141+
):
140142
data["time_horizon_days"] = 1
141143
# Strip unknown fields to prevent extra="forbid" crashes from LLM hallucinations.
142144
# The schema is strict by design (no junk in IPFS), but we sanitize at parse time.
@@ -315,12 +317,18 @@ def _repair_and_parse_once(
315317
extra_fields: dict[str, Any] | None = None,
316318
) -> BaseModel | None:
317319
"""Run exactly one JSON-repair pass, then parse again. Fail closed on error."""
318-
raw = getattr(output, "raw_response", None) or getattr(output, "data", None) or str(output or "")
320+
raw = (
321+
getattr(output, "raw_response", None)
322+
or getattr(output, "data", None)
323+
or str(output or "")
324+
)
319325
try:
320326
repaired_output = self.json_repair(
321327
prompt_kwargs={
322328
"schema_name": parser.model_class.__name__,
323-
"schema_json": json.dumps(parser.model_class.model_json_schema(), ensure_ascii=False),
329+
"schema_json": json.dumps(
330+
parser.model_class.model_json_schema(), ensure_ascii=False
331+
),
324332
"raw_output": _sanitize_untrusted_text(raw, max_len=16000),
325333
}
326334
)
@@ -384,7 +392,10 @@ async def analyze(self, ticker: str, prior_feedback: str = "") -> InvestmentThes
384392
_wait = 2 ** (_sub_attempt + 2)
385393
logger.warning(
386394
"Sub-agent 503 for %s/%s — retrying in %ds (attempt %d/3)",
387-
role.value, safe_ticker, _wait, _sub_attempt + 1,
395+
role.value,
396+
safe_ticker,
397+
_wait,
398+
_sub_attempt + 1,
388399
)
389400
await asyncio.sleep(_wait)
390401
continue
@@ -398,7 +409,7 @@ async def analyze(self, ticker: str, prior_feedback: str = "") -> InvestmentThes
398409
extra_fields={"agent_role": role.value},
399410
)
400411
if not isinstance(block, ReasoningBlock):
401-
raw_snippet = (getattr(block_output, 'raw_response', None) or '')[:300]
412+
raw_snippet = (getattr(block_output, "raw_response", None) or "")[:300]
402413
raise RuntimeError(
403414
f"{role} sub-agent failed to produce a ReasoningBlock after one repair pass. "
404415
f"raw: {raw_snippet}"
@@ -430,12 +441,15 @@ async def analyze(self, ticker: str, prior_feedback: str = "") -> InvestmentThes
430441
"learned_guidelines": _sanitize_untrusted_text(_guidelines_str, max_len=4000),
431442
}
432443
import asyncio as _asyncio
444+
433445
thesis_output = None
434446
for _attempt in range(3):
435447
thesis_output = self.synthesizer(prompt_kwargs=synthesis_kwargs)
436448
if getattr(thesis_output, "error", None) and "503" in str(thesis_output.error):
437449
wait = 2 ** (_attempt + 2) # 4s, 8s, 16s
438-
logger.warning("Synthesizer 503 — retrying in %ds (attempt %d/3)", wait, _attempt + 1)
450+
logger.warning(
451+
"Synthesizer 503 — retrying in %ds (attempt %d/3)", wait, _attempt + 1
452+
)
439453
await _asyncio.sleep(wait)
440454
continue
441455
break
@@ -450,7 +464,7 @@ async def analyze(self, ticker: str, prior_feedback: str = "") -> InvestmentThes
450464
extra_fields={"region": self.region.value, "ticker_or_asset": safe_ticker},
451465
)
452466
if not isinstance(thesis, InvestmentThesis):
453-
raw_snippet = (getattr(thesis_output, 'raw_response', None) or '')[:300]
467+
raw_snippet = (getattr(thesis_output, "raw_response", None) or "")[:300]
454468
raise RuntimeError(f"Synthesizer failed after one repair pass. raw: {raw_snippet}")
455469

456470
# 3. Splice the sub-agent blocks back in (synthesizer may have summarized them).
@@ -463,6 +477,7 @@ async def analyze(self, ticker: str, prior_feedback: str = "") -> InvestmentThes
463477
entry_price_1e8: int | None = None
464478
try:
465479
from data.yfinance_client import YFinanceClient as _YFC
480+
466481
# Normalize ticker for yfinance:
467482
# - Tushare .SH → Yahoo .SS (Shanghai A-shares)
468483
# - Bare crypto symbols (BTC, ETH) → BTC-USD, ETH-USD
@@ -472,7 +487,12 @@ async def analyze(self, ticker: str, prior_feedback: str = "") -> InvestmentThes
472487
price = await _YFC().get_current_price(_yf_ticker)
473488
if price and price > 0:
474489
entry_price_1e8 = int(price * 1e8)
475-
logger.debug("Live price for %s: %.4f → entry_price_1e8=%d", _yf_ticker, price, entry_price_1e8)
490+
logger.debug(
491+
"Live price for %s: %.4f → entry_price_1e8=%d",
492+
_yf_ticker,
493+
price,
494+
entry_price_1e8,
495+
)
476496
except Exception as _price_exc:
477497
logger.debug("Price fetch skipped for %s: %s", safe_ticker, _price_exc)
478498

src/agents/china_agent.py

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -124,50 +124,70 @@ def __init__(
124124
if deepseek_key and not _use_groq:
125125
# Probe DeepSeek to detect 402 (Insufficient Balance) before full analysis.
126126
import httpx as _httpx
127+
127128
try:
128129
_r = _httpx.post(
129130
"https://api.deepseek.com/v1/chat/completions",
130-
json={"model": _DEEPSEEK_MODEL, "messages": [{"role": "user", "content": "hi"}], "max_tokens": 1},
131+
json={
132+
"model": _DEEPSEEK_MODEL,
133+
"messages": [{"role": "user", "content": "hi"}],
134+
"max_tokens": 1,
135+
},
131136
headers={"Authorization": f"Bearer {deepseek_key}"},
132137
timeout=10,
133138
)
134139
if _r.status_code == 402:
135-
logger.warning("DeepSeek 402 Insufficient Balance — falling back to Groq for CN desk")
140+
logger.warning(
141+
"DeepSeek 402 Insufficient Balance — falling back to Groq for CN desk"
142+
)
136143
_use_groq = True
137144
except Exception as _probe_exc:
138-
logger.warning("DeepSeek probe failed (%s) — falling back to Groq for CN desk", _probe_exc)
145+
logger.warning(
146+
"DeepSeek probe failed (%s) — falling back to Groq for CN desk", _probe_exc
147+
)
139148
_use_groq = True
140149
if _use_groq:
141150
# Try OpenRouter (DeepSeek V4 Flash, free tier) before falling back to Groq.
142151
openrouter_key = os.environ.get("OPENROUTER_API_KEY", "")
143152
if openrouter_key:
144153
logger.info("CN desk: using OpenRouter DeepSeek V4 Flash (free tier)")
145154
from data.deepseek_client import DeepSeekClient as _DSC
155+
146156
model_client = _DSC(
147157
api_key=openrouter_key,
148158
base_url="https://openrouter.ai/api/v1",
149159
)
150160
if model_kwargs is None:
151-
model_kwargs = {"model": "deepseek/deepseek-v4-flash", "temperature": 0.2, "max_tokens": 2048}
161+
model_kwargs = {
162+
"model": "deepseek/deepseek-v4-flash",
163+
"temperature": 0.2,
164+
"max_tokens": 2048,
165+
}
152166
else:
153167
logger.warning("Using Groq fallback for CN desk")
154168
model_client = adal.GroqAPIClient() # type: ignore[attr-defined]
155169
if model_kwargs is None:
156-
model_kwargs = {"model": "llama-3.3-70b-versatile", "temperature": 0.2, "max_tokens": 2048}
170+
model_kwargs = {
171+
"model": "llama-3.3-70b-versatile",
172+
"temperature": 0.2,
173+
"max_tokens": 2048,
174+
}
157175
else:
158176
model_client = DeepSeekClient(api_key=deepseek_key)
159177
if model_kwargs is None:
160-
model_kwargs = {"model": _DEEPSEEK_MODEL, "temperature": 0.2, "max_tokens": 2048}
178+
model_kwargs = {
179+
"model": _DEEPSEEK_MODEL,
180+
"temperature": 0.2,
181+
"max_tokens": 2048,
182+
}
161183
if model_kwargs is None:
162184
model_kwargs = {"model": _DEEPSEEK_MODEL, "temperature": 0.2, "max_tokens": 2048}
163185

164186
# Call parent — it initialises self._block_parser / self._thesis_parser.
165187
super().__init__(model_client=model_client, model_kwargs=model_kwargs)
166188

167189
# Rebuild generators with Chinese-language templates.
168-
schema_str = json.dumps(
169-
InvestmentThesis.model_json_schema(), ensure_ascii=False, indent=2
170-
)
190+
schema_str = json.dumps(InvestmentThesis.model_json_schema(), ensure_ascii=False, indent=2)
171191
cn_synthesis = _CN_SYNTHESIS_TEMPLATE.replace("{{schema}}", schema_str)
172192

173193
self.sub_agent = adal.Generator(
@@ -237,6 +257,7 @@ async def get_data_sources(self, ticker: str) -> dict[AgentRole, str]:
237257
yf_ticker = _tushare_to_yf(ticker) # 600519.SH → 600519.SS
238258
try:
239259
import yfinance as yf
260+
240261
loop = asyncio.get_event_loop()
241262
yf_info = await loop.run_in_executor(
242263
None,
@@ -253,11 +274,23 @@ async def get_data_sources(self, ticker: str) -> dict[AgentRole, str]:
253274
logger.debug("yfinance snapshot skipped for %s: %s", yf_ticker, _yf_exc)
254275
price_snapshot = {}
255276

256-
daily_summary = json.dumps(daily, ensure_ascii=False, default=str)[:3000] if daily else "[无日线数据]"
257-
fina_summary = json.dumps(fina, ensure_ascii=False, default=str)[:2000] if fina else "[无财务数据]"
258-
info_summary = json.dumps(info, ensure_ascii=False, default=str)[:1500] if info else "[无公司信息]"
259-
news_summary = json.dumps(news, ensure_ascii=False, default=str)[:1500] if news else "[无新闻数据]"
260-
price_summary = json.dumps(price_snapshot, ensure_ascii=False)[:500] if price_snapshot else "[无价格快照]"
277+
daily_summary = (
278+
json.dumps(daily, ensure_ascii=False, default=str)[:3000] if daily else "[无日线数据]"
279+
)
280+
fina_summary = (
281+
json.dumps(fina, ensure_ascii=False, default=str)[:2000] if fina else "[无财务数据]"
282+
)
283+
info_summary = (
284+
json.dumps(info, ensure_ascii=False, default=str)[:1500] if info else "[无公司信息]"
285+
)
286+
news_summary = (
287+
json.dumps(news, ensure_ascii=False, default=str)[:1500] if news else "[无新闻数据]"
288+
)
289+
price_summary = (
290+
json.dumps(price_snapshot, ensure_ascii=False)[:500]
291+
if price_snapshot
292+
else "[无价格快照]"
293+
)
261294

262295
fundamental_data = (
263296
f"【股票代码】{ticker} (yfinance: {yf_ticker}) | 数据来源: {source_used}\n"

src/agents/crypto_agent.py

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ class CryptoAgent(RegionalAgent):
3232
working_language = "en"
3333
sub_agent_roles = (
3434
AgentRole.FUNDAMENTAL_ANALYST, # on-chain metrics, tokenomics
35-
AgentRole.SENTIMENT_ANALYST, # market sentiment, news
36-
AgentRole.MACRO_ANALYST, # DeFi ecosystem, TVL trends
35+
AgentRole.SENTIMENT_ANALYST, # market sentiment, news
36+
AgentRole.MACRO_ANALYST, # DeFi ecosystem, TVL trends
3737
)
3838

3939
@property
@@ -54,10 +54,15 @@ def __init__(
5454
if groq_key:
5555
model_client = adal.GroqAPIClient() # type: ignore[attr-defined]
5656
if model_kwargs is None:
57-
model_kwargs = {"model": "llama-3.3-70b-versatile", "temperature": 0.2, "max_tokens": 2048}
57+
model_kwargs = {
58+
"model": "llama-3.3-70b-versatile",
59+
"temperature": 0.2,
60+
"max_tokens": 2048,
61+
}
5862
elif gemini_key:
5963
logger.warning("GROQ_API_KEY not set — falling back to Gemini for Crypto desk")
6064
from data.gemini_client import GeminiClient
65+
6166
_gemini_model = os.environ.get("GEMINI_MODEL", "gemini-3.1-flash-lite")
6267
model_client = GeminiClient(api_key=gemini_key)
6368
if model_kwargs is None:
@@ -107,9 +112,7 @@ async def get_data_sources(self, ticker: str) -> dict[AgentRole, str]:
107112
coin_task = cg.get_coin(cg_id)
108113
# DefiLlama protocol slug often matches CoinGecko id — soft-fail if not
109114
protocol_task = dl.get_protocol(dl_slug)
110-
coin, protocol = await asyncio.gather(
111-
coin_task, protocol_task, return_exceptions=True
112-
)
115+
coin, protocol = await asyncio.gather(coin_task, protocol_task, return_exceptions=True)
113116

114117
# ---- Binance fallback when CoinGecko is rate-limited or down ----
115118
binance_ticker: dict | None = None
@@ -129,16 +132,19 @@ async def get_data_sources(self, ticker: str) -> dict[AgentRole, str]:
129132
# ---- fundamental: tokenomics + market data from CoinGecko (or Binance) ----
130133
if isinstance(coin, Exception):
131134
if binance_ticker:
132-
fundamental_data = json.dumps({
133-
"source": "Binance (CoinGecko unavailable)",
134-
"symbol": binance_ticker.get("symbol"),
135-
"lastPrice": binance_ticker.get("lastPrice"),
136-
"priceChangePercent": binance_ticker.get("priceChangePercent"),
137-
"quoteVolume": binance_ticker.get("quoteVolume"),
138-
"highPrice": binance_ticker.get("highPrice"),
139-
"lowPrice": binance_ticker.get("lowPrice"),
140-
"recent_daily_ohlcv": binance_klines,
141-
}, indent=2)[:4000]
135+
fundamental_data = json.dumps(
136+
{
137+
"source": "Binance (CoinGecko unavailable)",
138+
"symbol": binance_ticker.get("symbol"),
139+
"lastPrice": binance_ticker.get("lastPrice"),
140+
"priceChangePercent": binance_ticker.get("priceChangePercent"),
141+
"quoteVolume": binance_ticker.get("quoteVolume"),
142+
"highPrice": binance_ticker.get("highPrice"),
143+
"lowPrice": binance_ticker.get("lowPrice"),
144+
"recent_daily_ohlcv": binance_klines,
145+
},
146+
indent=2,
147+
)[:4000]
142148
else:
143149
fundamental_data = f"[CoinGecko ERROR: {coin}] [Binance fallback unavailable]"
144150
else:
@@ -181,12 +187,15 @@ async def get_data_sources(self, ticker: str) -> dict[AgentRole, str]:
181187
# ---- sentiment: derive from CoinGecko community + market sentiment ----
182188
if isinstance(coin, Exception):
183189
if binance_ticker:
184-
sentiment_data = json.dumps({
185-
"source": "Binance (CoinGecko unavailable)",
186-
"priceChangePercent_24h": binance_ticker.get("priceChangePercent"),
187-
"count": binance_ticker.get("count"), # number of trades
188-
"weightedAvgPrice": binance_ticker.get("weightedAvgPrice"),
189-
}, indent=2)
190+
sentiment_data = json.dumps(
191+
{
192+
"source": "Binance (CoinGecko unavailable)",
193+
"priceChangePercent_24h": binance_ticker.get("priceChangePercent"),
194+
"count": binance_ticker.get("count"), # number of trades
195+
"weightedAvgPrice": binance_ticker.get("weightedAvgPrice"),
196+
},
197+
indent=2,
198+
)
190199
else:
191200
sentiment_data = "[No sentiment data — CoinGecko and Binance both unavailable]"
192201
else:
@@ -197,7 +206,12 @@ async def get_data_sources(self, ticker: str) -> dict[AgentRole, str]:
197206
"community_data": coin.get("community_data", {}),
198207
"developer_data": {
199208
k: coin.get("developer_data", {}).get(k)
200-
for k in ["stars", "forks", "pull_request_contributors", "commit_count_4_weeks"]
209+
for k in [
210+
"stars",
211+
"forks",
212+
"pull_request_contributors",
213+
"commit_count_4_weeks",
214+
]
201215
},
202216
},
203217
indent=2,

src/agents/eu_agent.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,23 +119,26 @@ def __init__(
119119
gemini_key = os.environ.get("GEMINI_API_KEY", "")
120120
if gemini_key:
121121
from data.gemini_client import GeminiClient
122+
122123
model_client = GeminiClient(api_key=gemini_key)
123124
if model_kwargs is None:
124125
model_kwargs = {"model": _GEMINI_MODEL, "temperature": 0.2, "max_tokens": 2048}
125126
else:
126127
logger.warning("GEMINI_API_KEY not set — falling back to Groq for EU desk")
127128
model_client = adal.GroqAPIClient() # type: ignore[attr-defined]
128129
if model_kwargs is None:
129-
model_kwargs = {"model": "llama-3.3-70b-versatile", "temperature": 0.2, "max_tokens": 2048}
130+
model_kwargs = {
131+
"model": "llama-3.3-70b-versatile",
132+
"temperature": 0.2,
133+
"max_tokens": 2048,
134+
}
130135

131136
if model_kwargs is None:
132137
model_kwargs = {"model": _GEMINI_MODEL, "temperature": 0.2, "max_tokens": 2048}
133138

134139
super().__init__(model_client=model_client, model_kwargs=model_kwargs)
135140

136-
schema_str = json.dumps(
137-
InvestmentThesis.model_json_schema(), ensure_ascii=False, indent=2
138-
)
141+
schema_str = json.dumps(InvestmentThesis.model_json_schema(), ensure_ascii=False, indent=2)
139142
eu_synthesis = _EU_SYNTHESIS_TEMPLATE.replace("{{schema}}", schema_str)
140143

141144
self.sub_agent = adal.Generator(

0 commit comments

Comments
 (0)