mirror of
https://github.com/simonlin1212/TradingAgents-astock.git
synced 2026-10-30 09:29:56 +00:00
fix: codex 复审五处(告警死代码/资金流被滤空/只试10台/参数串味/降级丢上限)
复审的价值这轮很明显——其中两条正是上一版修复自己留下的洞。 1. 未来函数告警是死代码 v0.5.1 给 get_profit_forecast 加了告警,但它的 @tool 只暴露 ticker、 route_to_vendor 也只传 ticker,curr_date 恒为 None → 判定永远 False, 告警一次都不会触发。审计了全部 17 个工具,只有这一个漏传。 2. 历史资金流被过滤成一条不剩 东财接口只给"从今天回溯 N 天"、无 end_date。固定要 20 天再按 curr_date 过滤, 复盘较早日期时返回的 20 行全在分析日之后,滤完一行不剩——把"数据不对"变成 "没有数据"。而当时的测试夹具返回了真实请求不会包含的旧数据,把问题盖住了。 现按间隔放大窗口(上限 500),取不到时明说原因。 3. mootdx 只试 10 台就判"全网不可达" v0.5.5 为规避 bestip 全表测速而跳过它,导致 mootdx 自带表其余 28 台永远试不到。 候选表扩为 38 台(精选在前)逐台真实验证;TCP 预筛改并发(只并行化等 IO, 不改选取顺序)。实测 73.7s → 27.0s,覆盖面反而从 10 台升到 38 台。 4. 角色模型串味 role_llms 用别家 provider 时仍复用主 provider 的 llm_kwargs, reasoning_effort/thinking_level/effort 是各家私有参数,别家可能直接拒收。 现按角色 provider 过滤,通用参数保留。 5. 订阅降级后 max_tokens 丢失 max_tokens 只进 llm_kwargs 没进 fallback_spec,撞额度降级后报告照样被截断。 测试:新增 6 例,322 passed / 13 skipped / 0 failed
This commit is contained in:
@@ -6,6 +6,62 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
Breaking changes within the 0.x line are called out explicitly.
|
||||
|
||||
## [0.5.6] — 2026-08-09
|
||||
|
||||
Codex **复审**发现的五处,全部实测复现。复审的价值在这一轮体现得很清楚——其中两条
|
||||
正是上一版修复自己留下的洞。
|
||||
|
||||
### 修复:未来函数告警在生产路径上是死代码
|
||||
|
||||
v0.5.1 给 `get_profit_forecast` 加了未来函数告警,但它的 LangChain `@tool` 只暴露
|
||||
`ticker`,`route_to_vendor` 也只传 `ticker` —— `curr_date` 恒为 `None`,判定永远为
|
||||
False,**告警一次都不会触发**。模型照样把今天的一致预期当成分析日当天的事实。
|
||||
(审计发现只有这一个工具漏传,其余 16 个都正常。)
|
||||
|
||||
现已补上参数与转发,并加测试锁死"凡是数据层按 `curr_date` 做时点处理的工具,
|
||||
`@tool` 都必须暴露并转发它"。
|
||||
|
||||
### 修复:历史资金流被过滤成一条不剩
|
||||
|
||||
东财日线资金流接口只提供"从今天回溯 N 个交易日",没有 end_date。v0.5.1 仍固定要
|
||||
20 天再按 `curr_date` 过滤 —— 复盘一个较早的日期时,返回的 20 行全在分析日之后,
|
||||
**过滤后一行不剩**。把"数据不对"变成了"没有数据",比不过滤更糟;而当时的测试夹具
|
||||
恰好返回了一条真实请求不会包含的旧数据,把这个问题盖住了。
|
||||
|
||||
现按分析日与今天的间隔放大回溯窗口(上限 500,约两年),并在确实取不到时**明说原因**,
|
||||
不让正文凭空少一段。
|
||||
|
||||
### 修复:mootdx 只试 10 台就判"全网不可达"
|
||||
|
||||
v0.5.5 为了规避 bestip 的全表测速而跳过了它,但这样一来 mootdx 自带主机表里的其余
|
||||
28 台就永远试不到 —— 精选的 10 台恰好都不可用时会被误判成协议被拦,还记 5 分钟负缓存。
|
||||
|
||||
- **候选表扩为 38 台**(精选 10 台在前 + mootdx 自带主机表去重),逐台真实取数验证,
|
||||
覆盖面等同 bestip 而不做它那套要跑几分钟的测速。
|
||||
- **TCP 预筛改为并发**:多数候选是"连都连不上",串行要等满每个超时。并发只是把等 IO
|
||||
并行化,不改变选取顺序,精选表依旧优先。
|
||||
- 实测全表不可用时:**73.7s → 27.0s**(v0.5.5 是 23.7s 但只覆盖 10 台;最初 >170s)。
|
||||
二次调用命中负缓存 0ms。
|
||||
|
||||
### 修复:角色模型串味 —— 主 provider 的专属参数被带给别家
|
||||
|
||||
`role_llms` 给某个角色配了别家 provider 时,仍复用主 provider 的 `llm_kwargs`。
|
||||
`reasoning_effort`(openai)/ `thinking_level`(google)/ `effort`(anthropic)都是
|
||||
各家私有参数,塞进 qwen / glm / 自建网关的请求体里可能被直接拒收。现按角色 provider
|
||||
过滤,通用参数(`max_tokens` / `callbacks`)保留。
|
||||
|
||||
### 修复:订阅降级后 `max_tokens` 丢失
|
||||
|
||||
开着 Claude 订阅覆盖时,`max_tokens` 只进了 `llm_kwargs`,没进 `fallback_spec`。
|
||||
撞额度降级到付费 provider 后,那边用自己的默认上限——报告照样被截断,而这正是用户
|
||||
配 `max_tokens` 想避免的事。
|
||||
|
||||
### 测试
|
||||
|
||||
322 passed / 13 skipped / **0 failed**(新增 6 例)。
|
||||
|
||||
---
|
||||
|
||||
## [0.5.5] — 2026-08-09
|
||||
|
||||
Codex 审计后的五处修复。全部实测复现,无一误报。
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "tradingagents-astock"
|
||||
version = "0.5.5"
|
||||
version = "0.5.6"
|
||||
description = "A股多Agent投研框架 — 基于 TradingAgents 深度特化"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
|
||||
@@ -150,3 +150,65 @@ def test_profit_forecast_warns_on_historical_date(monkeypatch):
|
||||
out = a_stock.get_profit_forecast("600519", PAST)
|
||||
|
||||
assert "未来函数警告" in out
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# codex 复审补:防护要在**生产调用路径**上真的生效
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_profit_forecast_tool_exposes_curr_date():
|
||||
"""工具不把 curr_date 传下去,数据层的未来函数告警就是死代码。
|
||||
|
||||
v0.5.1 给 get_profit_forecast 加了告警,但 @tool 只暴露 ticker,
|
||||
curr_date 恒为 None → 告警永远不触发,模型照样把今天的一致预期当历史事实。
|
||||
"""
|
||||
from tradingagents.agents.utils.agent_utils import get_profit_forecast
|
||||
|
||||
assert "curr_date" in get_profit_forecast.args
|
||||
|
||||
|
||||
def test_every_date_aware_tool_forwards_its_date():
|
||||
"""凡是数据层按 curr_date 做时点处理的工具,@tool 都必须暴露并转发它。"""
|
||||
import inspect
|
||||
|
||||
from tradingagents.agents.utils import signal_data_tools
|
||||
|
||||
src = inspect.getsource(signal_data_tools)
|
||||
for name in ("get_profit_forecast", "get_fund_flow"):
|
||||
call = f'route_to_vendor("{name}", '
|
||||
idx = src.find(call)
|
||||
assert idx != -1, f"找不到 {name} 的路由调用"
|
||||
line = src[idx:src.find(")", idx)]
|
||||
assert "curr_date" in line, f"{name} 没有把 curr_date 转发给数据层"
|
||||
|
||||
|
||||
def test_fund_flow_widens_window_for_older_dates(fake_em, monkeypatch):
|
||||
"""分析日早于最近 20 个交易日时,必须放大回溯窗口。
|
||||
|
||||
接口只提供"从今天回溯 lmt 天",仍只要 20 天的话过滤后一行不剩——
|
||||
把"数据不对"变成"没有数据",比不过滤更糟(codex P2)。
|
||||
"""
|
||||
captured = {}
|
||||
orig = a_stock._em_get
|
||||
|
||||
def spy(url, params=None, timeout=10):
|
||||
if "push2his" in url:
|
||||
captured["lmt"] = params.get("lmt")
|
||||
return orig(url, params=params, timeout=timeout)
|
||||
|
||||
monkeypatch.setattr(a_stock, "_em_get", spy)
|
||||
a_stock.get_fund_flow("600519", PAST) # PAST = 90 天前
|
||||
|
||||
assert captured["lmt"] > 20, f"回溯窗口没有放大,仍是 {captured.get('lmt')}"
|
||||
|
||||
|
||||
def test_fund_flow_says_when_history_is_unavailable(monkeypatch):
|
||||
"""过滤后为空要说明原因,不能让正文凭空少一段。"""
|
||||
def empty_hist(url, params=None, timeout=10):
|
||||
return FakeResp({"data": {"klines": []}})
|
||||
|
||||
monkeypatch.setattr(a_stock, "_em_get", empty_hist)
|
||||
out = a_stock.get_fund_flow("600519", PAST)
|
||||
|
||||
assert "未能取到" in out
|
||||
|
||||
@@ -166,6 +166,8 @@ def handshake_fails(monkeypatch):
|
||||
state = {"factory_calls": [], "bestip_used": False}
|
||||
|
||||
monkeypatch.setattr(a_stock, "_TDX_SERVERS", servers)
|
||||
# 候选表 = 精选表 + mootdx 自带主机表;测试里只保留精选表,断言才可控
|
||||
monkeypatch.setattr(a_stock, "_candidate_tdx_servers", lambda: list(servers))
|
||||
monkeypatch.setattr(a_stock, "_mootdx_client", None)
|
||||
monkeypatch.setattr(a_stock, "_mootdx_unavailable_until", 0.0)
|
||||
monkeypatch.setattr(a_stock, "_probe_tdx", lambda ip, port, timeout=2.0: True)
|
||||
@@ -233,25 +235,25 @@ def test_bestip_skipped_when_protocol_is_the_problem(handshake_fails):
|
||||
assert handshake_fails["bestip_used"] is False
|
||||
|
||||
|
||||
def test_bestip_still_used_when_nothing_is_reachable(monkeypatch):
|
||||
"""反过来:内置表整体 TCP 都不通(IP 老化)时,bestip 仍要跑——那正是它的用途。"""
|
||||
import mootdx.quotes
|
||||
def test_candidate_list_includes_mootdx_own_hosts():
|
||||
"""候选表必须覆盖 mootdx 自带主机表,而不只是精选的那 10 台。
|
||||
|
||||
monkeypatch.setattr(a_stock, "_TDX_SERVERS", [("10.0.0.1", 7709)])
|
||||
monkeypatch.setattr(a_stock, "_mootdx_client", None)
|
||||
monkeypatch.setattr(a_stock, "_mootdx_unavailable_until", 0.0)
|
||||
monkeypatch.setattr(a_stock, "_probe_tdx", lambda ip, port, timeout=2.0: False)
|
||||
used = {"bestip": False}
|
||||
只试精选表的话,它们恰好都不可用时会被判成"全网不可达"并记 5 分钟负缓存,
|
||||
而 mootdx 自带表里可能还有活着的主机(codex 复审指出)。
|
||||
"""
|
||||
candidates = a_stock._candidate_tdx_servers()
|
||||
|
||||
class FakeQuotes:
|
||||
@staticmethod
|
||||
def factory(market="std", server=None, **kwargs):
|
||||
if kwargs.get("bestip"):
|
||||
used["bestip"] = True
|
||||
raise ConnectionResetError("nope")
|
||||
assert len(candidates) > len(a_stock._TDX_SERVERS)
|
||||
assert candidates[:len(a_stock._TDX_SERVERS)] == list(a_stock._TDX_SERVERS), (
|
||||
"实测精选的服务器应排在前面,让常见情况第一台就命中"
|
||||
)
|
||||
assert len(candidates) == len(set(candidates)), "候选表不该有重复"
|
||||
|
||||
monkeypatch.setattr(mootdx.quotes, "Quotes", FakeQuotes)
|
||||
with pytest.raises(RuntimeError, match="没有一台"):
|
||||
|
||||
def test_bestip_is_never_used(handshake_fails):
|
||||
"""不再用 bestip:它要把整张表测速一遍(实测几分钟),而候选表已逐台验证过,
|
||||
覆盖面相当且每台都是"真取到数才算通过"。"""
|
||||
with pytest.raises(RuntimeError):
|
||||
a_stock._get_mootdx_client()
|
||||
|
||||
assert used["bestip"] is True
|
||||
assert handshake_fails["bestip_used"] is False
|
||||
|
||||
@@ -229,3 +229,39 @@ def test_warns_when_bypassing_subscription(monkeypatch, caplog):
|
||||
build(cfg, monkeypatch, subscription_on=True)
|
||||
|
||||
assert any("bear" in r.getMessage() and "计费" in r.getMessage() for r in caplog.records)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# codex 复审补的两条
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_provider_specific_kwargs_not_leaked_to_other_vendors(monkeypatch):
|
||||
"""openai 的 reasoning_effort 不能带给 qwen —— 别家可能直接拒收这个参数。"""
|
||||
from tradingagents.graph import trading_graph as tg
|
||||
|
||||
created = []
|
||||
|
||||
class FakeClient:
|
||||
def __init__(self, **kw): pass
|
||||
def get_llm(self): return FakeLLM("x")
|
||||
|
||||
def fake_create(provider, model, base_url=None, **kwargs):
|
||||
created.append((provider, kwargs))
|
||||
return FakeClient()
|
||||
|
||||
monkeypatch.setattr(tg, "create_llm_client", fake_create)
|
||||
graph = tg.TradingAgentsGraph.__new__(tg.TradingAgentsGraph)
|
||||
graph.config = {
|
||||
"llm_provider": "openai",
|
||||
"role_llms": {
|
||||
"bull": {"provider": "qwen", "model": "qwen-plus"},
|
||||
"bear": {"model": "gpt-5.4-mini"}, # 同一家,应保留
|
||||
},
|
||||
}
|
||||
graph._build_role_llms({"reasoning_effort": "high", "max_tokens": 8000}, False)
|
||||
|
||||
by_provider = {p: kw for p, kw in created}
|
||||
assert "reasoning_effort" not in by_provider["qwen"], "别家 provider 收到了 openai 专属参数"
|
||||
assert by_provider["qwen"]["max_tokens"] == 8000, "通用参数不该被一起过滤掉"
|
||||
assert by_provider["openai"]["reasoning_effort"] == "high", "同一家应保留专属参数"
|
||||
|
||||
@@ -6,17 +6,24 @@ from tradingagents.dataflows.interface import route_to_vendor
|
||||
@tool
|
||||
def get_profit_forecast(
|
||||
ticker: Annotated[str, "A-stock code (e.g. 688017)"],
|
||||
curr_date: Annotated[str, "Analysis date in YYYY-MM-DD format"] = "",
|
||||
) -> str:
|
||||
"""
|
||||
Retrieve consensus EPS forecasts with forward valuation metrics.
|
||||
Returns analyst coverage count, EPS range, forward PE, PEG, and PE digestion time.
|
||||
Uses the configured signal_data vendor.
|
||||
|
||||
⚠️ curr_date 必须传:一致预期只有"当前"版本,没有历史时点值。数据层靠这个
|
||||
参数判断是不是在复盘历史,并在正文顶部加未来函数告警。不传的话告警永远不会
|
||||
触发,模型会把今天的预测当成分析日当天的事实(v0.5.5 修)。
|
||||
|
||||
Args:
|
||||
ticker (str): A-stock code
|
||||
curr_date (str): Analysis date, used to flag look-ahead when historical
|
||||
Returns:
|
||||
str: Consensus forecast report with valuation metrics
|
||||
"""
|
||||
return route_to_vendor("get_profit_forecast", ticker)
|
||||
return route_to_vendor("get_profit_forecast", ticker, curr_date)
|
||||
|
||||
|
||||
@tool
|
||||
|
||||
@@ -255,6 +255,43 @@ _mootdx_unavailable_until = 0.0
|
||||
# 大头是 bestip 全表测速,那个已经单独规避了。
|
||||
|
||||
|
||||
def _candidate_tdx_servers() -> list[tuple[str, int]]:
|
||||
"""待试的通达信服务器:先用实测精选的 `_TDX_SERVERS`,再补 mootdx 自带的完整主机表。
|
||||
|
||||
只试精选的那 10 台是不够的——它们要是恰好都不可用,而 mootdx 自带表里还有活着的
|
||||
主机,就会被判成"全网不可达"并记 5 分钟负缓存。这里把两张表合起来去重后逐台验证,
|
||||
覆盖面等同 `bestip`,但不做它那套要跑几分钟的全表测速。
|
||||
"""
|
||||
servers = list(_TDX_SERVERS)
|
||||
seen = set(servers)
|
||||
try:
|
||||
from mootdx.consts import HQ_HOSTS
|
||||
for entry in HQ_HOSTS:
|
||||
# 形如 ("深圳双线主站1", "110.41.147.114", 7709)
|
||||
host = (entry[1], entry[2]) if len(entry) >= 3 else None
|
||||
if host and host not in seen:
|
||||
seen.add(host)
|
||||
servers.append(host)
|
||||
except Exception as e: # mootdx 版本变动导致取不到就只用精选表,不影响主流程
|
||||
logger.debug("读取 mootdx HQ_HOSTS 失败,仅使用内置精选表:%s", e)
|
||||
return servers
|
||||
|
||||
|
||||
def _reachable_tdx_servers(servers, timeout: float = 2.0):
|
||||
"""并发做 TCP 预筛,返回可连的那些(保持原顺序)。
|
||||
|
||||
只是把"等超时"这件事并行化,不改变优先级:返回顺序仍是候选表顺序,所以实测
|
||||
精选的服务器依旧排在前面、依旧第一个被真实验证。
|
||||
"""
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
if not servers:
|
||||
return []
|
||||
with ThreadPoolExecutor(max_workers=min(16, len(servers))) as pool:
|
||||
flags = list(pool.map(lambda s: _probe_tdx(s[0], s[1], timeout), servers))
|
||||
return [srv for srv, ok in zip(servers, flags) if ok]
|
||||
|
||||
|
||||
def _probe_tdx(ip: str, port: int, timeout: float = 2.0) -> bool:
|
||||
"""TCP 握手探测通达信服务器端口是否开着。
|
||||
|
||||
@@ -313,10 +350,13 @@ def _get_mootdx_client():
|
||||
|
||||
from mootdx.quotes import Quotes
|
||||
|
||||
# TCP 预筛并发跑:38 台里多数是"连都连不上",串行每台要等满超时(实测整轮
|
||||
# 73.7s,首次调用像卡死)。预筛纯粹是等 IO,并发不改变选取语义——下面仍按
|
||||
# 原顺序、逐台做真实取数验证,精选表依旧优先。
|
||||
reachable = _reachable_tdx_servers(_candidate_tdx_servers())
|
||||
|
||||
tcp_ok_but_dead = 0
|
||||
for ip, port in _TDX_SERVERS:
|
||||
if not _probe_tdx(ip, port):
|
||||
continue
|
||||
for ip, port in reachable:
|
||||
# 「TCP 通但通达信协议不通」有两种表现:factory 建连时握手就被拒,
|
||||
# 或者建出来了但取不到数。**两种都要算**——只统计后者的话,计数永远是 0
|
||||
# (实测这批服务器全是在 factory 里抛 ConnectionReset),下面的快速失败
|
||||
@@ -335,12 +375,11 @@ def _get_mootdx_client():
|
||||
logger.debug("mootdx %s:%s 建连成功但取不到数,换下一台", ip, port)
|
||||
|
||||
|
||||
# fallback。bestip 会把 mootdx 内置主机表整个测速一遍,实测要几分钟,所以
|
||||
# **只在内置表整体连不上时才值得跑**——那才是它要解决的问题(IP 老化)。
|
||||
# 如果 TCP 明明连得上、只是通达信协议被拒,bestip 用的是同一套协议、同一批
|
||||
# 主机,不可能有别的结果,跑它纯粹是让用户白等几分钟(#90)。
|
||||
fallbacks = [{}] if tcp_ok_but_dead else [{"bestip": True}, {}]
|
||||
for kwargs in fallbacks:
|
||||
# 最后再试一次裸 factory(老用户的 mootdx config 里可能已存了可用 IP)。
|
||||
# ⚠️ 刻意**不用** `bestip=True`:它会把整张主机表做一遍测速,实测要几分钟。
|
||||
# 上面 `_candidate_tdx_servers()` 已经把 mootdx 自带的完整主机表逐台验证过了,
|
||||
# 覆盖面不比 bestip 差,而且每台都是"真取到数才算通过"。
|
||||
for kwargs in ({},):
|
||||
try:
|
||||
candidate = Quotes.factory(market="std", **kwargs)
|
||||
except Exception as e:
|
||||
@@ -2019,8 +2058,18 @@ def get_fund_flow(
|
||||
"https://push2his.eastmoney.com"
|
||||
"/api/qt/stock/fflow/daykline/get"
|
||||
)
|
||||
# 接口返回的是"从今天回溯 lmt 个交易日",没有 end_date 参数。复盘一个
|
||||
# 较早的日期时,若仍只要 20 天,过滤后会**一行不剩**——把"数据不对"
|
||||
# 变成"没有数据",比不过滤更糟。按分析日与今天的间隔把窗口放大到能
|
||||
# 覆盖到那一段(上限 500,够回溯约两年)。
|
||||
hist_limit = 20
|
||||
if historical:
|
||||
gap_days = (datetime.now() - datetime.strptime(
|
||||
str(curr_date)[:10], "%Y-%m-%d")).days
|
||||
# 日历日 → 交易日约 ×0.7,再多留 20 天余量
|
||||
hist_limit = min(500, 20 + int(gap_days * 0.7) + 20)
|
||||
params_hist = {
|
||||
"secid": secid, "lmt": 20, "klt": 101,
|
||||
"secid": secid, "lmt": hist_limit, "klt": 101,
|
||||
"fields1": "f1,f2,f3,f7",
|
||||
"fields2": "f51,f52,f53,f54,f55,f56,f57",
|
||||
}
|
||||
@@ -2036,7 +2085,14 @@ def get_fund_flow(
|
||||
k for k in hist_klines if k.split(",")[0][:10] <= cutoff
|
||||
]
|
||||
|
||||
if hist_klines:
|
||||
if historical and not hist_klines:
|
||||
# 说清楚是"这个日期取不到",而不是让正文里凭空少一段
|
||||
lines.append(
|
||||
f"\n## Historical Daily Fund Flow\n"
|
||||
f"({str(curr_date)[:10]} 及之前的资金流未能取到:该接口只提供"
|
||||
f"从今天回溯的窗口,分析日过早时可能已超出可回溯范围。)"
|
||||
)
|
||||
elif hist_klines:
|
||||
lines.append(
|
||||
f"\n## Historical Daily Fund Flow "
|
||||
f"(last {len(hist_klines)} trading days"
|
||||
|
||||
@@ -54,6 +54,13 @@ from .propagation import Propagator
|
||||
from .reflection import Reflector
|
||||
from .signal_processing import SignalProcessor
|
||||
|
||||
# 各家 provider 私有的参数:换了 provider 就不能带过去(别家可能直接拒收)。
|
||||
_PROVIDER_SPECIFIC_KWARGS = frozenset({
|
||||
"reasoning_effort", # openai
|
||||
"thinking_level", # google
|
||||
"effort", # anthropic
|
||||
})
|
||||
|
||||
|
||||
def _normalize_yfinance_ticker(ticker: str) -> str:
|
||||
"""Return the Yahoo Finance symbol for a ticker used by the graph.
|
||||
@@ -221,6 +228,11 @@ class TradingAgentsGraph:
|
||||
# 带上 callbacks:降级意味着**开始计费**,此时统计/成本回调
|
||||
# 反而看不到这些调用的话,恰好在花钱的时候统计是瞎的。
|
||||
**({"callbacks": self.callbacks} if self.callbacks else {}),
|
||||
# 用户显式配的输出上限也要带过去。否则撞额度降级之后,降级
|
||||
# provider 用它自己的默认上限,报告照样被截断——而这正是
|
||||
# 用户配 max_tokens 想避免的事(#91)。
|
||||
**({"max_tokens": self.config["max_tokens"]}
|
||||
if self.config.get("max_tokens") else {}),
|
||||
}
|
||||
return create_llm_client(
|
||||
provider="claude_agent_sdk",
|
||||
@@ -327,13 +339,23 @@ class TradingAgentsGraph:
|
||||
else:
|
||||
base_url = None
|
||||
|
||||
# 主 provider 的**专属**参数不能带给另一家:openai 的
|
||||
# `reasoning_effort`、google 的 `thinking_level`、anthropic 的 `effort`
|
||||
# 都是各家私有的,塞进 qwen / glm / 自建网关的请求体里可能直接被拒。
|
||||
# 通用参数(max_tokens / callbacks 等)保留。
|
||||
role_kwargs = (
|
||||
llm_kwargs if provider.lower() == str(main_provider).lower()
|
||||
else {k: v for k, v in llm_kwargs.items()
|
||||
if k not in _PROVIDER_SPECIFIC_KWARGS}
|
||||
)
|
||||
|
||||
key = (provider.lower(), spec["model"], base_url)
|
||||
if key not in cache:
|
||||
cache[key] = create_llm_client(
|
||||
provider=provider,
|
||||
model=spec["model"],
|
||||
base_url=base_url,
|
||||
**llm_kwargs,
|
||||
**role_kwargs,
|
||||
).get_llm()
|
||||
resolved[role] = cache[key]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user