mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 19:32:01 +00:00
核查工具箱收尾:连接的方面补上另一侧(关联条件里出现的别的表,CROSS JOIN 写它之前的来源),双盲复算对照数据目录里的关系时用得上;只读 agent 收尾前先拿到收尾用的模型再预留金额,模型拿不到时不在每日账上白记一次调用;收尾消息的参数改名 limit_words,说清是哪个上限的说法
This commit is contained in:
@@ -217,12 +217,14 @@ class Part:
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class JoinFacet:
|
||||
"""一次连接。kind:inner / left / right / full / cross / semi / anti / natural。on 是关联条件的合取式排好序后
|
||||
用 AND 连起来的写法,没有条件是 None。"""
|
||||
"""一次连接。kind:inner / left / right / full / cross / semi / anti / natural。table 是关联进来的那一侧,other
|
||||
是另一侧:关联条件里出现的别的表(给人看的真实表名);没有条件的(CROSS JOIN)是在它之前出现的各个来源。on 是
|
||||
关联条件的合取式排好序后用 AND 连起来的写法,没有条件是 None。"""
|
||||
|
||||
kind: str
|
||||
table: Part
|
||||
on: Part | None = None
|
||||
other: tuple[str, ...] = ()
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -688,7 +690,8 @@ class _Statement:
|
||||
conds = [self.canonical(exp.EQ(this=exp.column(ident.name, table=first_alias),
|
||||
expression=exp.column(ident.name, table=alias)), sources)
|
||||
for ident in join.args["using"]]
|
||||
drafts.append({"kind": _join_kind(join), "slot": slot, "source": sources.get(slot), "conds": conds})
|
||||
drafts.append({"kind": _join_kind(join), "slot": slot, "source": sources.get(slot), "conds": conds,
|
||||
"before": [s.name for key, s in list(sources.items())[:i + 1] if key != slot]})
|
||||
return drafts
|
||||
|
||||
def _joins_on_where(self, cond: Any, sources: dict[str, _Source], drafts: list[dict[str, Any]]) -> bool:
|
||||
@@ -720,7 +723,10 @@ class _Statement:
|
||||
on = Part(key=" AND ".join(t[0] for t in triples), text=" AND ".join(t[2] for t in triples),
|
||||
loose=" AND ".join(sorted(t[1] for t in triples)), full=" AND ".join(t[2] for t in triples)) \
|
||||
if triples else None
|
||||
return JoinFacet(kind=draft["kind"], table=table, on=on)
|
||||
mentioned = {table for c in draft["conds"] for table, _ in self.part(c, sources).columns}
|
||||
other = [name for name in dict.fromkeys(t for t in mentioned if t) if name != table.text] if draft["conds"] \
|
||||
else draft["before"]
|
||||
return JoinFacet(kind=draft["kind"], table=table, on=on, other=tuple(sorted(other)))
|
||||
|
||||
def _time(self, cond: Any, sources: dict[str, _Source]) -> TimePredicate | None:
|
||||
"""时间条件:一边恰好一列(可以套着日期函数、CAST),另一边没有列;列名像日期,或者用了日期函数、像日期的字符串。"""
|
||||
|
||||
@@ -323,12 +323,13 @@ def _text_of(messages: list[BaseMessage]) -> str:
|
||||
for m in messages)
|
||||
|
||||
|
||||
def _final_messages(system: str, task: str, queries: list[dict[str, Any]], said: str, stopped: str | None
|
||||
def _final_messages(system: str, task: str, queries: list[dict[str, Any]], said: str, limit_words: str | None
|
||||
) -> list[BaseMessage]:
|
||||
"""收尾那次调用的消息:任务、查到的结果(每次查询列出行号)、模型最后说的话,以及交结果的规则。"""
|
||||
"""收尾那次调用的消息:任务、查到的结果(每次查询列出行号)、模型最后说的话,以及交结果的规则。
|
||||
limit_words:触顶时是哪个上限(「最多查询 6 次」),告诉模型为什么不能再查。"""
|
||||
parts = [f"任务:\n{task.strip()}"]
|
||||
if stopped:
|
||||
parts.append(f"已达上限({stopped}),不能再查询,按已经查到的结果交结果。")
|
||||
if limit_words:
|
||||
parts.append(f"已达上限({limit_words}),不能再查询,按已经查到的结果交结果。")
|
||||
parts.append(f"你查到的结果:\n{_digest(queries) if queries else '(没有成功的查询)'}")
|
||||
parts.append(f"你最后的说明(只作参考,数以查询结果为准):\n{said.strip() or '(没有)'}")
|
||||
parts.append(VERIFY_FINAL_PROMPT)
|
||||
@@ -541,7 +542,12 @@ async def run_verify_agent(
|
||||
await _announce(emit, EventType.LOG, level="warn", code="verify_limit", limit=stopped,
|
||||
message=f"核查已达上限({words}),已停止查询,按已有结果收尾")
|
||||
|
||||
# ---- 收尾:按结构交结果
|
||||
# ---- 收尾:按结构交结果。关掉 thinking:结构化输出靠强制工具调用,和思考模式不兼容
|
||||
try:
|
||||
async with SessionLocal() as session:
|
||||
settler, _ = await get_chat_model(session, spec.model_copy(update={"thinking": "off"}))
|
||||
except Exception as e: # noqa: BLE001
|
||||
return outcome(model=model_id, limit=stopped, answered=answered, error=f"核查模型不可用({describe_exception(e)})")
|
||||
schema = result_schema(output_schema)
|
||||
final = _final_messages(head, task, queries, said, words)
|
||||
estimate = budget.estimate(_text_of(final), _OUT_FINAL)
|
||||
@@ -554,12 +560,6 @@ async def run_verify_agent(
|
||||
remaining = left()
|
||||
grace = None if remaining is None else max(remaining, SETTLE_GRACE_S)
|
||||
finish = None if grace is None else time.monotonic() + grace
|
||||
try:
|
||||
async with SessionLocal() as session:
|
||||
settler, _ = await get_chat_model(session, spec.model_copy(update={"thinking": "off"}))
|
||||
except Exception as e: # noqa: BLE001
|
||||
await spend.settle(day or "", -estimate)
|
||||
return outcome(model=model_id, limit=stopped, answered=answered, error=f"核查模型不可用({describe_exception(e)})")
|
||||
|
||||
def until_finish() -> float | None:
|
||||
return None if finish is None else finish - time.monotonic()
|
||||
|
||||
@@ -116,7 +116,7 @@ def test_tables_are_the_real_tables_without_cte_names():
|
||||
def test_joins_carry_kind_both_sides_and_a_normalized_condition():
|
||||
f = facets("SELECT o.id FROM orders o LEFT JOIN refunds r ON r.order_id = o.id AND r.state = 'done'")
|
||||
[join] = f.joins
|
||||
assert join.kind == "left" and join.table.text == "refunds"
|
||||
assert join.kind == "left" and join.table.text == "refunds" and join.other == ("orders",)
|
||||
# 别名换成真实表名,两边排好顺序,几个条件按 key 排序后用 AND 连起来
|
||||
assert join.on.text == "orders.id = refunds.order_id AND refunds.state = 'done'"
|
||||
same = facets("SELECT orders.id FROM orders LEFT OUTER JOIN refunds ON refunds.state = 'done' "
|
||||
@@ -128,6 +128,9 @@ def test_a_comma_join_is_the_same_as_an_inner_join():
|
||||
comma = facets("SELECT a.x FROM orders a, stores b WHERE a.store_id = b.id AND b.city = '虚构市'")
|
||||
inner = facets("SELECT orders.x FROM orders JOIN stores ON stores.id = orders.store_id WHERE stores.city = '虚构市'")
|
||||
assert comma.joins == inner.joins and comma.joins[0].kind == "inner"
|
||||
assert comma.joins[0].table.text == "stores" and comma.joins[0].other == ("orders",)
|
||||
cross = facets("SELECT 1 FROM orders o CROSS JOIN stores s")
|
||||
assert (cross.joins[0].kind, cross.joins[0].on, cross.joins[0].other) == ("cross", None, ("orders",))
|
||||
assert [p.key for p in comma.where] == [p.key for p in inner.where] == ["stores.city = '虚构市'"]
|
||||
|
||||
|
||||
|
||||
@@ -212,7 +212,8 @@ async def test_writes_are_refused_on_a_writable_source(monkeypatch, shop):
|
||||
script = Script(monkeypatch, [q(name, "DELETE FROM orders WHERE is_deleted = 1", "w1"),
|
||||
q(name, "WITH k AS (SELECT 1) UPDATE orders SET amount = 0", "w2"),
|
||||
q(name, VALID_SUM, "c1")],
|
||||
{"value": {"alias": "Q1", "row": 0, "column": "total"}, "verdict": "一致", "rationale": "见 [[see:Q1]]。"})
|
||||
{"value": {"alias": "Q1", "row": 0, "column": "total"}, "verdict": "一致",
|
||||
"rationale": "见 [[see:Q1]]。"})
|
||||
out = await run(name)
|
||||
seen = script.tool_results()
|
||||
assert seen["w1"].startswith("SQL 被拒绝:") and seen["w2"].startswith("SQL 被拒绝:")
|
||||
|
||||
Reference in New Issue
Block a user