mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 06:31:58 +00:00
df935599f2
三件事,都是"这三个模块一条测试都没有"的后果。 **换 embedder 会让检索整体 500。** 库里是 512 维的本地向量、查询是 1536 维的 远端向量时,cosine 直接 `shapes (1536,) and (512,) not aligned`。from_blob 有 dim 参数,但 kb.py 和 memory/store.py 调用时都没传;而且存量向量属于哪个模型 代码里没有任何地方记着,所以也无从降级。 Chunk / MemoryItem 各加 embed_model + embed_dim(走现成的 _COLUMN_MIGRATIONS, 存量回填空值,正好表示"不知道是谁建的")。检索遇到对不上的向量整体退回纯 关键词并说破,不是把它们当 0 分静默沉底——那样用户看到的是结果莫名其妙变差, 而不是"这里有一批索引该重建了"。配套 kb.reindex + POST /api/kb/reindex。 长期记忆那份一起修了。判重那条更危险:维度碰巧相同但模型不同时 cosine 算出来 是随机数,会把不相关的旧记忆认成"重复",然后把新内容丢掉。 **默认 embedder 没有语义能力,而开关藏在一个环境变量里。** LocalEmbedder 是 特征哈希 + TF,和 BM25 吃同一批词;alpha 默认 0.5 等于给了它一半权重,同义 改写一条都召不回。开启真模型的唯一开关是 AGENTLAB_USE_OPENAI_EMBEDDINGS—— 没界面、没人会发现。改成设置项(照 COPILOT_SETTING_KEY 的路子),知识库页顶部 能选能看能重建。 配置失败必须响:第一版写成静默退回本地,但选了 OpenAI 却拿到哈希向量的人, 在搜不出同义表达时完全无从怀疑到这里。现在抛 EmbedderUnavailable,API 400 原样交回"检查 OPENAI_API_KEY 是否配了",且不会把配置改掉。 **检索结果不进工件库。** db_query、工具节点、agent 工具调用都落 put_json 快照, run_retrieve 什么都不落——于是结论只要来自知识库,"这个数是哪来的"就断在这儿, 而数字回指正是这个项目的签名能力。现在落 kind="retrieval_snapshot",并发一条 retrieve.end 带 artifact id。解码层补了正式映射(原来会掉进"未知事件"兜底, 画成一坨原始 JSON)。 新增 test_kb.py 11 条,补上这三个模块此前的零覆盖,包括 embeddings.py 里 注释记着、但一直没有测试守的那个归一化边界(单条候选被 min_score 误杀)。 端到端在真实服务上验过:撞上存量向量返回 200 + 降级说明(以前 500)、 重建后 stale 归零、配不起来的远端模型 400 且配置不变。
105 lines
2.9 KiB
Python
105 lines
2.9 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
from contextlib import asynccontextmanager
|
|
from typing import AsyncIterator
|
|
|
|
from fastapi import FastAPI, Request
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.responses import JSONResponse
|
|
|
|
from app.api import (
|
|
artifacts, conversations, copilot, datasources, governance, knowledge, runs,
|
|
sandbox, settings as settings_api, tools, workflows,
|
|
)
|
|
from app.core.config import settings
|
|
from app.db.base import init_db
|
|
from app.engine.runner import run_manager
|
|
from app.seed import seed_defaults
|
|
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format="%(asctime)s %(levelname)-7s %(name)s | %(message)s",
|
|
datefmt="%H:%M:%S",
|
|
)
|
|
logger = logging.getLogger("agentlab")
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(_: FastAPI) -> AsyncIterator[None]:
|
|
settings.ensure_dirs()
|
|
await init_db()
|
|
await seed_defaults()
|
|
|
|
# 把存的 embedding 配置读进来。不读的话重启后会悄悄退回本地哈希向量,
|
|
# 而库里的向量是上次那个模型建的——检索会整体退回关键词,
|
|
# 用户只看到"重启之后搜得不准了"
|
|
from app.db.base import SessionLocal
|
|
from app.memory.embeddings import load_setting
|
|
|
|
async with SessionLocal() as _session:
|
|
try:
|
|
await load_setting(_session)
|
|
except Exception as e: # noqa: BLE001 - key 没了不该拦住整个服务起不来
|
|
logger.warning("embedding 配置没能生效,暂用本地向量:%s", e)
|
|
|
|
await run_manager.setup()
|
|
from app.sandbox.manager import sandbox_manager as _sbm
|
|
|
|
await _sbm.start_reaper()
|
|
logger.info("AgentLab 就绪 · 数据目录 %s", settings.data_dir)
|
|
try:
|
|
yield
|
|
finally:
|
|
await run_manager.shutdown()
|
|
from app.sandbox.manager import sandbox_manager
|
|
|
|
await sandbox_manager.close()
|
|
logger.info("已停止")
|
|
|
|
|
|
app = FastAPI(
|
|
title="AgentLab",
|
|
description="可视化 Agent 编排实验台",
|
|
version="0.1.0",
|
|
lifespan=lifespan,
|
|
)
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=settings.cors_origins,
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
for router in (
|
|
workflows.router,
|
|
runs.router,
|
|
runs.approvals_router,
|
|
settings_api.router,
|
|
tools.router,
|
|
tools.custom_router,
|
|
tools.mcp_router,
|
|
knowledge.memory_router,
|
|
knowledge.kb_router,
|
|
knowledge.skills_router,
|
|
sandbox.router,
|
|
copilot.router,
|
|
conversations.router,
|
|
artifacts.router,
|
|
governance.router,
|
|
datasources.router,
|
|
):
|
|
app.include_router(router)
|
|
|
|
|
|
@app.exception_handler(ValueError)
|
|
async def value_error_handler(_: Request, exc: ValueError) -> JSONResponse:
|
|
return JSONResponse(status_code=400, content={"detail": str(exc)})
|
|
|
|
|
|
@app.get("/api/health")
|
|
async def health() -> dict[str, str]:
|
|
return {"status": "ok", "service": "agentlab"}
|