我们想做这么一个产品,就是,嗯,能够通过智能外设接入这个语音转文字,然后嗯,这个场景是比如说拿医院的问诊来举例。医生在通过这个智能外设进行录音的时候,会录一段医生和患者的问答,然后录完这段问答之后,外设会将语音转化成文字,然后输入在这个计算机当中。然后我们希望有个页面来收到这么一个,这么一个接收这么一个语,这么一段对话,然后调用这个大模型,通过提示词能够提取这段对话当中的一部分,这个有意义、有心、有价值的信息。然后将它转化成我在图2这个给你展示的这么一个 JSON,然后比如说患者跟医生的一段问答之后,从中提取出患者的主诉先病史、既往史等等一堆东西,然后把它提取成一个结构化的数据,然后这个结构化的数据再通过调用浏览器的某个 agent 能够将这么一个 JSON 结构化数据直接代替人去完成表单的。这个填写当然包括我普通的文字输入表单,包括下达菜单,包括单选、多选框,这些就类似我图一的这个结构,现在有没有东西?现在有没有技术能够做到
你描述的这个 “医疗问诊语音转结构化数据 + 自动填表单” 的产品,目前的技术是完全可以实现的,各个环节都有成熟的技术方案支撑,下面拆解一下具体实现路径:
这个产品的每个环节都有成熟的技术工具支撑,不需要从零开发核心能力,只需要将 “语音转文字→大模型提取→RPA / 浏览器自动化填表单” 这三个模块做对接整合即可。
要不要我帮你整理一份具体的技术对接流程清单?
你先帮我整理一份技术这些清单,然后我们在这个公司的技术站是使用 spring boot 加 long term FOG 去完成大模型的对接。然后此外呢,你能否依据第一张图片这个表单信息帮我们生成一个 demo?这个 Demo 就是,嗯,拿到转化成把一段自然语言,典型的自然语言的事例转化成表单上的内容。符合表单内容的 Jason,然后再调,再能够调用浏览器的 agent 将这段 json 自动完成这个表单的图。1。这个表单的填写做这么一个 demo 出来
语音转文字(ASR)→ 文本预处理 → 大模型结构化提取(Spring Boot + LangChain)→ JSON 数据持久化 / 传输 → 浏览器 Agent 自动填表单
| 技术模块 | 可选方案 | 集成要点 | 适配医疗场景说明 |
|---|---|---|---|
| 音频采集 | 1. 普通麦克风 / 医疗专用录音外设(USB / 蓝牙)2. WebRTC(前端实时采集音频流)3. 外设本地录音后上传音频文件(wav/mp3 格式) | 1. 前端:使用 MediaRecorder API 采集音频,转成 blob 格式2. 后端:Spring Boot 提供文件上传接口(MultipartFile 接收) | 医疗外设可直接对接,支持无菌录音设备适配 |
| ASR 服务集成 | 1. 科大讯飞医疗 ASR(推荐,专业术语准确率高)2. 百度智能云 ASR3. 阿里云语音识别 | 1. Spring Boot 引入对应 SDK 依赖(Maven/Gradle)2. 配置 API Key/Secret,封装 ASR 工具类3. 支持「实时流转写」和「文件转写」两种模式 | 科大讯飞 / 百度有医疗术语词库,可自定义上传医院专属术语 |
| 文本后处理 | 1. 去除无效语气词(嗯 / 啊 / 这个)2. 修正识别错别字(医疗术语纠错)3. 区分医生 / 患者话术(便于大模型精准提取) | 1. 正则表达式预处理文本2. 可选:调用医疗术语纠错 API(腾讯觅影) | 提升后续大模型提取信息的准确性 |
| 技术模块 | 可选方案 | 集成要点 | 适配医疗场景说明 |
|---|---|---|---|
| 核心框架 | 1. Spring Boot(2.7.x/3.x 稳定版)2. LangChain4j(Java 版 LangChain,适配 Spring Boot) | 1. Maven 引入 LangChain4j 依赖2. 配置 Spring Boot 配置文件(application.yml)3. 封装大模型调用服务(Service 层) | 纯 Java 技术栈,符合公司现有技术站 |
| 大模型对接 | 1. 国产模型:通义千问(医疗版)、文心一言(医疗微调)2. 海外模型:GPT-4o、Claude 33. 本地部署模型:Llama 3 医疗微调版 | 1. LangChain4j 提供对应模型的封装类2. 配置模型 API 地址、API Key、请求超时时间3. 支持批量请求和流式返回 | 优先选择医疗微调模型,提升主诉 / 现病史等信息提取准确率 |
| 提示词工程 | 1. 结构化提示词模板(固定字段 + JSON 格式要求)2. 少样本示例(给模型提供 1-2 个示例参考) | 1. 在 Spring Boot 中编写静态提示词模板(可配置化,便于修改)2. 拼接「提示词 + ASR 转写文本」作为大模型输入 | 示例提示词需包含医疗专属字段(主诉、现病史、既往史等) |
| JSON 格式输出 | 1. LangChain4j 结构化输出(绑定 Java 实体类)2. 大模型强制输出 JSON 格式,后端做格式校验 | 1. 定义医疗问诊实体类(PatientMedicalInfo.java)2. 使用 LangChain4j 的 @JsonOutput 或 PojoOutputParser 自动将模型输出转为 Java 对象(再序列化为 JSON)3. 增加 JSON 格式校验(fastjson/jackson),异常时重试 |
保证输出的 JSON 可直接被后续填表单环节读取 |
| 技术模块 | 可选方案 | 集成要点 | 适配表单场景说明 |
|---|---|---|---|
| 浏览器自动化核心 | 1. Playwright(推荐,支持多浏览器,稳定性优于 Selenium)2. Selenium(传统方案,生态成熟) | 1. 引入对应 Java 依赖(Playwright-java/Selenium-java)2. 配置浏览器驱动(Playwright 可自动下载驱动)3. 编写元素定位逻辑(XPath/CSS 选择器) | 支持输入框、下拉菜单、单选框、多选框等所有表单元素 |
| JSON 数据读取 | 1. Spring Boot 后端接口返回 JSON 数据(HTTP 请求获取)2. 本地 JSON 文件读取(测试环境快速验证) | 1. 使用 RestTemplate/OkHttp 调用后端接口2. 使用 jackson/fastjson 解析 JSON 数据到 Java 对象 | 适配生产环境(接口获取)和测试环境(本地文件) |
| 表单自动填写 | 1. 输入框:直接赋值输入2. 单选框:根据 JSON 值定位对应选项并点击3. 下拉菜单:根据 JSON 值选择对应选项(select 标签)4. 多选框:遍历 JSON 数组,批量勾选对应选项 | 1. 封装表单操作工具类(FormFiller.java)2. 增加元素等待逻辑(显式等待,避免页面未加载完成导致定位失败) | 完全覆盖图 1 表单的所有组件类型 |
| 备选方案(无代码 / 低代码) | 1. RPA 工具:影刀 RPA、UiPath(可视化配置)2. 浏览器插件:Tampermonkey(油猴脚本) | 1. RPA:可视化配置「读取 JSON→定位表单→填写」流程2. 油猴脚本:前端 JS 读取 JSON,直接操作 DOM 元素 | 适合非技术人员维护,或快速落地测试 |
java
运行
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.List;
// 对应图1表单字段,按需扩展(匹配主诉、现病史等核心字段+表单组件)
@Data
public class PatientMedicalInfo {
// 患者基本信息(表单输入框)
private String patientName; // 患者姓名
private Integer patientAge; // 患者年龄
private String patientGender; // 患者性别(单选框:男/女/其他)
private String patientPhone; // 联系电话
// 医疗核心信息(表单输入框/文本域)
private String chiefComplaint; // 主诉
private String presentIllness; // 现病史
private String pastHistory; // 既往史
private String allergyHistory; // 过敏史
// 症状选项(多选框:对应图1的多选组件)
private List<String> symptoms; // 症状列表(如:头痛、咳嗽、发烧)
// 就诊类型(下拉菜单:对应图1的下拉组件)
private String visitType; // 门诊/急诊/住院
}
xml
<!-- Spring Boot核心 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- LangChain4j 核心 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-core</artifactId>
<version>0.32.0</version>
</dependency>
<!-- LangChain4j 通义千问集成(可替换为GPT等) -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-dashscope</artifactId>
<version>0.32.0</version>
</dependency>
<!-- JSON解析 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
yaml
spring:
application:
name: medical-form-demo
# 通义千问配置(替换为你的API Key)
dashscope:
api-key: your-dashscope-api-key
model: qwen-plus # 医疗场景可替换为 qwen-medical(需申请权限)
java
运行
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.dashscope.DashScopeChatModel;
import dev.langchain4j.model.output.Response;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
public class MedicalInfoExtractService {
@Value("${dashscope.api-key}")
private String dashScopeApiKey;
@Value("${dashscope.model}")
private String dashScopeModel;
// 构建通义千问模型
private ChatLanguageModel buildChatModel() {
return DashScopeChatModel.builder()
.apiKey(dashScopeApiKey)
.modelName(dashScopeModel)
.temperature(0.1) // 低温度,保证输出稳定性
.maxTokens(2048)
.build();
}
// 从自然语言对话中提取医疗信息,返回JSON字符串
public String extractMedicalInfoToJson(String dialogueText) {
ChatLanguageModel model = buildChatModel();
// 精准提示词(强制输出JSON,匹配PatientMedicalInfo实体)
String prompt = """
请你作为医疗信息提取助手,从以下医生与患者的对话文本中,提取指定医疗信息,并严格按照以下JSON格式输出,不要添加任何额外说明文字:
JSON格式要求:
{
"patientName": "字符串,患者姓名",
"patientAge": 整数,患者年龄,
"patientGender": "字符串,男/女/其他",
"patientPhone": "字符串,联系电话",
"chiefComplaint": "字符串,患者主诉(核心不适症状+持续时间)",
"presentIllness": "字符串,现病史(发病过程、症状变化、诊疗经过等)",
"pastHistory": "字符串,既往史(既往疾病、手术史、输血史等)",
"allergyHistory": "字符串,过敏史(药物/食物过敏情况)",
"symptoms": ["字符串1", "字符串2"],症状列表(多选,如头痛、咳嗽),
"visitType": "字符串,门诊/急诊/住院"
}
医生患者对话文本:
%s
""".formatted(dialogueText);
// 调用大模型
Response<String> response = model.generate(prompt);
return response.content();
}
}
java
运行
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MedicalInfoController {
@Autowired
private MedicalInfoExtractService medicalInfoExtractService;
@Autowired
private ObjectMapper objectMapper;
// 示例接口:传入对话文本,返回医疗信息JSON
@GetMapping("/extract/medical/json")
public Object extractMedicalJson(@RequestParam String dialogue) {
try {
String jsonStr = medicalInfoExtractService.extractMedicalInfoToJson(dialogue);
// 解析为对象,返回标准JSON
return objectMapper.readValue(jsonStr, Object.class);
} catch (Exception e) {
e.printStackTrace();
return "提取失败:" + e.getMessage();
}
}
}
plaintext
医生:您好,请先报一下您的姓名、年龄和联系电话。
患者:我叫张三,今年35岁,电话是13800138000,男性。
医生:您今天来是因为什么不舒服呀?
患者:我咳嗽、发烧已经3天了,还伴有咽痛,这几天一直没好转。
医生:您详细说一下发病过程。
患者:3天前受凉后开始发烧,体温最高38.9℃,随后出现干咳,第二天开始有少量白痰,咽痛明显,吞咽时加重,昨天在家吃了布洛芬,体温暂时下降,但反复升高,没有去医院看过。
医生:您以前有没有什么基础疾病?
患者:我有高血压病史5年了,一直在吃硝苯地平,没有手术史,对青霉素过敏。
医生:好的,您这次是来门诊就诊对吧?
患者:是的,门诊。
访问 http://localhost:8080/extract/medical/json?dialogue=上述对话文本,将返回如下标准 JSON(匹配表单字段):
json
{
"patientName": "张三",
"patientAge": 35,
"patientGender": "男",
"patientPhone": "13800138000",
"chiefComplaint": "咳嗽、发烧伴咽痛3天,症状无好转",
"presentIllness": "3天前受凉后出现发烧(最高38.9℃),随后干咳,次日出现少量白痰,咽痛明显、吞咽时加重;自行服用布洛芬后体温暂时下降但反复升高,未到医院诊疗",
"pastHistory": "高血压病史5年,长期服用硝苯地平,无手术史",
"allergyHistory": "对青霉素过敏",
"symptoms": ["咳嗽", "发烧", "咽痛"],
"visitType": "门诊"
}
xml
<!-- Playwright Java -->
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>1.40.0</version>
</dependency>
html
预览
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>医疗问诊表单(图1模拟)</title>
<style>
.form-item {
margin: 15px 0;
}
label {
display: inline-block;
width: 120px;
}
input[type="text"], input[type="tel"], select {
width: 200px;
padding: 5px;
}
textarea {
width: 400px;
height: 80px;
}
.radio-item, .checkbox-item {
display: inline-block;
margin-right: 15px;
}
</style>
</head>
<body>
<h3>医疗问诊信息登记表(图1模拟)</h3>
<form id="medicalForm">
<!-- 基本信息 -->
<div class="form-item">
<label for="patientName">患者姓名:</label>
<input type="text" id="patientName" name="patientName">
</div>
<div class="form-item">
<label for="patientAge">患者年龄:</label>
<input type="text" id="patientAge" name="patientAge">
</div>
<div class="form-item">
<label>患者性别:</label>
<div class="radio-item">
<input type="radio" id="male" name="gender" value="男">
<label for="male">男</label>
</div>
<div class="radio-item">
<input type="radio" id="female" name="gender" value="女">
<label for="female">女</label>
</div>
<div class="radio-item">
<input type="radio" id="other" name="gender" value="其他">
<label for="other">其他</label>
</div>
</div>
<div class="form-item">
<label for="patientPhone">联系电话:</label>
<input type="tel" id="patientPhone" name="patientPhone">
</div>
<!-- 医疗核心信息 -->
<div class="form-item">
<label for="chiefComplaint">主诉:</label>
<textarea id="chiefComplaint" name="chiefComplaint"></textarea>
</div>
<div class="form-item">
<label for="presentIllness">现病史:</label>
<textarea id="presentIllness" name="presentIllness"></textarea>
</div>
<div class="form-item">
<label for="pastHistory">既往史:</label>
<textarea id="pastHistory" name="pastHistory"></textarea>
</div>
<div class="form-item">
<label for="allergyHistory">过敏史:</label>
<textarea id="allergyHistory" name="allergyHistory"></textarea>
</div>
<!-- 多选框:症状 -->
<div class="form-item">
<label>伴随症状:</label>
<div class="checkbox-item">
<input type="checkbox" id="symptom1" name="symptoms" value="头痛">
<label for="symptom1">头痛</label>
</div>
<div class="checkbox-item">
<input type="checkbox" id="symptom2" name="symptoms" value="咳嗽">
<label for="symptom2">咳嗽</label>
</div>
<div class="checkbox-item">
<input type="checkbox" id="symptom3" name="symptoms" value="发烧">
<label for="symptom3">发烧</label>
</div>
<div class="checkbox-item">
<input type="checkbox" id="symptom4" name="symptoms" value="咽痛">
<label for="symptom4">咽痛</label>
</div>
<div class="checkbox-item">
<input type="checkbox" id="symptom5" name="symptoms" value="乏力">
<label for="symptom5">乏力</label>
</div>
</div>
<!-- 下拉菜单:就诊类型 -->
<div class="form-item">
<label for="visitType">就诊类型:</label>
<select id="visitType" name="visitType">
<option value="">请选择</option>
<option value="门诊">门诊</option>
<option value="急诊">急诊</option>
<option value="住院">住院</option>
</select>
</div>
<div class="form-item">
<button type="button" id="submitBtn">提交</button>
</div>
</form>
</body>
</html>
java
运行
import com.microsoft.playwright.Browser;
import com.microsoft.playwright.BrowserType;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.Playwright;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.util.List;
import java.util.Map;
public class FormAutoFiller {
// 读取JSON文件(测试用,生产可调用Spring Boot接口)
private static Map<String, Object> readMedicalJson(String jsonPath) throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(new File(jsonPath), Map.class);
}
// 自动填写表单核心逻辑
public static void autoFillMedicalForm(String jsonPath, String formHtmlPath) {
try (Playwright playwright = Playwright.create()) {
// 启动Chrome浏览器(无头模式可改为 headless=true)
Browser browser = playwright.chromium().launch(new BrowserType.LaunchOptions()
.setHeadless(false)
.setSlowMo(500)); // 慢动作,便于观察填写过程
// 打开页面
Page page = browser.newPage();
page.navigate("file:///" + new File(formHtmlPath).getAbsolutePath());
// 读取JSON数据
Map<String, Object> medicalData = readMedicalJson(jsonPath);
// 1. 填写输入框(基本信息)
page.locator("#patientName").fill((String) medicalData.get("patientName"));
page.locator("#patientAge").fill(medicalData.get("patientAge").toString());
page.locator("#patientPhone").fill((String) medicalData.get("patientPhone"));
// 2. 选择单选框(性别)
String gender = (String) medicalData.get("patientGender");
if ("男".equals(gender)) {
page.locator("#male").check();
} else if ("女".equals(gender)) {
page.locator("#female").check();
} else {
page.locator("#other").check();
}
// 3. 填写文本域(医疗核心信息)
page.locator("#chiefComplaint").fill((String) medicalData.get("chiefComplaint"));
page.locator("#presentIllness").fill((String) medicalData.get("presentIllness"));
page.locator("#pastHistory").fill((String) medicalData.get("pastHistory"));
page.locator("#allergyHistory").fill((String) medicalData.get("allergyHistory"));
// 4. 勾选多选框(症状)
List<String> symptoms = (List<String>) medicalData.get("symptoms");
for (String symptom : symptoms) {
switch (symptom) {
case "头痛" -> page.locator("#symptom1").check();
case "咳嗽" -> page.locator("#symptom2").check();
case "发烧" -> page.locator("#symptom3").check();
case "咽痛" -> page.locator("#symptom4").check();
case "乏力" -> page.locator("#symptom5").check();
}
}
// 5. 选择下拉菜单(就诊类型)
String visitType = (String) medicalData.get("visitType");
page.locator("#visitType").selectOption(visitType);
System.out.println("表单自动填写完成!");
// 停留10秒,便于查看结果
Thread.sleep(10000);
// 关闭浏览器
browser.close();
} catch (Exception e) {
e.printStackTrace();
}
}
// 主方法测试
public static void main(String[] args) {
// 替换为你的JSON文件路径和表单HTML路径
String jsonPath = "D:\\medical-info.json";
String formHtmlPath = "D:\\test-form.html";
autoFillMedicalForm(jsonPath, formHtmlPath);
}
}
your-dashscope-api-key)medical-info.json文件,放入步骤 2 生成的 JSON 数据test-form.html文件,保存上述模拟表单代码medical-info.jsonFormAutoFiller.java的 main 方法,即可看到浏览器自动打开并填写表单headless=true,在服务器后台运行