feat:LLM 分析文件并生成结构化建议
This commit is contained in:
+19
-10
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user