diff --git a/backend/app/data/introspect.py b/backend/app/data/introspect.py index 27ab77e1..ea8875be 100644 --- a/backend/app/data/introspect.py +++ b/backend/app/data/introspect.py @@ -447,6 +447,15 @@ def describe_table(source: Any, table: str, *, catalog: Mapping[str, Mapping[str available = "、".join( m.get("qualified", n) for n, m in list(tables.items())[:30] ) + if cache.get("truncated"): + # 只探查了一部分表:没探查到不等于没有。以前照样回「中没有 X」,模型据此换了别的表或者编答案 + total = cache.get("total") + of = f"共 {total} 个对象," if isinstance(total, int) else "" + return ( + f"数据源「{source.name}」{of}只探查了其中 {len(tables)} 个,{table} 不在已探查的范围内," + "无法判断是否存在。请直接查询数据字典(information_schema.tables / columns,SQLite 用 " + f"sqlite_master)确认它的结构。已探查的对象包括:{available}" + ) return f"数据源「{source.name}」中没有 {table}。现有的对象:{available}" head = f"{'视图' if meta.get('is_view') else '表'} {meta.get('qualified', table)}" diff --git a/backend/tests/test_catalog_schema_truncated.py b/backend/tests/test_catalog_schema_truncated.py index b16489d2..4477b7f8 100644 --- a/backend/tests/test_catalog_schema_truncated.py +++ b/backend/tests/test_catalog_schema_truncated.py @@ -114,3 +114,20 @@ async def test_assistant_context_of_a_complete_schema_says_nothing_extra(narrow) (row,) = ctx.op()["sources"] assert row["total"] == 3 and "explored" not in row assert "没有探查到" not in ctx.section() + + +async def test_schema_tool_does_not_deny_a_table_that_was_not_introspected(wide, narrow): + """只探查了一部分表时,查一张没探查到的表不能说「没有」:它可能就在没探查到的那部分里。 + 以前回「数据源中没有 X」,模型据此改用别的表,或者编一个答案。""" + from app.data.introspect import describe_table + + row = await _row(wide[0]) + explored = set((row.schema_cache or {}).get("tables") or {}) + missing = next(f"t{i:03d}" for i in range(WIDE) if f"t{i:03d}" not in explored) + text = describe_table(row, missing) + assert "中没有" not in text + assert "只探查了" in text and "information_schema" in text + + # 探查完整的库:查不存在的表照旧说没有 + small = await _row(narrow[0]) + assert "中没有 nope" in describe_table(small, "nope")