feat: 科研分析层全栈落地——光谱/时域/运动学分析工具链 + JWST/X 射线数据源 + 定时文献同步
数据分析层(新增 services/{spectrum,timeseries,analysis}):
- 光谱参数提取 parameters.rs:LAMOST/SDSS/APOGEE/DESI FITS header 跨源归一化读取
Teff/logg/[Fe/H]/RV 及 ASPCAP 20+ 元素丰度,rayon 并发批量提取
- 谱线测量 lines.rs:内置真空/空气波长谱线表,窗口内极值搜索 + 梯形法积分 EW + FWHM,支持自定义谱线
- 交叉相关测速 cross_correlate.rs:对数波长重采样对齐,内置 Pickles 模板按光谱型插值,
CCF 峰值位置提取 RV 及不确定度
- 周期搜索 periodicity.rs:Lomb-Scargle 周期图(含 FAP 误报概率)+ BLS 凌星检测 + 相位折叠
- 变星分类 classification.rs:振幅/偏度/峰度/过零率/eta 等统计特征 + 规则分类(RR Lyrae/Cepheid/食双星/AGN 等)
- SED 拟合 sed.rs:多波段测光黑体模型拟合,输出 T_eff/半径/消光 A_V/光度及不确定度
- 运动学 kinematics.rs:视差+自行+RV → 银河系 UVW 空间速度,含移动星群成员概率(Banyan Σ 简化版)
- 化学丰度 chemistry.rs:[α/Fe] vs [Fe/H] 计算,厚盘/薄盘/晕星族判别
- 观测规划 observability.rs:目标升落时间/airmass/月相影响/曝光时间估算
- 赫罗图 hr_diagram.rs:Gaia TAP CMD 查询,新增 GET /api/analysis/hr-diagram 端点
数据获取层:
- JWST:clients/mast/jwst.rs 封装 MAST Portal 锥形检索 + JwstSpectrumFetcher(NIRSpec/MIRI 光谱)
- X 射线:clients/heasarc 封装 HEASARC TAP(ADQL)+ XMM-Newton/Chandra 光谱 fetcher
- 图像 cutout:SDSS SkyServer/STScI DSS/Pan-STARRS 三源 cutout + 发现图(Finding Chart)生成
- Source 枚举新增 Jwst/Xmm/Chandra 并注册 ObservationRegistry,前端 SOURCE_THEME 与筛选器同步三源
Agent 工具集(24→35):
- 新增 9 个分析工具:get_spectrum_parameters / measure_spectral_lines / measure_radial_velocity /
find_period / classify_variable_star / fit_sed / analyze_kinematics / analyze_abundance_pattern / plan_observation
- batch_process:批量样本"查询→下载→分析→报告"流水线,并发控制防数据源速率限制
- literature_monitor:按 ADS 查询式/时间窗/最低引用数检查最新文献
定时文献同步:
- sync_queries 表新增 is_scheduled 列(migration 20260713)
- 新增 POST /sync/queries/:id/schedule 端点
- 服务启动时拉起每小时调度器,对 is_scheduled=1 的检索配置静默执行 ADS(entdate 增量)/arXiv 增量同步
- search_history 工具收敛至 services/search::search_agent_history,消除 FTS 查询逻辑重复
其他:
- plotting skill 由占位填充为完整科研绘图规范:光谱/光变/折叠曲线/CMD/SED/[α/Fe]/周期图/Mollweide/发现图 9 类 matplotlib 模板
- 删除死代码 streaming_executor.rs(929 行,仅剩 mod 声明引用,无调用方)
- 新增 docs/roadmap-research-features.md 科研功能路线图及实现状态
This commit is contained in:
+58
-14
@@ -16,7 +16,7 @@ use tracing::{error, info};
|
||||
|
||||
use super::error::{ApiResult, AppError};
|
||||
use super::AppState;
|
||||
use crate::agent::runtime::{AgentRuntime, AgentStreamEvent};
|
||||
use crate::agent::runtime::AgentStreamEvent;
|
||||
|
||||
// ── POST /api/chat/agent ──
|
||||
// SSE 流式智能体对话接口
|
||||
@@ -191,25 +191,68 @@ pub async fn chat_agent(
|
||||
None => (None, None),
|
||||
};
|
||||
|
||||
let mut runtime = AgentRuntime::new(Arc::clone(&state)).with_mode(&req.mode);
|
||||
// ── 会话解析与运行时复用 ──
|
||||
// 预分配会话 ID:新会话的首个请求也能写入取消标记并命中运行时缓存
|
||||
// (历史上新会话首请求超时只能 abort 任务,无法写入取消标记)
|
||||
let session_key = req
|
||||
.session_id
|
||||
.clone()
|
||||
.unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
|
||||
|
||||
// 模式回放:已存在的会话从 DB 读取创建时的模式。
|
||||
// 修复:恢复会话时传不同 mode 会静默改变行为——现在会话模式一次创建后保持稳定。
|
||||
let mode_id = crate::agent::runtime::session::load_session_mode(&state.db, &session_key)
|
||||
.await
|
||||
.unwrap_or_else(|| req.mode.clone());
|
||||
|
||||
let runtime = state
|
||||
.agent_runtimes
|
||||
.get_or_create(Arc::clone(&state), &session_key, &mode_id);
|
||||
// 只有 mode 未强制固定 thinking 时,用户才可以覆盖
|
||||
if runtime.mode_fixed_thinking().is_none() {
|
||||
if let Some(thinking) = req.thinking {
|
||||
runtime = runtime.with_thinking(thinking);
|
||||
runtime.set_thinking(thinking);
|
||||
}
|
||||
}
|
||||
|
||||
// 同会话 turn 串行化:并发请求 fail-loud 拒绝,防止 turn_index/消息顺序被破坏
|
||||
let turn_lock = state
|
||||
.agent_runtimes
|
||||
.turn_lock(&session_key)
|
||||
.unwrap_or_else(|| std::sync::Arc::new(tokio::sync::Mutex::new(())));
|
||||
// 快速检测:已占用直接 409(guard 立即释放,任务内部会重新 try_lock 兜底竞态)
|
||||
if turn_lock.try_lock().is_err() {
|
||||
return Err(AppError::conflict(
|
||||
"该会话正在执行中,请等待当前回合完成后再发送新消息",
|
||||
));
|
||||
}
|
||||
|
||||
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::<AgentStreamEvent>();
|
||||
|
||||
let session_id = req.session_id.clone();
|
||||
let question_owned = question.clone();
|
||||
let image_context_owned = image_context.clone();
|
||||
let image_path_owned = image_path_for_db.clone();
|
||||
let cancel_session_key = session_key.clone();
|
||||
|
||||
// 在后台 tokio 任务中执行 Agent 循环
|
||||
let agent_handle = tokio::spawn(async move {
|
||||
// 持有 turn 串行锁直到回合结束(内部重新 try_lock 兜底竞态窗口)
|
||||
let _turn_guard = match turn_lock.try_lock() {
|
||||
Ok(guard) => guard,
|
||||
Err(_) => {
|
||||
let _ = tx.send(AgentStreamEvent::Error {
|
||||
message: "该会话正在执行中,请稍后重试。".to_string(),
|
||||
});
|
||||
let _ = tx.send(AgentStreamEvent::Done);
|
||||
return;
|
||||
}
|
||||
};
|
||||
match runtime
|
||||
.run_turn_with_image_context(
|
||||
session_id,
|
||||
&question,
|
||||
image_context,
|
||||
image_path_for_db,
|
||||
Some(session_key.clone()),
|
||||
&question_owned,
|
||||
image_context_owned,
|
||||
image_path_owned,
|
||||
tx.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -236,9 +279,7 @@ pub async fn chat_agent(
|
||||
let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());
|
||||
if remaining.is_zero() {
|
||||
// 超时:通知 CancellationHook 停止 Agent,并中止后台任务
|
||||
if let Some(sid) = &req.session_id {
|
||||
cancelled_runs.insert(sid.clone(), ());
|
||||
}
|
||||
cancelled_runs.insert(cancel_session_key.clone(), ());
|
||||
agent_handle.abort();
|
||||
let timeout_event = AgentStreamEvent::Error {
|
||||
message: "Agent 执行超时(10 分钟),请重试。".to_string(),
|
||||
@@ -259,9 +300,7 @@ pub async fn chat_agent(
|
||||
Ok(None) => break, // channel closed
|
||||
Err(_) => {
|
||||
// 超时:通知 CancellationHook 停止 Agent,并中止后台任务
|
||||
if let Some(sid) = &req.session_id {
|
||||
cancelled_runs.insert(sid.clone(), ());
|
||||
}
|
||||
cancelled_runs.insert(cancel_session_key.clone(), ());
|
||||
agent_handle.abort();
|
||||
let timeout_event = AgentStreamEvent::Error {
|
||||
message: "Agent 执行超时(10 分钟),请重试。".to_string(),
|
||||
@@ -323,6 +362,11 @@ pub async fn delete_session(
|
||||
.await
|
||||
.map_err(|e| AppError::internal(format!("删除会话失败: {}", e)))?;
|
||||
|
||||
if success {
|
||||
// 同步移除会话级运行时缓存(后台队列/压缩日志等状态随之释放)
|
||||
state.agent_runtimes.remove(&session_id);
|
||||
}
|
||||
|
||||
if !success {
|
||||
return Err(AppError::not_found(format!(
|
||||
"会话 {} 不存在或已删除",
|
||||
|
||||
Reference in New Issue
Block a user