Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## [0.10.3] - 2026-09-10

### Added
- **Review history shows the LLM that actually produced each report (RENG-38)**: `CompletionResult` now carries the hitting provider's name — `complete_with_fallback` attributes a success to the exact fallback-chain config that answered, not the caller's primary — and every expert/aggregator production point snapshots `(provider, model)` onto the report (`ExpertReport.llm_provider/llm_model`, `AggregatedReport.llm_provider/llm_model`, both `Option`, `#[serde(default)]` so pre-0.10.2 JSON still deserializes). Migration `0002_llm_snapshot.sql` adds `expert_reports.llm_provider/llm_model` (denormalized per-expert snapshot — deliberately name snapshots, not FK references: `llm_providers` is rewritten DELETE+INSERT on every config save, so foreign keys would dangle) and `reviews.llm_summary` (deduplicated `[{provider, model}]` JSON, written on terminal completion so the history list never parses `reviews.result`). API: `GET /reviews/{id}` experts gain `llmProvider`/`llmModel`, `GET /reviews` items gain `llmSummary`; the History page shows a per-expert `provider/model` tag in the detail drawer and a compact LLM column in the list. Records predating 0.10.2 carry NULLs and render as "未知"/not shown — never an error. Note: the lead-overview, verifier, and adjudicator passes also call the LLM but produce no expert report row, so their provider/model is not yet snapshotted (deferred). (`src/llm/provider.rs`, `src/llm/client/mod.rs`, `src/models/finding.rs`, `src/team/orchestrator/pipeline.rs`, `src/team/orchestrator/mod.rs`, `src/expert/mod.rs`, `migrations/0002_llm_snapshot.sql`, `src/store/rows.rs`, `src/store/sqlx.rs`, `src/server/task_queue.rs`, `src/server/api/types.rs`, `src/server/api/review/task.rs`, `frontend/src/types/history.ts`, `frontend/src/services/reviews.ts`, `frontend/src/views/ReviewHistory.vue`, `frontend/src/i18n/locales/*` ×6)

## [0.10.2] - 2026-09-08

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "review-engine"
version = "0.10.2"
version = "0.10.3"
license = "Apache-2.0"
edition = "2021"

