第二次生成结果后台挂起
This commit is contained in:
1 parent
19399482e1
commit
90bced3ed2
12 files changed
+465
-45
No files matched your search
@@ -149,12 +149,26 @@ async def _run_agent_turn(
|
||||
|
||||
|
||||
def _read_report_html(app, thread_id: str, user_id: str) -> str | None:
|
||||
"""从 report thread 的 outputs/方案总览.html 读出报告 HTML。"""
|
||||
"""从 report thread 的 outputs 目录读出报告 HTML。
|
||||
|
||||
优先 ``方案总览.html``;找不到就扫目录里**任意 .html**(取体积最大的那个),
|
||||
容忍报告 agent 用了别的文件名。
|
||||
"""
|
||||
try:
|
||||
outputs_dir = get_paths().sandbox_outputs_dir(thread_id, user_id=user_id)
|
||||
path = outputs_dir / _REPORT_FILE
|
||||
if path.exists():
|
||||
return path.read_text(encoding="utf-8")
|
||||
preferred = outputs_dir / _REPORT_FILE
|
||||
if preferred.exists():
|
||||
return preferred.read_text(encoding="utf-8")
|
||||
if outputs_dir.exists():
|
||||
htmls = sorted(
|
||||
outputs_dir.glob("*.html"),
|
||||
key=lambda p: p.stat().st_size,
|
||||
reverse=True,
|
||||
)
|
||||
if htmls:
|
||||
logger.info("report html: 方案总览.html not found, using %s", htmls[0].name)
|
||||
return htmls[0].read_text(encoding="utf-8")
|
||||
logger.warning("report html: no .html found in %s", outputs_dir)
|
||||
except Exception: # noqa: BLE001
|
||||
logger.warning("read report html failed for thread %s", thread_id, exc_info=True)
|
||||
return None
|
||||
|
||||
@@ -106,23 +106,26 @@ class RoundtableJobExecutor:
|
||||
mode=params.mode,
|
||||
intent_text=params.intent_text,
|
||||
)
|
||||
# 完成且有报告:写回关联草稿的 step3 + 推进 furthest_step=3,
|
||||
# 这样用户从历史「查看报告」加载草稿即可看到最终方案(沿用既有 hydrateStep3 路径)。
|
||||
if (
|
||||
result.status == "done"
|
||||
and result.step3
|
||||
and params.draft_id
|
||||
and self._draft_store is not None
|
||||
):
|
||||
# 完成且有报告:
|
||||
# 1) 把 step3 挂到 job 上 —— 前端「多运行」模型从单个 job 即可拿到完整结果
|
||||
# (对话 dialogues + 报告 step3)组装一个 run;
|
||||
# 2) 同时写回关联草稿的顶层 step3 + 推进 furthest_step=3,兼容既有
|
||||
# 「查看报告 → 加载草稿 → Step3」路径。
|
||||
if result.status == "done" and result.step3:
|
||||
try:
|
||||
await self._draft_store.update_draft(
|
||||
params.draft_id,
|
||||
params.user_id,
|
||||
step3=result.step3,
|
||||
furthest_step=3,
|
||||
)
|
||||
except Exception: # noqa: BLE001 — 写报告失败不影响作业本身已 done
|
||||
logger.warning("persist step3 to draft %s failed", params.draft_id, exc_info=True)
|
||||
await self._store.update_progress(params.job_id, step3=result.step3)
|
||||
except Exception: # noqa: BLE001
|
||||
logger.warning("persist step3 to job %s failed", params.job_id, exc_info=True)
|
||||
if params.draft_id and self._draft_store is not None:
|
||||
try:
|
||||
await self._draft_store.update_draft(
|
||||
params.draft_id,
|
||||
params.user_id,
|
||||
step3=result.step3,
|
||||
furthest_step=3,
|
||||
)
|
||||
except Exception: # noqa: BLE001 — 写报告失败不影响作业本身已 done
|
||||
logger.warning("persist step3 to draft %s failed", params.draft_id, exc_info=True)
|
||||
except asyncio.CancelledError:
|
||||
# 干净停止即可,**不在被取消的任务里写 DB**:取消态下二次 await 会再抛
|
||||
# CancelledError、并可能打断进行中的 sqlite 连接。``cancelled`` 终态由
|
||||
|
||||
@@ -98,6 +98,7 @@ class RoundtableJobResponse(BaseModel):
|
||||
chain: dict[str, Any] | None = None
|
||||
error: str | None = None
|
||||
dialogues: list[dict[str, Any]] = Field(default_factory=list)
|
||||
step3: dict[str, Any] | None = None
|
||||
createdAt: datetime | str | None = None
|
||||
updatedAt: datetime | str | None = None
|
||||
|
||||
@@ -149,6 +150,7 @@ def _to_response(row: dict[str, Any]) -> RoundtableJobResponse:
|
||||
chain=row.get("chain"),
|
||||
error=row.get("error"),
|
||||
dialogues=row.get("dialogues") or [],
|
||||
step3=row.get("step3"),
|
||||
createdAt=row.get("created_at"),
|
||||
updatedAt=row.get("updated_at"),
|
||||
)
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
"""Add step3 column to roundtable_jobs.
|
||||
|
||||
把作业的 Step3 报告({html, generatedAt, model, summary})直接挂在 job 上,使前端
|
||||
「多运行」模型(draft.step2.runs[])能从单个 job 读到完整结果(对话 + 报告)组装一个 run,
|
||||
而不必依赖草稿顶层的单一 step3。LONGTEXT(HTML 可超 64 KB)。
|
||||
|
||||
Revision ID: 20260605_02
|
||||
Revises: 20260605_01
|
||||
Create Date: 2026-06-05
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
from sqlalchemy import inspect
|
||||
from sqlalchemy.dialects.mysql import LONGTEXT
|
||||
|
||||
|
||||
revision = "20260605_02"
|
||||
down_revision = "20260605_01"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def _has_table(table_name: str) -> bool:
|
||||
inspector = inspect(op.get_bind())
|
||||
return table_name in inspector.get_table_names()
|
||||
|
||||
|
||||
def _has_column(table_name: str, column_name: str) -> bool:
|
||||
if not _has_table(table_name):
|
||||
return False
|
||||
inspector = inspect(op.get_bind())
|
||||
return any(col["name"] == column_name for col in inspector.get_columns(table_name))
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
if _has_table("roundtable_jobs") and not _has_column("roundtable_jobs", "step3"):
|
||||
col_type = LONGTEXT() if op.get_bind().dialect.name == "mysql" else sa.Text()
|
||||
op.add_column("roundtable_jobs", sa.Column("step3", col_type, nullable=True))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
if _has_column("roundtable_jobs", "step3"):
|
||||
op.drop_column("roundtable_jobs", "step3")
|
||||
+3
@@ -58,6 +58,9 @@ class RoundtableJobRow(Base):
|
||||
# 研讨对话记录:JSON array of { id, role('leader'|'seat'), agentId, name, content, cycle }。
|
||||
# 让用户跑完 / 进行中都能看到真实研讨内容(leader 说明 + 各席位交付),而非只有流程图。
|
||||
dialogues: Mapped[str | None] = mapped_column(PortableLongText, nullable=True)
|
||||
# Step3 报告快照:JSON object { html, generatedAt, model, summary }。done 时写入,
|
||||
# 使前端从单个 job 即可拿到完整结果(对话 + 报告)。
|
||||
step3: Mapped[str | None] = mapped_column(PortableLongText, nullable=True)
|
||||
# Terminal error detail when status=error.
|
||||
error: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
# 时间列必须用 BeijingDateTime(项目规范,与 ai_writing_sessions / roundtable_drafts 对齐)。
|
||||
|
||||
+4
-1
@@ -55,7 +55,7 @@ class RoundtableJobRepository:
|
||||
for k in ("created_at", "updated_at"):
|
||||
if isinstance(d.get(k), datetime):
|
||||
d[k] = d[k].isoformat()
|
||||
for k in ("dispatch_chain", "thread_ids", "chain", "dialogues"):
|
||||
for k in ("dispatch_chain", "thread_ids", "chain", "dialogues", "step3"):
|
||||
d[k] = _loads(d.get(k))
|
||||
return d
|
||||
|
||||
@@ -119,6 +119,7 @@ class RoundtableJobRepository:
|
||||
thread_ids=_UNSET,
|
||||
coordinator_name=_UNSET,
|
||||
dialogues=_UNSET,
|
||||
step3=_UNSET,
|
||||
error=_UNSET,
|
||||
) -> dict[str, Any] | None:
|
||||
resolved_user_id = resolve_user_id(user_id, method_name="RoundtableJobRepository.update_progress")
|
||||
@@ -148,6 +149,8 @@ class RoundtableJobRepository:
|
||||
row.coordinator_name = coordinator_name
|
||||
if dialogues is not _UNSET:
|
||||
row.dialogues = _dumps(dialogues)
|
||||
if step3 is not _UNSET:
|
||||
row.step3 = _dumps(step3)
|
||||
if error is not _UNSET:
|
||||
row.error = error
|
||||
row.updated_at = datetime.now(UTC)
|
||||
|
||||
Reference in new issue
Block a user