From 47642353435ed14e9c1b3ee41e3594a70df6e897 Mon Sep 17 00:00:00 2001 From: xsl Date: Fri, 14 Aug 2026 21:22:28 +0800 Subject: [PATCH] fix(retrieval): list_gaps returns ORM entities; admin switches back to the domain API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit T12 集成实测暴露:select(KbGap) 的结果调 .mappings(),行键是实体类, GapSummary 按列名取值必然 AttributeError。改为 .scalars() 返回实体; admin 的缺口看板从临时自实现切回 retrieval.list_gaps(语义单一定义)。 --- apps/admin/routers/gaps.py | 39 ++++++++++++-------------------------- packages/retrieval/repo.py | 10 +++++++--- 2 files changed, 19 insertions(+), 30 deletions(-) diff --git a/apps/admin/routers/gaps.py b/apps/admin/routers/gaps.py index 7083a08..fb222c8 100644 --- a/apps/admin/routers/gaps.py +++ b/apps/admin/routers/gaps.py @@ -3,11 +3,9 @@ 数据来自检索无结果时的自动埋点(T-P2-06),看板只读:它回答「下一批内容 该生产什么」,是内容排期的输入,不是待办队列。 -偏差说明(详见提交信息):协调提交提供的 ``packages.retrieval.list_gaps`` -对 ``select(KbGap)`` 的结果调 ``.mappings()``,行键是实体名而非列名, -``GapSummary(row)`` 取 ``row.gap_id`` 必然 ``AttributeError``(该路径无既有 -测试覆盖,真库一跑即暴露)。``packages/**`` 本任务禁改,故此处以等价查询 -在 admin 包内自实现;上游修复后应切回 retrieval 的 ``list_gaps``。 +数据访问走 retrieval 域的 ``list_gaps``(T12 开发时它有个 mappings 误用 +的缺陷,当时在 admin 内自实现了等价查询;上游已在合并阶段修复,现切回, +缺口语义只有一处定义)。 """ from __future__ import annotations @@ -15,44 +13,31 @@ from __future__ import annotations from typing import Any from fastapi import APIRouter, Depends, Query -from sqlalchemy import select -from sqlalchemy.ext.asyncio import AsyncSession from apps.admin.deps import SessionDep, admin_auth from apps.admin.schemas import GapOut -from packages.shared.models import KbGap +from packages.retrieval import service as retrieval_service from packages.shared.response import ok router = APIRouter(prefix="/kb", tags=["admin:gaps"], dependencies=[Depends(admin_auth)]) -async def list_gap_rows(session: AsyncSession, *, limit: int, offset: int) -> list[KbGap]: - """kb_gap 看板查询:hit_count 降序,同频次按最近出现降序(与 retrieval 语义一致)。""" - result = await session.execute( - select(KbGap) - .order_by(KbGap.hit_count.desc(), KbGap.last_seen_at.desc()) - .limit(limit) - .offset(offset) - ) - return list(result.scalars().all()) - - @router.get("/gaps", summary="知识缺口列表(hit_count 降序:最常被问到的排最前)") async def list_gaps( session: SessionDep, limit: int = Query(default=50, ge=1, le=200), offset: int = Query(default=0, ge=0), ) -> dict[str, Any]: - rows = await list_gap_rows(session, limit=limit, offset=offset) + summaries = await retrieval_service.list_gaps(session, limit=limit, offset=offset) items = [ GapOut( - gap_id=str(row.gap_id), - query_text=row.query_text, - filter_ctx=dict(row.filter_ctx), - hit_count=row.hit_count, - first_seen_at=row.first_seen_at, - last_seen_at=row.last_seen_at, + gap_id=str(g.gap_id), + query_text=g.query_text, + filter_ctx=dict(g.filter_ctx), + hit_count=g.hit_count, + first_seen_at=g.first_seen_at, + last_seen_at=g.last_seen_at, ).model_dump() - for row in rows + for g in summaries ] return ok({"items": items, "total": len(items), "limit": limit, "offset": offset}) diff --git a/packages/retrieval/repo.py b/packages/retrieval/repo.py index a123ed9..bf631cd 100644 --- a/packages/retrieval/repo.py +++ b/packages/retrieval/repo.py @@ -268,8 +268,12 @@ async def record_gap( INVALIDATION_VERSION = 1 -async def list_gaps(session: AsyncSession, *, limit: int = 50, offset: int = 0) -> list[RowMapping]: - """缺口看板查询:hit_count 降序,同频次按最近出现降序。""" +async def list_gaps(session: AsyncSession, *, limit: int = 50, offset: int = 0) -> list[KbGap]: + """缺口看板查询:hit_count 降序,同频次按最近出现降序。 + + 返回 ORM 实体而非 mappings:单实体 select 的 mapping 行键是实体类, + 按列名取值必然 AttributeError(T12 集成实测暴露,此处修正)。 + """ from sqlalchemy import select from packages.shared.models import KbGap @@ -281,4 +285,4 @@ async def list_gaps(session: AsyncSession, *, limit: int = 50, offset: int = 0) .offset(offset) ) result = await session.execute(stmt) - return list(result.mappings().all()) + return list(result.scalars().all())