Expand Down
5 changes: 5 additions & 0 deletions frontend/src/i18n/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,11 +183,16 @@ export default {
author: 'Author',
status: 'Status',
score: 'Score',
llm: 'LLM',
duration: 'Duration',
created: 'Created',
time: 'Time',
actions: 'Actions',
},
llm: {
unknown: 'unknown',
expertTooltip: 'LLM that actually produced this report (provider/model)',
},
actions: {
rerun: 'Re-run review',
viewDetails: 'View details',
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/i18n/locales/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,16 @@ export default {
author: 'Auteur',
status: 'Statut',
score: 'Score',
llm: 'LLM',
duration: 'Durée',
created: 'Créée',
time: 'Heure',
actions: 'Actions',
},
llm: {
unknown: 'inconnu',
expertTooltip: 'LLM ayant réellement produit ce rapport (provider/model)',
},
actions: {
rerun: 'Relancer la revue',
viewDetails: 'Voir les détails',
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/i18n/locales/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,11 +174,16 @@ export default {
author: '作成者',
status: 'ステータス',
score: 'スコア',
llm: 'LLM',
duration: '所要時間',
created: '作成日時',
time: '時間',
actions: '操作',
},
llm: {
unknown: '不明',
expertTooltip: 'このレポートを実際に生成した LLM(provider/model)',
},
actions: {
rerun: 'レビューを再実行',
viewDetails: '詳細を表示',
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/i18n/locales/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,16 @@ export default {
author: '작성자',
status: '상태',
score: '점수',
llm: 'LLM',
duration: '소요 시간',
created: '생성 시간',
time: '시간',
actions: '작업',
},
llm: {
unknown: '알 수 없음',
expertTooltip: '이 리포트를 실제로 생성한 LLM (provider/model)',
},
actions: {
rerun: '리뷰 다시 실행',
viewDetails: '상세 보기',
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/i18n/locales/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,11 +171,16 @@ export default {
author: '作者',
status: '状态',
score: '得分',
llm: 'LLM',
duration: '耗时',
created: '创建时间',
time: '时间',
actions: '操作',
},
llm: {
unknown: '未知',
expertTooltip: '实际生成该报告的 LLM(provider/model)',
},
actions: {
rerun: '重新评审',
viewDetails: '查看详情',
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/i18n/locales/zh-TW.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,11 +171,16 @@ export default {
author: '作者',
status: '狀態',
score: '得分',
llm: 'LLM',
duration: '耗時',
created: '建立時間',
time: '時間',
actions: '操作',
},
llm: {
unknown: '未知',
expertTooltip: '實際產生該報告的 LLM(provider/model)',
},
actions: {
rerun: '重新審查',
viewDetails: '查看詳情',
Expand Down
7 changes: 6 additions & 1 deletion frontend/src/services/reviews.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { request } from './api';
import { normalizeStatus } from './status';
import type { ReviewListItem, ReviewDetail, HistoryFilters, RiskLevel, ReviewAssessment } from '../types/history';
import type { ReviewListItem, ReviewDetail, HistoryFilters, RiskLevel, ReviewAssessment, LlmUsage } from '../types/history';

export interface ReviewsListResponse {
items: ReviewListItem[];
Expand Down Expand Up @@ -40,6 +40,8 @@ interface RawReviewItem {
createdAt?: string;
gitlab_mr_url?: string | null;
gitlabMrUrl?: string;
/** RENG-38: deduplicated LLM pairs (`reviews.llm_summary` snapshot). */
llmSummary?: LlmUsage[] | null;
/** Embedded full `ReviewOutput` JSON (carries `consolidated.assessment`). */
result?: unknown;
}
Expand Down Expand Up @@ -115,6 +117,9 @@ function normalizeReviewListItem(raw: RawReviewItem): ReviewListItem {
createdAt: raw.createdAt ?? raw.created_at ?? '',
gitlabMrUrl: raw.gitlabMrUrl ?? raw.gitlab_mr_url ?? undefined,
assessment: extractAssessment(raw.result),
// Pass the snapshot through only when it is a non-empty array — anything
// else (null, missing, malformed) degrades to "unknown" at render time.
llmSummary: Array.isArray(raw.llmSummary) && raw.llmSummary.length > 0 ? raw.llmSummary : undefined,
};
}

Expand Down
16 changes: 16 additions & 0 deletions frontend/src/types/history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ export interface ReviewAuthor {
email?: string
}

/**
* One LLM `provider/model` pair observed during a review (RENG-38), from the
* backend's `reviews.llm_summary` snapshot (`ReviewOutput::llm_usages`).
*/
export interface LlmUsage {
provider: string
model: string
}

export interface ExpertResult {
expertId: string
expertName: string
Expand All @@ -36,6 +45,10 @@ export interface ExpertResult {
summary: string
// Raw LLM response (`report.raw_llm_response`); debugging aid only.
details?: string
// RENG-38: name snapshot of the LLM that actually produced this report
// (the fallback-chain hit). Absent for records predating 0.10.2.
llmProvider?: string | null
llmModel?: string | null
}

export interface ReviewListItem {
Expand All @@ -51,6 +64,9 @@ export interface ReviewListItem {
createdAt: string
gitlabMrUrl?: string
assessment?: ReviewAssessment
// RENG-38: deduplicated LLM pairs used by this review (`reviews.llm_summary`).
// Absent for records predating 0.10.2 and non-completed tasks.
llmSummary?: LlmUsage[] | null
}

export interface ReviewDetail {
Expand Down
58 changes: 56 additions & 2 deletions frontend/src/views/ReviewHistory.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
import { ElMessage, ElMessageBox, ElNotification } from 'element-plus'
import { useI18n } from 'vue-i18n'
import type { ApiError } from '../services/api'
import type { ReviewListItem, HistoryFilters, RiskLevel } from '../types/history'
import type { ReviewListItem, ExpertResult, HistoryFilters, RiskLevel } from '../types/history'
import { getReviews } from '../services/reviews'
import { useReviews } from '../composables/useReviews'
import StatusBadge from '../components/ReviewHistory/StatusBadge.vue'
Expand Down Expand Up @@ -424,6 +424,21 @@ function riskLabelKey(level: RiskLevel): string {
return 'history.riskLevel.' + riskLevelKeys[level]
}

/* ─────────────── LLM usage snapshot (RENG-38) ─────────────── */

/** Compact `provider/model` form for one expert's LLM tag; null when the
record predates the 0.10.2 snapshot (rendered as "未知"/"Unknown"). */
function expertLlmLabel(exp: ExpertResult): string | null {
if (!exp.llmProvider && !exp.llmModel) return null
return `${exp.llmProvider ?? t('history.llm.unknown')}/${exp.llmModel ?? t('history.llm.unknown')}`
}

/** Compact list-cell form of the deduplicated `llmSummary` snapshot. */
function formatLlmSummary(usages: ReviewListItem['llmSummary']): string {
if (!usages || usages.length === 0) return '-'
return usages.map((u) => `${u.provider}/${u.model}`).join(', ')
}

const hasRawComment = computed(
() => !!selectedReview.value?.rawComment?.trim()
)
Expand Down Expand Up @@ -622,6 +637,14 @@ watch(() => route.query, () => {
</template>
</el-table-column>

<!-- RENG-38: LLM snapshot column — compact `provider/model` pairs,
'-' for records predating the 0.10.2 snapshot. -->
<el-table-column :label="$t('history.columns.llm')" min-width="150" class-name="col-llm">
<template #default="{ row }">
<span class="llm-cell">{{ formatLlmSummary(row.llmSummary) }}</span>
</template>
</el-table-column>

<el-table-column :label="$t('history.columns.duration')" width="100" sortable :sort-by="['durationMs']">
<template #default="{ row }">
<span class="duration-text">{{ formatDuration(row.durationMs) }}</span>
Expand Down Expand Up @@ -776,14 +799,24 @@ watch(() => route.query, () => {
<div class="expert-title">
<span>{{ exp.expertName }}</span>
<div class="expert-meta">
<StatusBadge :status="exp.status" size="small" />
<!-- Success is the default state: only flag non-success
statuses (warning/error/skipped) on the collapsed row. -->
<StatusBadge v-if="exp.status !== 'success'" :status="exp.status" size="small" />
<el-tag v-if="exp.score" size="small" :type="exp.score >= 80 ? 'success' : exp.score >= 60 ? 'warning' : 'danger'">
{{ exp.score }}
</el-tag>
</div>
</div>
</template>
<div class="expert-content">
<!-- RENG-38: which LLM actually produced this report; shown as a
plain meta row at the top of the expanded panel, absent for
pre-0.10.2 records (no snapshot -> nothing rendered). -->
<div v-if="expertLlmLabel(exp)" class="expert-llm-meta">
<el-tooltip :content="$t('history.llm.expertTooltip')" placement="bottom">
<el-tag size="small" effect="plain" class="llm-tag">{{ expertLlmLabel(exp) }}</el-tag>
</el-tooltip>
</div>
<!-- `summary` carries the curated pre-rendered Markdown report;
MarkdownView renders it (marked -> DOMPurify sanitized). -->
<div class="expert-markdown">
Expand Down Expand Up @@ -1167,6 +1200,27 @@ watch(() => route.query, () => {
margin-right: 50px;
}

/* RENG-38: per-expert LLM snapshot tag (provider/model). Mono so model IDs
stay scannable; ellipsized when a provider ships a very long model name. */
.llm-tag {
font-family: var(--font-mono, monospace);
max-width: 220px;
overflow: hidden;
text-overflow: ellipsis;
}

/* RENG-38: history list LLM column cell. */
.llm-cell {
font-family: var(--font-mono, monospace);
font-size: 12px;
color: var(--text-secondary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
max-width: 100%;
}

.expert-content {
padding: 8px 0;
display: flex;
Expand Down
15 changes: 15 additions & 0 deletions migrations/0002_llm_snapshot.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-- 0.10.2 (RENG-38):评审历史的 LLM 使用快照。
-- 报告侧冗余名称快照(不做外键、不做软删):llm_providers 是整表
-- DELETE+INSERT 语义,行 id 每次保存都会重生成,外键引用必然悬空;
-- 历史展示要的是"当时实际用了哪个 provider/model",存名称快照即可。
-- 方言约束与 0001 一致:占位符 `?`(由 store 层重写)、JSON 一律 TEXT。
-- 旧行三列均为 NULL,前端显示「未知」/不显示,不做回填。

-- 每条专家报告实际使用的 LLM(命中 fallback 链中第几个 config 就记哪个)。
ALTER TABLE expert_reports ADD COLUMN llm_provider TEXT;
ALTER TABLE expert_reports ADD COLUMN llm_model TEXT;

-- 评审级去重后的 provider/model 对列表(JSON 数组 TEXT,
-- 形如 [{"provider":"xiaomi","model":"mimo-v2.5-pro"}]),
-- 供历史列表页免解析 reviews.result 直接展示。
ALTER TABLE reviews ADD COLUMN llm_summary TEXT;
2 changes: 2 additions & 0 deletions src/actions/repo_review/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,8 @@ pub async fn run_repo_review(
raw_llm_response: String::new(),
parse_error: None,
raw_dump_path: None,
llm_provider: None,
llm_model: None,
}];
let dropped =
crate::team::verifier::verify_findings(&mut reports, &[], local_path, llm_configs, max_file_bytes)
Expand Down
6 changes: 6 additions & 0 deletions src/cli/handlers/ask.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ pub async fn run_ask(
raw_llm_response: String::new(),
parse_error: None,
raw_dump_path: None,
llm_model: None,
llm_provider: None,
}],
aggregated: None,
dropped_findings: vec![],
Expand Down Expand Up @@ -103,6 +105,8 @@ async fn run_ask_with_diff(
raw_llm_response: String::new(),
parse_error: None,
raw_dump_path: None,
llm_model: None,
llm_provider: None,
}],
aggregated: None,
dropped_findings: vec![],
Expand Down Expand Up @@ -176,6 +180,8 @@ pub async fn run_ask_local_repo(
raw_llm_response: String::new(),
parse_error: None,
raw_dump_path: None,
llm_model: None,
llm_provider: None,
}],
aggregated: None,
dropped_findings: vec![],
Expand Down
2 changes: 2 additions & 0 deletions src/cli/handlers/changelog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ pub async fn run_update_changelog(
raw_llm_response: String::new(),
parse_error: None,
raw_dump_path: None,
llm_model: None,
llm_provider: None,
}],
aggregated: None,
dropped_findings: vec![],
Expand Down
6 changes: 6 additions & 0 deletions src/cli/handlers/describe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ pub async fn run_describe(
raw_llm_response: String::new(),
parse_error: None,
raw_dump_path: None,
llm_model: None,
llm_provider: None,
}],
aggregated: None,
dropped_findings: vec![],
Expand Down Expand Up @@ -114,6 +116,8 @@ pub async fn run_describe_local_diff(
raw_llm_response: String::new(),
parse_error: None,
raw_dump_path: None,
llm_model: None,
llm_provider: None,
}],
aggregated: None,
dropped_findings: vec![],
Expand Down Expand Up @@ -177,6 +181,8 @@ pub async fn run_describe_local_repo(
raw_llm_response: String::new(),
parse_error: None,
raw_dump_path: None,
llm_model: None,
llm_provider: None,
}],
aggregated: None,
dropped_findings: vec![],
Expand Down
Loading
Loading