Files
adminandClaude Fable 5 04fb306624 fix(bioflow): 内核树运行时挂载——非 gf 阶段镜像缺 cli 模块
- 8 个阶段镜像中仅 bioflow-gf 内置 /bioflow-kernel,其余 7 个
  (rnaseq/wgs/gfpy/rnar/rde/pan/interproscan) 执行时 No module named cli
- 服务端统一 -v kernel_dir:/bioflow-kernel:ro + -w /bioflow-kernel,
  全部镜像可用,免重建 7 个镜像
- config 新增 kernel_dir/container_kernel_dir(env 可覆盖)
- 337 用例全绿

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-15 23:22:06 +08:00

167 lines
6.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""config.py —— 生信服务(bioflow-service 8098)环境变量驱动配置(pydantic-settings)。
全部键可用环境变量覆盖(前缀 BIOFLOW_;LLM 沿用 docgen 的 DOCGEN_LLM_* 直连模式)。
零硬编码纪律:不出现任何写死宿主路径;仓库内相对路径按 __file__ 推导 repo 根。
"""
from __future__ import annotations
from functools import lru_cache
from pathlib import Path
from pydantic_settings import BaseSettings, SettingsConfigDict
_REPO_ROOT = Path(__file__).resolve().parent.parent.parent # bioflow_service/.. = 仓库根
def _resolve_root(v: str) -> str:
"""仓库内相对路径按 __file__ 推导 repo 根解析为绝对路径。"""
p = Path(v).expanduser()
if not p.is_absolute():
p = _REPO_ROOT / p
return str(p)
class Settings(BaseSettings):
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
extra="ignore",
env_prefix="BIOFLOW_",
)
# ---- 应用 ----
app_name: str = "plantai-bioflow-service"
version: str = "0.1.0"
# ---- 阶段→镜像映射(A1 交付物,仓库内 bioflow_kernel/stage_image_map.json)----
image_map_path: str = "bioflow_kernel/stage_image_map.json"
# 内核树:运行时只读挂载进执行容器(-v kernel:/bioflow-kernel:ro + -w)
# 除 bioflow-gf 外其余镜像未内置内核,挂载后全部阶段镜像统一可用
kernel_dir: str = "bioflow_kernel"
container_kernel_dir: str = "/bioflow-kernel"
# ---- 数据与任务目录 ----
data_root: str = "bioflow_data" # 生信数据根(hmm/rag_db 等,只读挂载进容器)
jobs_dir: str = "bioflow_data/jobs" # 任务目录根(input/workspace/events)
db_path: str = "bioflow_data/bioflow.db" # 独立 SQLite(与 BFF 完全隔离,FR-BIO-028)
archives_dir: str = "bioflow_data/archives"
# ---- 执行 ----
concurrency: int = 3 # 全局并发信号量上限(FR-BIO-031,可配)
stage_timeout_sec: float = 36000.0 # 单阶段 docker 硬超时(秒,10h)
docker_image_tag_suffix: str = "dev" # 镜像 tag 后缀(P0 交付物为 :dev)
host_uid_env: str = "BIOFLOW_HOST_UID" # 宿主机 uid 环境变量名;未设置时回退服务进程 uid
container_data_dir: str = "/bioflow-data"
container_input_dir: str = "/job/input"
container_workspace_dir: str = "/job/workspace"
default_cpus: str = "4"
default_memory: str = "32g"
log_tail_lines: int = 80 # manifest 接口日志尾行数(≤80)
max_upload_gb: float = 100.0 # 上传大小上限(GB;超限 413)
upload_ext_allow: str = ".fasta,.fa,.faa,.fna,.pep,.cds,.gz,.gff,.gff3,.gtf,.txt,.tsv,.csv,.xlsx,.xls,.bed,.vcf,.vcf.gz,.bam,.cram,.sam,.hmm,.nwk,.newick,.treefile,.json,.yaml,.yml,.pdf,.zip,.py,.r,.sh"
# ---- 服务器路径登记(FR-BIO-007;用户数据可在任意位置)----
max_register_files_per_job: int = 200 # 目录展开单任务文件数上限(防误填大目录)
# 敏感路径黑名单:命中前缀即 403(白名单根校验已按用户要求移除)
sensitive_path_prefixes: tuple[str, ...] = (
"/etc", "/root", "/proc", "/sys", "/dev", "/run", "/boot",
"/bin", "/sbin", "/lib", "/lib64", "/usr/bin", "/usr/sbin",
"/usr/lib", "/usr/lib64", "/usr/local/bin", "/var/lib", "/var/log",
"/opt/neo4j", "/var/lib/docker", "/srv/plantai/install",
)
# 敏感文件名(正则,re.match 整名);.env 等含密钥配置、密钥文件不可登记
sensitive_filename_patterns: tuple[str, ...] = (
r"\.env.*", r".*\.key$", r".*\.pem$", r"id_rsa.*", r"id_ed25519.*",
r".*\.p12$", r"\.gitconfig.*",
)
# ---- 用户自定义扩展分析(并行链路;存储根 + AI 转换 LLM 键 + 修复上限)----
extensions_dir: str = "bioflow_data/extensions" # 扩展存储根({ext_id}/extension.json...)
extension_max_repair_rounds: int = 20 # AI 修复循环上限(契约 §6 可配)
# 扩展转换 LLM 键:DOCGEN_LLM_* 直接复用(docgen 直连模式),
# BIOFLOW_EXTENSION_* 为可选别名(extension_llm.llm_config 兜底读取)。
# ---- 规划(planner)----
llm_plan_timeout: float = 60.0 # 规划 LLM 单次调用超时(秒)
plan_candidates: int = 3 # 候选路径条数(2~3,FR-BIO-002)
plan_max_rounds: int = 2 # LLM 输出硬校验失败的最多重试轮数
# ---- 知识库(vstore/flow.py 本地实例;无 vstore 时自动降级 llm_only)----
flow_docs_dir: str = "vstore/data/flow_docs"
# ---- LLM(沿用 docgen deepseek 直连模式,DOCGEN_LLM_* 键)----
docgen_llm_base_url: str = "https://api.deepseek.com/v1"
docgen_llm_api_key: str = ""
docgen_llm_model: str = "deepseek-chat"
docgen_llm_timeout: float = 300.0
# ---- 报告登记(BFF 反代端点;空=未配置,登记降级为仅落表)----
service_url: str = "http://127.0.0.1:8098"
# ---- 容器环境变量(BBA_*;零硬编码:路径全部从本配置推导)----
@property
def bba_env(self) -> dict[str, str]:
"""docker run 注入的 BBA_* 环境(值全部来自配置推导,无写死宿主路径)。"""
return {
"BBA_PFAM_DIR": f"{self.container_data_dir}/hmm",
"BBA_RAG_MAPPING_FILE": f"{self.container_data_dir}/rag_db/pfam_mapping.json",
"BBA_DISABLE_RAG_PFAM_LOOKUP": "0",
}
@property
def host_uid(self) -> int:
"""--user 注入 uid:BIOFLOW_HOST_UID 环境变量优先,否则服务进程 uid。"""
import os
raw = os.environ.get(self.host_uid_env, "").strip()
if raw:
try:
return int(raw)
except ValueError:
pass
return os.getuid()
@property
def image_map_abs(self) -> str:
return _resolve_root(self.image_map_path)
@property
def data_root_abs(self) -> str:
return _resolve_root(self.data_root)
@property
def kernel_dir_abs(self) -> str:
return _resolve_root(self.kernel_dir)
@property
def jobs_dir_abs(self) -> str:
return _resolve_root(self.jobs_dir)
@property
def db_path_abs(self) -> str:
return _resolve_root(self.db_path)
@property
def archives_dir_abs(self) -> str:
return _resolve_root(self.archives_dir)
@property
def flow_docs_dir_abs(self) -> str:
return _resolve_root(self.flow_docs_dir)
@property
def extensions_dir_abs(self) -> str:
return _resolve_root(self.extensions_dir)
@property
def upload_ext_set(self) -> set[str]:
return {ext.strip().lower() for ext in self.upload_ext_allow.split(",") if ext.strip()}
@lru_cache(maxsize=1)
def get_settings() -> Settings:
return Settings()
settings = get_settings()