feat: LAMOST DR12-14 与子版本体系接入、观测层安全加固与并发异步化

- LAMOST 新增 DR12/13/14 及子版本(v0/v1.0/v1.1/v2.0)维度,Internal 发布标记需登录认证并前端灰显,release×subtype 交叉约束下沉至 capabilities 统一声明
- ObservationFetcher trait 扩展版本/认证/交叉约束能力声明,version 参数贯穿 client→service→API→Agent tool→前端全链路
- 安全:observation cache SQL 全参数绑定 + LIKE 转义、cone_cache_hash 加长度前缀防碰撞、DESI survey/program 白名单防穿越
- 异步化:persist/cached_files_total_size/maybe_persist_tool_result迁移到 tokio::fs;cancelled_runs 与 session_permission_checkers改用 DashMap;auth 读锁优先 + 60s 节流
- Gaia 去 native-tls 改禁用连接池规避 UnexpectedEof,reqwest 移除 native-tls feature
- 重构:Source/ProductType from_str 集中解析、download 模块拆分为 try_download_pdf/html、AgentRuntime::init 抽取共享逻辑
- 部署:新增 deploy.sh 一键打包推送脚本、catch-panic 启用
This commit is contained in:
fmq
2026-07-07 21:16:46 +08:00
parent 2f1fd19d74
commit 2c8d0b8f8b
75 changed files with 2482 additions and 1370 deletions
+26 -47
View File
@@ -185,10 +185,7 @@ impl AgentTool for CatalogOperationTool {
}
}
async fn do_catalog_search(
state: &crate::api::AppState,
args: &serde_json::Value,
) -> ToolOutput {
async fn do_catalog_search(state: &crate::api::AppState, args: &serde_json::Value) -> ToolOutput {
let keyword = match args.get("keyword").and_then(|v| v.as_str()) {
Some(k) => k,
None => return ToolOutput::error("search 需要 'keyword' 参数"),
@@ -226,10 +223,7 @@ async fn do_catalog_search(
ToolOutput::success(content, json!(items))
}
async fn do_catalog_describe(
state: &crate::api::AppState,
args: &serde_json::Value,
) -> ToolOutput {
async fn do_catalog_describe(state: &crate::api::AppState, args: &serde_json::Value) -> ToolOutput {
let table = match args.get("table").and_then(|v| v.as_str()) {
Some(t) => t,
None => return ToolOutput::error("describe 需要 'table' 参数"),
@@ -259,10 +253,7 @@ async fn do_catalog_describe(
ToolOutput::success(content, json!(items))
}
async fn do_catalog_query(
state: &crate::api::AppState,
args: &serde_json::Value,
) -> ToolOutput {
async fn do_catalog_query(state: &crate::api::AppState, args: &serde_json::Value) -> ToolOutput {
let limit = args
.get("limit")
.and_then(|v| v.as_i64())
@@ -275,8 +266,7 @@ async fn do_catalog_query(
limit,
adql.chars().take(150).collect::<String>()
);
crate::services::cds::vizier::query_adql_cached(&state.db, &state.vizier, adql, limit)
.await
crate::services::cds::vizier::query_adql_cached(&state.db, &state.vizier, adql, limit).await
} else if let Some(table) = args.get("table").and_then(|v| v.as_str()) {
let columns: Vec<String> = args
.get("columns")
@@ -284,14 +274,8 @@ async fn do_catalog_query(
.map(|c| c.split(',').map(|s| s.trim().to_string()).collect())
.unwrap_or_default();
info!("[CatalogOp:query] table={} limit={}", table, limit);
crate::services::cds::vizier::query_table(
&state.db,
&state.vizier,
table,
&columns,
limit,
)
.await
crate::services::cds::vizier::query_table(&state.db, &state.vizier, table, &columns, limit)
.await
} else {
return ToolOutput::error("query 需要 'adql' 或 'table' 参数之一");
};
@@ -305,10 +289,7 @@ async fn do_catalog_query(
}
}
async fn do_catalog_cone(
state: &crate::api::AppState,
args: &serde_json::Value,
) -> ToolOutput {
async fn do_catalog_cone(state: &crate::api::AppState, args: &serde_json::Value) -> ToolOutput {
let coords = match args.get("coords").and_then(|v| v.as_object()) {
Some(c) => c,
None => return ToolOutput::error("cone 需要 'coords' 参数(含 ra/dec"),
@@ -338,7 +319,11 @@ async fn do_catalog_cone(
info!(
"[CatalogOp:cone] ra={} dec={} radius={}° table={} strategy={}",
ra, dec, radius, table, if nearest { "nearest" } else { "all" }
ra,
dec,
radius,
table,
if nearest { "nearest" } else { "all" }
);
match crate::services::cds::vizier::cone_search(
@@ -365,10 +350,7 @@ async fn do_catalog_cone(
}
}
async fn do_catalog_export(
state: &crate::api::AppState,
args: &serde_json::Value,
) -> ToolOutput {
async fn do_catalog_export(state: &crate::api::AppState, args: &serde_json::Value) -> ToolOutput {
let limit = args
.get("limit")
.and_then(|v| v.as_i64())
@@ -384,13 +366,16 @@ async fn do_catalog_export(
}
let catalog = crate::services::cds::vizier::VizierCatalog::new(&state.db, &state.vizier);
let result = match catalog.export_to_file(
state.config.library_dir.to_str().unwrap_or("."),
adql,
table,
columns,
limit,
).await {
let result = match catalog
.export_to_file(
state.config.library_dir.to_str().unwrap_or("."),
adql,
table,
columns,
limit,
)
.await
{
Ok(r) => r,
Err(e) => return ToolOutput::error(format!("导出失败: {}", e)),
};
@@ -410,10 +395,7 @@ async fn do_catalog_export(
)
}
async fn do_catalog_lookup(
state: &crate::api::AppState,
args: &serde_json::Value,
) -> ToolOutput {
async fn do_catalog_lookup(state: &crate::api::AppState, args: &serde_json::Value) -> ToolOutput {
let bibcode = match args.get("bibcode").and_then(|v| v.as_str()) {
Some(b) => b,
None => return ToolOutput::error("lookup 需要 'bibcode' 参数"),
@@ -424,11 +406,8 @@ async fn do_catalog_lookup(
.unwrap_or(10)
.clamp(1, 30) as usize;
let catalog = crate::services::cds::vizier::VizierCatalog::with_ads(
&state.db,
&state.vizier,
&state.ads,
);
let catalog =
crate::services::cds::vizier::VizierCatalog::with_ads(&state.db, &state.vizier, &state.ads);
let results = match catalog.lookup(bibcode, limit).await {
Ok(r) => r,
Err(e) => return ToolOutput::error(format!("查找失败: {}", e)),