mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 04:21:57 +00:00
表助手:SDK 不再自己重试(同起草,网络错误、429、5xx 只由表助手在还没输出时重试,退避 3、10 秒);测试补上取模型的规格(起草设置、保留思考、额度 32768 / 8192、单次时限、不重试)和两个工具在 OpenAI 兼容、Anthropic 模型上都绑得上;query_snapshot_tables 不进 catalog 的 __all__(按模块属性取用,少和起草那边的改动撞在一起)
This commit is contained in:
@@ -2351,8 +2351,7 @@ __all__ = [
|
||||
"fk_relations", "frozen_catalog", "infer_name_relations", "initial_status", "is_date_column", "is_profile_note",
|
||||
"join_paths",
|
||||
"make_item", "merge_notes", "name_words", "now_iso", "parse_path", "plan_patch", "profile_relation_holds",
|
||||
"query_snapshot_meta", "query_snapshot_tables", "read_catalog", "read_entry", "relation_graph", "relation_id",
|
||||
"render_table_index",
|
||||
"query_snapshot_meta", "read_catalog", "read_entry", "relation_graph", "relation_id", "render_table_index",
|
||||
"render_table_notes", "resolve_draft_model", "resolve_table_name", "review_entry", "review_item", "same_notes",
|
||||
"same_profile_note", "save_human_edit", "save_patch", "status_counts", "structure_relation_ids",
|
||||
"system_notes_source", "table_usage", "unreviewed_status", "validate_notes", "visible_notes", "write_entry",
|
||||
|
||||
@@ -75,8 +75,8 @@ QUERY_ROWS = 50
|
||||
SHOWN_ROWS = 10
|
||||
CELL_MAX = 300
|
||||
TOOL_CHARS = 12_000
|
||||
#: 网络错误、429、5xx 重试前等几秒(依次取用,个数就是最多重试几次)
|
||||
RETRY_DELAYS_S = (2.0, 5.0)
|
||||
#: 网络错误、429、5xx 重试前等几秒(依次取用,个数就是最多重试几次)。在对话里等,比起草(10、30 秒)短
|
||||
RETRY_DELAYS_S = (3.0, 10.0)
|
||||
#: 等模型的时候每隔几秒发一行 SSE 注释
|
||||
HEARTBEAT_S = 10.0
|
||||
#: 背景里「目录里还没有中文名和含义的列」最多列几个;别的表指过来的关联关系最多列几条
|
||||
@@ -257,8 +257,9 @@ async def resolve_model(session: AsyncSession) -> tuple[Any, str]:
|
||||
provider, choice = await resolve_draft_provider(session, none_reason=ERROR_NO_MODEL, mock_reason=ERROR_NO_MODEL)
|
||||
except DraftModelUnavailable as e:
|
||||
raise catalog.CatalogModelUnavailable(str(e)) from e
|
||||
# SDK 自己不重试(同起草):网络错误、429、5xx 由这里在还没输出时重试,两层叠起来实际次数就对不上了
|
||||
spec = ModelSpec(provider=choice.provider, model=choice.model, max_tokens=catalog.MODEL_MAX_TOKENS, temperature=0,
|
||||
timeout=int(catalog.MODEL_TIMEOUT_S))
|
||||
timeout=int(catalog.MODEL_TIMEOUT_S), max_retries=0)
|
||||
try:
|
||||
big = build_chat_model(provider, spec)
|
||||
small = build_chat_model(provider, spec.model_copy(update={"max_tokens": catalog.MODEL_FALLBACK_MAX_TOKENS}))
|
||||
|
||||
@@ -228,6 +228,69 @@ async def test_unavailable_draft_model_is_409_with_the_draft_wording(client, tmp
|
||||
assert r.json()["code"] == "catalog_draft_model_unavailable" and "模型接入" in r.json()["detail"]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def mem(monkeypatch):
|
||||
"""单独的内存库:接入和设置只在这个用例里。"""
|
||||
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
||||
from sqlalchemy.pool import StaticPool
|
||||
|
||||
from app.db.base import Base
|
||||
|
||||
for key in ("ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN", "OPENAI_API_KEY", "ANTHROPIC_BASE_URL"):
|
||||
monkeypatch.delenv(key, raising=False)
|
||||
engine = create_async_engine("sqlite+aiosqlite:///:memory:", poolclass=StaticPool)
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
session = async_sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)()
|
||||
yield session
|
||||
await session.close()
|
||||
await engine.dispose()
|
||||
|
||||
|
||||
async def _provider(session, kind: str) -> None:
|
||||
from app.core.crypto import encrypt
|
||||
from app.data import draft_model
|
||||
from app.db.models import Provider, Setting
|
||||
|
||||
session.add(Provider(name="起草网关", kind=kind, base_url="http://gw.example.test/v1" if kind != "anthropic" else None,
|
||||
api_key=encrypt("sk-test"), models=[{"id": "模型甲"}], default_model="模型甲", enabled=True,
|
||||
extra={}))
|
||||
session.add(Setting(key=draft_model.CATALOG_DRAFT_SETTING_KEY, value={"provider": "起草网关", "model": None}))
|
||||
await session.commit()
|
||||
|
||||
|
||||
async def test_model_follows_the_draft_setting_keeps_thinking_and_a_big_budget(mem, monkeypatch):
|
||||
from app.providers import factory
|
||||
|
||||
await _provider(mem, "openai_compatible")
|
||||
specs: list[Any] = []
|
||||
real = factory.build_chat_model
|
||||
|
||||
def spy(provider, spec):
|
||||
specs.append(spec)
|
||||
return real(provider, spec)
|
||||
|
||||
monkeypatch.setattr(factory, "build_chat_model", spy)
|
||||
model, model_id = await catalog_assistant.resolve_model(mem)
|
||||
assert model_id == "模型甲" and model.max_tokens == catalog.MODEL_MAX_TOKENS == 32768
|
||||
mine = specs[-2:] # 前两份是 catalog.resolve_draft_model 查能不能用时建的
|
||||
assert [s.max_tokens for s in mine] == [catalog.MODEL_MAX_TOKENS, catalog.MODEL_FALLBACK_MAX_TOKENS]
|
||||
assert all(s.thinking is None and s.temperature == 0 and s.max_retries == 0 for s in mine)
|
||||
assert all(s.timeout == catalog.MODEL_TIMEOUT_S for s in mine)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("kind", ["openai_compatible", "anthropic"])
|
||||
async def test_tools_bind_on_real_chat_models(mem, kind):
|
||||
"""两个工具的定义各家的模型都收(只绑定,不发请求)。"""
|
||||
await _provider(mem, kind)
|
||||
model, _ = await catalog_assistant.resolve_model(mem)
|
||||
tools = [catalog_assistant.QUERY_TOOL_PROMPT, catalog_assistant.PROPOSE_TOOL_PROMPT]
|
||||
bound = model.bind_tools(tools)
|
||||
given = bound.model.kwargs["tools"]
|
||||
names = [t.get("name") or t["function"]["name"] for t in given]
|
||||
assert names == ["run_query", "propose_changes"]
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 背景
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user