From 558498a4bb53fa33ec54b5d031d41a1b04999aaf Mon Sep 17 00:00:00 2001 From: admin <572701190@qq.com> Date: Sat, 2 May 2026 04:44:17 +0800 Subject: [PATCH] Revert "Prioritize doctor instructions in AI report prompts" This reverts commit 55622368e3bf6228a847329a50778703974f8dcb. --- AGENTS.md | 1 - README.md | 1 - docs/design.md | 2 +- docs/modules/report-editor.md | 10 ------ docs/progress.md | 1 - docs/testing.md | 1 - src/pages/ReportEditor.tsx | 21 ++++-------- src/utils/aiPrompt.test.ts | 37 -------------------- src/utils/aiPrompt.ts | 63 ----------------------------------- 9 files changed, 8 insertions(+), 129 deletions(-) delete mode 100644 src/utils/aiPrompt.test.ts delete mode 100644 src/utils/aiPrompt.ts diff --git a/AGENTS.md b/AGENTS.md index 00e0710..c1c4dba 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -351,7 +351,6 @@ PostgreSQL 数据模型。当前覆盖 `Tenant`、`Department`、`User`、`UserS - 后端模板 DTO 和权限资源映射 - 模板列表合并工具,防止新增模板被旧 `localStorage.templates` 覆盖 - 模板导入导出工具,覆盖 HTML 模板包生成、字段库元数据回导和旧 JSON 导入兼容 -- AI Prompt 生成工具,确保医生指令和结构化报告字段优先于默认模板旧内容 - 后端用户 DTO 和部门模板授权映射 - 后端系统设置 schema 校验 - 后端 AI 入参和讯飞语音代理帧处理 diff --git a/README.md b/README.md index f9978ae..d4e08f1 100644 --- a/README.md +++ b/README.md @@ -201,7 +201,6 @@ cp .env.example .env.local - 本地存储封装和系统设置兼容。 - 默认报告模板结构和字段配置。 - 模板 HTML 导出包字段库元数据。 -- AI Prompt 优先级,避免默认模板旧术式覆盖医生新指令。 - 打印导出入口。 - 后端权限策略、AI 入参和语音代理帧处理。 diff --git a/docs/design.md b/docs/design.md index d6c2cee..0ec2374 100644 --- a/docs/design.md +++ b/docs/design.md @@ -90,7 +90,7 @@ src/ - `data-mode="frame"` 或无 `manual` 标记的占位符可接收关键帧。 - `data-mode="manual"` 用于 Logo、签名等静态图片,不接收视频关键帧拖入。 -AI 可编辑区域通过 `.ai-region[data-ai-id]` 和内部 `.ai-content` 定位。AI 返回 `updatedHtml` 后,系统先显示差异确认,再注入目标区域。AI prompt 会把医生指令和结构化报告字段放在最高优先级,旧目标区域 HTML 和整份报告正文只作为低优先级参考,避免默认模板中的旧术式描述持续影响新生成内容。 +AI 可编辑区域通过 `.ai-region[data-ai-id]` 和内部 `.ai-content` 定位。AI 返回 `updatedHtml` 后,系统先显示差异确认,再注入目标区域。 ## 打印导出设计 diff --git a/docs/modules/report-editor.md b/docs/modules/report-editor.md index d65b218..a8e678f 100644 --- a/docs/modules/report-editor.md +++ b/docs/modules/report-editor.md @@ -71,16 +71,6 @@ AI 面板支持两种模式: - 对话模式:根据当前报告内容和图片上下文回答问题。 - 修改模式:选中 `.ai-region` 后要求模型返回 JSON,其中包含 `reply` 和 `updatedHtml`。 -AI 提议版本由以下内容生成: - -- 医生在 AI 输入框中的指令。 -- 当前结构化报告字段,包括手术名称、术前诊断、术中/术后诊断、麻醉方式和报告备注。 -- 整份报告正文文本,作为低优先级上下文。 -- 当前选中的 AI 可编辑区域 HTML,作为低优先级格式和旧内容参考。 -- 用户选择的报告图片或上传给 AI 的图片。 - -Prompt 明确规定优先级为:医生指令 > 当前结构化报告字段 > 图片内容 > 旧目标区域/整份报告正文。默认模板中的胆囊、肝脏等旧术式描述不得覆盖医生新指令;如果医生要求生成新的术式或不同部位,AI 应围绕新术式重写目标区域。 - 编辑器会监听正文中的 `.ai-region` 变化;用户插入新的 AI 可编辑区域后,AI 撰写面板的目标下拉栏会立即更新,不需要离开页面或刷新。 模型返回 HTML 后,系统会清理换行和 `
`,生成差异预览。用户确认后才写入目标 `.ai-content`。 diff --git a/docs/progress.md b/docs/progress.md index b4d0638..8a984ce 100644 --- a/docs/progress.md +++ b/docs/progress.md @@ -89,4 +89,3 @@ | 2026-05-02 | 修复报告编辑器新增 AI 可编辑区域后 AI 撰写下拉栏不立即更新的问题,并补充 E2E。 | | 2026-05-02 | 移除前端用户可见 JSON 导出入口,保留模板历史 JSON 导入兼容。 | | 2026-05-02 | 模板 HTML 导出包补充模板字段和字段管理设置,导入时恢复字段库元数据。 | -| 2026-05-02 | 调整报告编辑器 AI Prompt 优先级,医生指令和结构化报告字段优先于默认模板旧术式内容。 | diff --git a/docs/testing.md b/docs/testing.md index 5f8df82..d12fe4a 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -82,7 +82,6 @@ AI 第三方接口、讯飞语音上游 WebSocket、麦克风权限和真实视 | 系统设置混淆兼容 | 已覆盖 | `storage.test.ts` | | 默认报告模板结构 | 已覆盖 | `defaultContent.test.ts` | | 默认字段和 AI Provider | 已覆盖 | `defaultContent.test.ts` | -| AI Prompt 优先级 | 已覆盖 | `aiPrompt.test.ts`,覆盖医生指令和结构化字段优先于旧模板内容。 | | 打印导出入口 | 已覆盖 | `print.test.ts` | | 模板列表合并工具 | 已覆盖 | `templateList.test.ts`,防止新增模板被旧本地缓存覆盖。 | | 模板导入导出工具 | 已覆盖 | `templateExport.test.ts`,覆盖 HTML 模板包、字段库元数据回导、旧 JSON 导入兼容和文件名清理。 | diff --git a/src/pages/ReportEditor.tsx b/src/pages/ReportEditor.tsx index 2111a9b..524f70d 100644 --- a/src/pages/ReportEditor.tsx +++ b/src/pages/ReportEditor.tsx @@ -22,7 +22,6 @@ import { getSpeechIatWebSocketUrl } from '../api/speech'; import { getFieldLibrary, updateFieldLibrary } from '../api/library'; import { listFiles, uploadFileResource } from '../api/files'; import { isLocalFallbackEnabled } from '../config/runtime'; -import { buildSurgicalAiPrompt } from '../utils/aiPrompt'; import { diffChars } from 'diff'; type AudioWindow = Window & typeof globalThis & { @@ -1285,20 +1284,14 @@ export default function ReportEditor() { } const currentHtml = targetRegionEl ? targetRegionEl.innerHTML.replace(/​/g, '').replace(/>(\s+)<').trim() : ''; const globalContextText = editorRef.current?.innerText || ''; - const targetTitle = aiRegions.find(region => region.id === actualTargetId)?.title - || (aiRegion as HTMLElement | null)?.dataset.aiTitle - || actualTargetId; let messageContent: any; const selectedEditorImageUrls = editorImages.filter(img => aiSelectedEditorImages.includes(img.id)).map(img => img.src); const allImages = [...selectedEditorImageUrls, ...aiUploadedImages.map(i => i.dataUrl)]; - const promptText = buildSurgicalAiPrompt({ - reportData, - globalContextText, - currentHtml, - doctorInstruction: text, - modifyEnabled: aiModifyEnabled && Boolean(targetRegionEl), - targetTitle, - }); + let promptText = `【全局手术报告参考内容】:\n${globalContextText}\n\n`; + if (aiModifyEnabled && targetRegionEl) { + promptText += `【你需要进行修改的目标区域 HTML 源码】:\n${currentHtml || '(当前区域为空)'}\n\n`; + } + promptText += `【医生指令】: ${text}\n\n【格式要求】:\n1. 仅针对【目标区域】的主题生成对应的多段落 HTML 内容\n2. ⚠️ 绝对禁止将【全局参考内容】中的其他模块(如:基本信息、术后情况、标本描述、病理结果、医生签名、日期等)生成并混入你的输出中!你只能输出该区域应有的内容\n3. 段落使用

