feat:LLM 分析文件并生成结构化建议

This commit is contained in:
2026-10-10 06:07:56 -07:00
parent b90842237f
commit e3699d1a30
22 changed files with 457 additions and 327 deletions
+19 -10
View File
@@ -7,7 +7,7 @@ apply_actions 里的 $query 也通过 DataContext.query 执行,见 tools.py
上下文类型因 agent 而异,所以写 ToolRuntime[Any](不写类型参数时按 None 处理,序列化会告警)
"""
from dataclasses import dataclass
from dataclasses import dataclass, field
from pathlib import Path
from typing import Annotated, Any
@@ -17,7 +17,8 @@ from pydantic import Field
from app.config import settings
from app.datafiles.query import MAX_ROWS, QueryError, QueryResult, run_query
from app.schemas.data_file import RequestDataFile, RequestDataTable
from app.datafiles.storage import table_path
from app.schemas.data_file import DataFileRecord, DataTable
# 列超过这么多时,不指定 columns 只返回列名和类型,避免一次塞进太多概况
BRIEF_COLUMNS = 50
@@ -25,9 +26,12 @@ BRIEF_COLUMNS = 50
@dataclass(frozen=True)
class DataContext:
files: tuple[RequestDataFile, ...] = ()
"""本次运行可用的数据文件;root 是数据文件的根目录,Parquet 路径由它和文件记录算出"""
def find(self, name: str) -> tuple[RequestDataFile, RequestDataTable] | None:
files: tuple[DataFileRecord, ...] = ()
root: Path = field(default_factory=lambda: settings.data_file_dir)
def find(self, name: str) -> tuple[DataFileRecord, DataTable] | None:
for file in self.files:
for table in file.tables:
if table.table == name:
@@ -36,7 +40,7 @@ class DataContext:
def paths(self) -> dict[str, Path]:
return {
table.table: Path(table.path)
table.table: table_path(self.root, file.file_id, table)
for file in self.files
for table in file.tables
}
@@ -86,7 +90,7 @@ def describe_data(
if found is None:
return _error(runtime, f"没有表 {table},可用的表:{_table_list(data)}")
file, item = found
profiled: list[dict[str, Any]] = item.profile["columns"]
profiled = item.profile.columns
result: dict[str, Any] = {
"table": item.table,
"label": item.label,
@@ -94,20 +98,25 @@ def describe_data(
}
if columns is None and len(profiled) > BRIEF_COLUMNS:
result["columns"] = [
{key: column[key] for key in ("name", "type", "format") if key in column}
column.model_dump(
mode="json", include={"name", "type", "format"}, exclude_none=True
)
for column in profiled
]
result["note"] = (
f"共 {len(profiled)} 列,只列出列名和类型;用 columns 指定列查看详细概况和样例"
)
else:
index = {column["name"]: position for position, column in enumerate(profiled)}
index = {column.name: position for position, column in enumerate(profiled)}
names = list(index) if columns is None else list(dict.fromkeys(columns))
positions = [index[name] for name in names if name in index]
result["columns"] = [profiled[position] for position in positions]
result["columns"] = [
profiled[position].model_dump(mode="json", exclude_none=True)
for position in positions
]
# 样例行的值和 columns 一一对应
result["sample_rows"] = [
[row[position] for position in positions] for row in item.profile["sample"]
[row[position] for position in positions] for row in item.profile.sample
]
if missing := [name for name in names if name not in index]:
result["missing_columns"] = missing