文件浏览
This commit is contained in:
1 parent
a3c69dab07
commit
4fab13c728
6 files changed
+60
-19
No files matched your search
@@ -163,7 +163,8 @@ export function RecentChatList() {
|
|||||||
const visibleThreads = threads.filter(
|
const visibleThreads = threads.filter(
|
||||||
(thread) =>
|
(thread) =>
|
||||||
thread.metadata?.thread_type !== "scheduler" &&
|
thread.metadata?.thread_type !== "scheduler" &&
|
||||||
thread.metadata?.thread_type !== "bootstrap",
|
thread.metadata?.thread_type !== "bootstrap" &&
|
||||||
|
thread.metadata?.thread_type !== "roundtable",
|
||||||
);
|
);
|
||||||
|
|
||||||
if (visibleThreads.length === 0) {
|
if (visibleThreads.length === 0) {
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export default function ChatsPage() {
|
|||||||
return threads?.filter((thread) => {
|
return threads?.filter((thread) => {
|
||||||
return (
|
return (
|
||||||
thread.metadata?.thread_type !== "scheduler" &&
|
thread.metadata?.thread_type !== "scheduler" &&
|
||||||
|
thread.metadata?.thread_type !== "roundtable" &&
|
||||||
titleOfThread(thread).toLowerCase().includes(search.toLowerCase())
|
titleOfThread(thread).toLowerCase().includes(search.toLowerCase())
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -476,8 +476,13 @@ function Step2Message({
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 扫描所有 `tool_calling:present_files` 步骤里的 `filepaths`,按出现顺序去重。
|
* 收集气泡里所有"写了文件"的产出路径,按出现顺序去重,聚合到一行文件卡片。
|
||||||
* 同一气泡可能多次 present_files(总控分批交付),都聚合到一行卡片里。
|
*
|
||||||
|
* 三类来源都算:
|
||||||
|
* - `present_files`:总控呈现文件,args.filepaths 是数组(可一次多个);
|
||||||
|
* - `write_file` / `str_replace`:子智能体被后端 excluded_tools 禁用了 present_files,
|
||||||
|
* 只能 write_file 写文件,单文件 path 在 args.path —— 也聚合进卡片,保证"凡是写了
|
||||||
|
* 文件就出可点的卡片",而不是只有 present_files 才出。
|
||||||
*/
|
*/
|
||||||
function collectPresentedFiles(
|
function collectPresentedFiles(
|
||||||
steps: Step2Dialogue["steps"],
|
steps: Step2Dialogue["steps"],
|
||||||
@@ -485,14 +490,22 @@ function collectPresentedFiles(
|
|||||||
if (!Array.isArray(steps) || steps.length === 0) return [];
|
if (!Array.isArray(steps) || steps.length === 0) return [];
|
||||||
const out: string[] = [];
|
const out: string[] = [];
|
||||||
const seen = new Set<string>();
|
const seen = new Set<string>();
|
||||||
|
const add = (fp: unknown) => {
|
||||||
|
if (typeof fp !== "string" || !fp || seen.has(fp)) return;
|
||||||
|
seen.add(fp);
|
||||||
|
out.push(fp);
|
||||||
|
};
|
||||||
for (const step of steps) {
|
for (const step of steps) {
|
||||||
if (step.key !== "tool_calling:present_files") continue;
|
if (step.key === "tool_calling:present_files") {
|
||||||
const fps = step.args?.filepaths;
|
const fps = step.args?.filepaths;
|
||||||
if (!Array.isArray(fps)) continue;
|
if (Array.isArray(fps)) fps.forEach(add);
|
||||||
for (const fp of fps) {
|
continue;
|
||||||
if (typeof fp !== "string" || !fp || seen.has(fp)) continue;
|
}
|
||||||
seen.add(fp);
|
if (
|
||||||
out.push(fp);
|
step.key === "tool_calling:write_file" ||
|
||||||
|
step.key === "tool_calling:str_replace"
|
||||||
|
) {
|
||||||
|
add(step.args?.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ from fastapi.responses import StreamingResponse
|
|||||||
# identical in connection handling, payload shape, and error mapping.
|
# identical in connection handling, payload shape, and error mapping.
|
||||||
from app.gateway.routers._roundtable_seed import ensure_roundtable_functional_agents
|
from app.gateway.routers._roundtable_seed import ensure_roundtable_functional_agents
|
||||||
from app.gateway.routers.multi_agent import (
|
from app.gateway.routers.multi_agent import (
|
||||||
|
_ROUNDTABLE_THREAD_METADATA,
|
||||||
_auth_headers,
|
_auth_headers,
|
||||||
_create_thread,
|
_create_thread,
|
||||||
_diagnose_exception,
|
_diagnose_exception,
|
||||||
@@ -286,7 +287,7 @@ async def init_intent(request: Request, payload: dict = Body(...)) -> dict[str,
|
|||||||
# 看不到逐 token 增量。
|
# 看不到逐 token 增量。
|
||||||
try:
|
try:
|
||||||
async with _make_loopback_client(request) as client:
|
async with _make_loopback_client(request) as client:
|
||||||
thread_id = await _create_thread(client, base, headers)
|
thread_id = await _create_thread(client, base, headers, metadata=_ROUNDTABLE_THREAD_METADATA)
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
# 上游已用结构化方式失败,原样传给前端不需要二次包装。
|
# 上游已用结构化方式失败,原样传给前端不需要二次包装。
|
||||||
raise
|
raise
|
||||||
|
|||||||
@@ -69,6 +69,12 @@ logger = logging.getLogger(__name__)
|
|||||||
router = APIRouter(prefix="/api/multi-agent", tags=["multi-agent"])
|
router = APIRouter(prefix="/api/multi-agent", tags=["multi-agent"])
|
||||||
|
|
||||||
|
|
||||||
|
# 圆桌(intent / recommend / 席位 / 协调)创建的 thread 都是功能型内部线程,
|
||||||
|
# 不是用户的普通对话。打上这个 metadata,前端「最近的对话」列表(recent-chat-list.tsx)
|
||||||
|
# 据 `metadata.thread_type` 过滤掉,与 scheduler / bootstrap 系统会话同一套机制。
|
||||||
|
_ROUNDTABLE_THREAD_METADATA: dict[str, Any] = {"thread_type": "roundtable", "system": True}
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# 共享:异常诊断器(intent/recommend/multi-agent 共用)
|
# 共享:异常诊断器(intent/recommend/multi-agent 共用)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -289,9 +295,22 @@ def _auth_headers(request: Request) -> dict[str, str]:
|
|||||||
return {"Authorization": token, "Content-Type": "application/json"}
|
return {"Authorization": token, "Content-Type": "application/json"}
|
||||||
|
|
||||||
|
|
||||||
async def _create_thread(client: httpx.AsyncClient, base: str, headers: dict[str, str]) -> str:
|
async def _create_thread(
|
||||||
"""``POST /api/threads`` → 新 thread_id(intent/recommend/init 席位均用)。"""
|
client: httpx.AsyncClient,
|
||||||
resp = await client.post(f"{base}/api/threads", json={}, headers=headers)
|
base: str,
|
||||||
|
headers: dict[str, str],
|
||||||
|
*,
|
||||||
|
metadata: dict[str, Any] | None = None,
|
||||||
|
) -> str:
|
||||||
|
"""``POST /api/threads`` → 新 thread_id(intent/recommend/init 席位均用)。
|
||||||
|
|
||||||
|
``metadata`` 写入 thread 元数据,圆桌调用方传 ``_ROUNDTABLE_THREAD_METADATA``
|
||||||
|
把这些功能型线程标记成可被前端「最近的对话」过滤掉的系统会话。
|
||||||
|
"""
|
||||||
|
body: dict[str, Any] = {}
|
||||||
|
if metadata:
|
||||||
|
body["metadata"] = metadata
|
||||||
|
resp = await client.post(f"{base}/api/threads", json=body, headers=headers)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
return resp.json()["thread_id"]
|
return resp.json()["thread_id"]
|
||||||
|
|
||||||
@@ -351,12 +370,15 @@ def _resolve_user_id(request: Request) -> str:
|
|||||||
return get_effective_user_id()
|
return get_effective_user_id()
|
||||||
|
|
||||||
|
|
||||||
async def _create_thread_direct(request: Request) -> str:
|
async def _create_thread_direct(request: Request, *, metadata: dict[str, Any] | None = None) -> str:
|
||||||
"""同进程版的 ``POST /api/threads``。
|
"""同进程版的 ``POST /api/threads``。
|
||||||
|
|
||||||
完全镜像 ``routers/threads.create_thread`` 的写盘动作(thread_meta +
|
完全镜像 ``routers/threads.create_thread`` 的写盘动作(thread_meta +
|
||||||
空 checkpoint),但跳过 TCP + Pydantic 反序列化。仅供 init 阶段批量建席位
|
空 checkpoint),但跳过 TCP + Pydantic 反序列化。仅供 init 阶段批量建席位
|
||||||
线程用,失败时抛 HTTPException 由 SSE 帧上报。
|
线程用,失败时抛 HTTPException 由 SSE 帧上报。
|
||||||
|
|
||||||
|
``metadata`` 写入 thread 元数据;init 传 ``_ROUNDTABLE_THREAD_METADATA`` 把
|
||||||
|
席位 / 协调线程标记成系统会话,从前端「最近的对话」里隐藏。
|
||||||
"""
|
"""
|
||||||
from app.gateway.deps import get_checkpointer, get_thread_store
|
from app.gateway.deps import get_checkpointer, get_thread_store
|
||||||
|
|
||||||
@@ -366,7 +388,7 @@ async def _create_thread_direct(request: Request) -> str:
|
|||||||
now = now_iso()
|
now = now_iso()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await thread_store.create(thread_id, assistant_id=None, metadata={})
|
await thread_store.create(thread_id, assistant_id=None, metadata=metadata or {})
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("[init-direct] thread_meta create failed for %s", thread_id)
|
logger.exception("[init-direct] thread_meta create failed for %s", thread_id)
|
||||||
raise HTTPException(status_code=500, detail="Failed to create thread")
|
raise HTTPException(status_code=500, detail="Failed to create thread")
|
||||||
@@ -740,7 +762,7 @@ async def init_multi_agent(request: Request, payload: dict = Body(...)) -> Strea
|
|||||||
# 2) 每个席位 thread 并行创建(直调 thread_store + checkpointer),
|
# 2) 每个席位 thread 并行创建(直调 thread_store + checkpointer),
|
||||||
# 用 asyncio.as_completed 保持"先完先发 seat_ready"的体验。
|
# 用 asyncio.as_completed 保持"先完先发 seat_ready"的体验。
|
||||||
async def _prepare_sub_agent(sub_id: str) -> tuple[str, str, str, str]:
|
async def _prepare_sub_agent(sub_id: str) -> tuple[str, str, str, str]:
|
||||||
thread_id = await _create_thread_direct(request)
|
thread_id = await _create_thread_direct(request, metadata=_ROUNDTABLE_THREAD_METADATA)
|
||||||
meta = agent_meta.get(sub_id.lower(), {})
|
meta = agent_meta.get(sub_id.lower(), {})
|
||||||
return (
|
return (
|
||||||
sub_id.lower(),
|
sub_id.lower(),
|
||||||
@@ -802,7 +824,9 @@ async def init_multi_agent(request: Request, payload: dict = Body(...)) -> Strea
|
|||||||
|
|
||||||
# 3) coordinator thread + agent 并行创建。thread 与 agent 之间无依赖,
|
# 3) coordinator thread + agent 并行创建。thread 与 agent 之间无依赖,
|
||||||
# asyncio.gather 比串行省一个直调往返的开销。
|
# asyncio.gather 比串行省一个直调往返的开销。
|
||||||
coord_thread_task = asyncio.create_task(_create_thread_direct(request))
|
coord_thread_task = asyncio.create_task(
|
||||||
|
_create_thread_direct(request, metadata=_ROUNDTABLE_THREAD_METADATA)
|
||||||
|
)
|
||||||
coord_agent_task = asyncio.create_task(
|
coord_agent_task = asyncio.create_task(
|
||||||
_create_coordinator_direct(
|
_create_coordinator_direct(
|
||||||
request,
|
request,
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ from fastapi.responses import StreamingResponse
|
|||||||
|
|
||||||
from app.gateway.routers._roundtable_seed import ensure_roundtable_functional_agents
|
from app.gateway.routers._roundtable_seed import ensure_roundtable_functional_agents
|
||||||
from app.gateway.routers.multi_agent import (
|
from app.gateway.routers.multi_agent import (
|
||||||
|
_ROUNDTABLE_THREAD_METADATA,
|
||||||
_auth_headers,
|
_auth_headers,
|
||||||
_create_thread,
|
_create_thread,
|
||||||
_flatten_content,
|
_flatten_content,
|
||||||
@@ -237,7 +238,7 @@ async def stream_recommend(request: Request, payload: dict = Body(...)) -> Strea
|
|||||||
async with _make_loopback_client(request) as client:
|
async with _make_loopback_client(request) as client:
|
||||||
try:
|
try:
|
||||||
# 每次推荐独立 thread;prompt 已包含完整 intent + 候选列表,无需多轮。
|
# 每次推荐独立 thread;prompt 已包含完整 intent + 候选列表,无需多轮。
|
||||||
thread_id = await _create_thread(client, base, headers)
|
thread_id = await _create_thread(client, base, headers, metadata=_ROUNDTABLE_THREAD_METADATA)
|
||||||
|
|
||||||
body = _run_payload(
|
body = _run_payload(
|
||||||
agent_name=RECOMMENDER_AGENT_ID,
|
agent_name=RECOMMENDER_AGENT_ID,
|
||||||
|
|||||||
Reference in new issue
Block a user