标签,段落之间不要使用
标签或换行符\n4. 输出紧凑 HTML,标签间不要有空格或换行`; if (allImages.length > 0) { messageContent = []; allImages.forEach(url => { @@ -1309,8 +1302,8 @@ export default function ReportEditor() { messageContent = promptText; } const systemPrompt = aiModifyEnabled - ? '你是一名专业的外科医生助理。当前处于【修改模式】。\n请根据用户的【医生指令】直接重写目标区域 HTML。优先级必须是:医生指令 > 当前结构化报告字段 > 图片内容 > 旧目标区域/整份报告参考文本。\n\n重要指令:\n1. 必须返回合法的 JSON 对象\n2. 必须包含 "reply"(简短回复)和 "updatedHtml"(修改后的完整 HTML 代码)两个字段\n3. 如果医生要求生成新的术式、其他手术或不同部位,必须抛开旧目标区域中的默认模板内容,不要继续写胆囊、肝脏等旧术式描述\n4. 【内容边界】:全局参考内容仅供理解上下文。updatedHtml 只能包含目标区域本身的内容(例如:如果目标区域是"手术步骤",你就只写步骤)。严禁输出签名、落款、术后总结等属于报告其他部分的结构!\n5. 段落必须使用

标签包裹,段落间绝对不要使用
标签,也不要使用换行符 (\\n)\n6. 输出的 HTML 必须紧凑,标签之间不要有空格或换行\n7. 绝对不要包含任何 Markdown 标记(如 ```json)' - : '你是一名专业的外科医生助理。当前处于【对话模式】。\n请根据【医生指令】进行专业解答。优先级必须是:医生指令 > 当前结构化报告字段 > 图片内容 > 旧正文参考文本;如果医生要求新的术式或不同部位,不要被旧正文中的胆囊、肝脏等默认模板内容带偏。\n重要指令:\n1. 必须返回合法的 JSON 对象\n2. 仅包含 "reply"(你的专业回答)一个字段\n3. 不要返回任何 HTML 代码\n4. 绝对不要包含任何 Markdown 标记\n5. 无论图片内容是什么,请严格以纯 JSON 格式输出,禁止任何多余的开头解释'; + ? '你是一名专业的外科医生助理。当前处于【修改模式】。\n我为你提供了当前手术报告的【全局参考内容】作为背景知识,以及你需要修改的【目标区域 HTML 源码】。\n请根据用户的【医生指令】,直接重写并输出目标区域的 HTML。\n\n重要指令:\n1. 必须返回合法的 JSON 对象\n2. 必须包含 "reply"(简短回复)和 "updatedHtml"(修改后的完整 HTML 代码)两个字段\n3. 【内容边界】:全局参考内容仅供你理解上下文。你的 updatedHtml 只能包含目标区域本身的内容(例如:如果目标区域是"手术步骤",你就只写步骤)。严禁输出签名、落款、术后总结等属于报告其他部分的结构!\n4. 段落必须使用

