Files
AgentLab/backend/app/main.py
T
ThearchyHelios dcadc0c48b 对话有上下文了,左边也能选会话
三条 run(d983c4e6 / 14103db5 / 08c3de1c)在用户看来是一次对话,系统里却
毫无关联。查下来不是哪里传漏了,是整层缺失:

- 每轮 base_graph=None 从零建图,Copilot 不知道上一轮问过什么
- 每轮 thread_id = run.id,三条独立的 checkpoint 线程
- 前端 turns 只活在 zustand 里,刷新页面就全没了——那三轮当时在界面上
  已经找不回来,只剩 runs 表里三条互不相干的记录
- 后端 13 张表里没有会话这个概念

加两张表(conversations / conversation_turns)。失败的轮次也记:只记成功的,
历史就成了一份美化过的记录,而人回来最想搞清楚的恰恰是上次卡在哪。

Copilot 的上下文带三样:前几轮问答、上一轮那张图(走 _slim 去掉坐标噪音)。
只给问答文字不够——"再按月份拆一下"会让模型重新设计一张图,实测经常接到另
一张表上,答案对不上前一轮。运行时另外注入 {{ input.history }},管的是指代:
用户问"它们平均多少",图里的 agent 得知道"它们"是上一轮那 12 家店。

左侧会话栏只在问数据页出现,可折叠、可改名删除,当前会话存 localStorage。
轮次按会话分桶缓存——切走再切回来要看得到原来的步骤,回源只拿得到问答。
历史轮次的执行过程不预加载,点「看执行过程」才取事件、走现成的 decodeRun。
画布右栏的 Copilot 依附工作流各有一条会话,连说两次"再加一个节点"能接上了
(base_graph 带的是图的结果,带不出"你刚才要我干什么"),但不进左侧列表。

过程中修掉的两个自己引入的坑:

- 第一版在列表为空时自动建一条会话。create 是异步的、StrictMode 把 effect
  跑两遍,于是每次进页面都留下两三条"新对话"。改成用户开口那一刻按需建
- 加了"在上一轮的图上改"这条指令之后,模型照改图协议只发 update_node,
  而问数据页这条路径的累加器从空图起步,操作全部落空,用户收到的是
  "图校验未通过:图是空的,先拖一个节点进来"(会话 f5db9809 的第二轮,
  而它的 explanation 证明上下文其实完全读懂了)。指令改成沿用 id 和 config
  但完整重发,另加一道兜底:空图且上一轮有图时把操作重放到那张图上

check-ui 多一节「会话」:切换、刷新还在、逛一圈不多出空会话。
2026-09-18 17:58:04 +08:00

92 lines
2.3 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()
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"}