标签包裹,段落间绝对不要使用
标签,也不要使用换行符 (\\n)\n5. 输出的 HTML 必须紧凑,标签之间不要有空格或换行\n6. 绝对不要包含任何 Markdown 标记(如 ```json)' + : '你是一名专业的外科医生助理。当前处于【对话模式】。\n请仔细阅读我提供的【全局手术报告参考内容】,并根据【医生指令】进行专业解答。\n重要指令:\n1. 必须返回合法的 JSON 对象\n2. 仅包含 "reply"(你的专业回答)一个字段\n3. 不要返回任何 HTML 代码\n4. 绝对不要包含任何 Markdown 标记\n5. 无论图片内容是什么,请严格以纯 JSON 格式输出,禁止任何多余的开头解释'; const payload: any = { model: modelName, messages: [ diff --git a/src/utils/aiPrompt.test.ts b/src/utils/aiPrompt.test.ts deleted file mode 100644 index 950f90e..0000000 --- a/src/utils/aiPrompt.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import { buildSurgicalAiPrompt } from './aiPrompt'; - -describe('buildSurgicalAiPrompt', () => { - it('makes doctor instructions and structured fields higher priority than old template content', () => { - const prompt = buildSurgicalAiPrompt({ - reportData: { - title: '腹腔镜阑尾切除术报告', - preoperativeDiagnosis: '急性阑尾炎', - postoperativeDiagnosis: '急性化脓性阑尾炎', - }, - globalContextText: '默认模板旧内容:肝脏色红质软,遂行腹腔镜胆囊切除术。', - currentHtml: '

腹腔镜探查肝脏,切除胆囊。

', - doctorInstruction: '生成阑尾切除术的手术步骤', - modifyEnabled: true, - targetTitle: '手术步骤', - }); - - expect(prompt.indexOf('医生指令(最高优先级)')).toBeLessThan(prompt.indexOf('旧目标区域 HTML 源码')); - expect(prompt).toContain('手术名称: 腹腔镜阑尾切除术报告'); - expect(prompt).toContain('如果医生指令要求生成新的术式'); - expect(prompt).toContain('不要沿用旧目标区域中的胆囊、肝脏或其他默认模板内容'); - }); - - it('truncates long global context to reduce old-content anchoring', () => { - const prompt = buildSurgicalAiPrompt({ - reportData: {}, - globalContextText: '旧'.repeat(5000), - currentHtml: '', - doctorInstruction: '生成新的手术步骤', - modifyEnabled: false, - }); - - expect(prompt.length).toBeLessThan(4700); - expect(prompt).toContain('...'); - }); -}); diff --git a/src/utils/aiPrompt.ts b/src/utils/aiPrompt.ts deleted file mode 100644 index bab7ef0..0000000 --- a/src/utils/aiPrompt.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { Report } from '../types'; - -const cleanText = (value: unknown) => { - if (Array.isArray(value)) return value.filter(Boolean).join('、'); - return String(value ?? '').replace(/\s+/g, ' ').trim(); -}; - -const truncateText = (value: string, maxLength = 4000) => - value.length > maxLength ? `${value.slice(0, maxLength)}...` : value; - -export interface SurgicalAiPromptInput { - reportData: Partial & { - preoperativeDiagnosis?: unknown; - postoperativeDiagnosis?: unknown; - }; - globalContextText: string; - currentHtml: string; - doctorInstruction: string; - modifyEnabled: boolean; - targetTitle?: string; -} - -export const buildSurgicalAiPrompt = ({ - reportData, - globalContextText, - currentHtml, - doctorInstruction, - modifyEnabled, - targetTitle, -}: SurgicalAiPromptInput) => { - const structuredFields = [ - ['手术名称', reportData.title], - ['术前诊断', reportData.preoperativeDiagnosis], - ['术中/术后诊断', reportData.postoperativeDiagnosis], - ['麻醉方式', reportData.anesthesiaType], - ['报告备注', reportData.reportNote], - ] - .map(([label, value]) => { - const cleaned = cleanText(value); - return cleaned ? `${label}: ${cleaned}` : ''; - }) - .filter(Boolean) - .join('\n') || '暂无结构化字段'; - - let promptText = `【医生指令(最高优先级)】:\n${doctorInstruction}\n\n`; - promptText += `【当前结构化报告字段(高优先级,若与旧正文冲突,以这里和医生指令为准)】:\n${structuredFields}\n\n`; - promptText += `【整份报告参考文本(低优先级,仅用于患者信息、术式背景和上下文;若与医生指令或结构化字段冲突,必须忽略这里的旧术式描述)】:\n${truncateText(globalContextText)}\n\n`; - - if (modifyEnabled) { - promptText += `【目标区域】:\n${targetTitle || '未命名区域'}\n\n`; - promptText += `【旧目标区域 HTML 源码(只作为格式、段落风格和可复用事实参考;不得因为旧内容存在胆囊、肝脏等描述就继续沿用,除非医生指令或结构化字段明确要求)】:\n${currentHtml || '(当前区域为空)'}\n\n`; - } - - promptText += `【格式要求】: -1. 优先执行【医生指令】,其次参考【当前结构化报告字段】,旧目标区域和整份报告文本优先级最低 -2. 如果医生指令要求生成新的术式、其他手术或不同部位,必须围绕新术式重写,不要沿用旧目标区域中的胆囊、肝脏或其他默认模板内容 -3. 仅针对【目标区域】的主题生成对应的多段落 HTML 内容 -4. 绝对禁止将【整份报告参考文本】中的其他模块(如:基本信息、术后情况、标本描述、病理结果、医生签名、日期等)生成并混入输出 -5. 段落使用

标签,段落之间不要使用
标签或换行符 -6. 输出紧凑 HTML,标签间不要有空格或换行`; - - return promptText; -};