跳转到内容

语音识别

2 篇包含标签 "语音识别" 的文章

打字不如说话,说话不如截图——AI 代码助手的多模态输入实践

打字不如说话,说话不如截图——AI 代码助手的多模态输入实践

Section titled “打字不如说话,说话不如截图——AI 代码助手的多模态输入实践”

其实写代码这事儿,打字再快也有个上限。有时候说一句话的事,非得敲半天键盘;有时候一张图就能说明白,却得用一堆文字去描述。本文聊聊我们在做 HagiCode 时遇到的那些事儿——语音识别也好,图片上传也罢,反正就是想让 AI 代码助手变得好用一点,罢了。

在做 HagiCode 的时候,我们发现了一个问题——或者说,用户们用得多了,自然就显现出来的问题:光靠打字,有时候挺累的。

你想啊,用户和 Agent 交互,这可是核心场景。可是每次都得坐在键盘上噼里啪啦地敲,怎么说呢,效率确实不太高:

  1. 打字太慢了:有些复杂的问题,什么报错啊、界面上的事儿啊,打字说出来得耗上半分钟,嘴上可能十秒就说完了。这时间差,挺让人难受的。

  2. 图片更直接:有时候界面报错了,或者想对比一下设计稿,又或者想展示代码结构……”一图胜千言”这话虽老,可理儿不假。让 AI 直接”看”到问题,比描述半天要清楚得多。

  3. 交互就该自然点:现在的 AI 助手,应该支持文字、语音、图片这些方式吧?用户想用什么就用什么,这才叫自然,不是吗?

所以啊,我们就想,不如给 HagiCode 加上语音识别和图片上传的功能,让 Agent 操作变得方便些。毕竟,能让用户少敲几个字,也是好的。

本文分享的这些方案,来自我们在 HagiCode 项目中的实践——或者说,是在不断踩坑中摸索出来的经验。

HagiCode 是个开源的 AI 代码助手项目,想法很简单:用 AI 技术提升开发效率。做着做着就发现,用户对多模态输入的需求其实挺强烈的——有时候说一句话比打一堆字快,有时候一张截图比描述半天清楚。

这些需求推着我们往前走,最后也就有了语音识别和图片上传这些功能。用户可以用最自然的方式和 AI 交互,这感觉,挺好的。

做语音识别功能的时候,我们遇到了一个挺棘手的问题:浏览器的 WebSocket API 不支持自定义 HTTP header

而我们选的语音识别服务,是字节跳动的豆包语音识别 API。这个 API 偏偏要求通过 HTTP header 传递认证信息,什么 accessTokensecretKey 之类的。这下好了,技术矛盾来了:

// 浏览器 WebSocket API 不支持以下方式
const ws = new WebSocket('wss://api.com/ws', {
headers: {
'Authorization': 'Bearer token' // 不支持
}
});

摆在我们面前的方案,大概有两个:

  1. URL 查询参数方案:把认证信息放在 URL 里

    • 优点是,实现起来简单
    • 缺点是,凭证暴露在前端,安全性差;而且有些 API 强制要求 header 验证
  2. 后端代理方案:在后端实现 WebSocket 代理

    • 优点是,凭证安全存储在后端;完全兼容 API 要求
    • 缺点是,实现起来稍微复杂一点

最后我们还是选了后端代理方案。毕竟啊,安全性这东西,是不能妥协的底线——这一点,谁也别想糊弄过去。

图片上传功能嘛,我们的需求其实也挺简单的:

  1. 多种上传方式:点击选文件、拖拽上传、剪贴板粘贴,总得有吧?
  2. 文件验证:类型限制(PNG、JPG、WebP、GIF)、大小限制(5-10MB),这些是基本操作
  3. 用户体验:上传进度、预览、错误提示,总得让人知道发生了什么
  4. 安全性:服务端验证、防止恶意文件上传,这可是大事

我们设计了一个三层架构的语音识别方案,怎么说呢,算是找到了一条路:

Browser WebSocket
|
| ws://backend/api/voice/ws
| (binary audio)
v
Backend Proxy
|
| wss://openspeech.bytedance.com/ (with auth header)
v
Doubao API

核心组件实现

  1. 前端 AudioWorklet 处理器
class AudioProcessorWorklet extends AudioWorkletProcessor {
process(inputs, outputs, parameters) {
const input = inputs[0]?.[0];
if (!input) return true;
// 重采样到 16kHz(豆包 API 要求)
const samples = this.resampleAudio(input, 48000, 16000);
// 累积样本到 500ms 块
this.accumulatedSamples.push(...samples);
if (this.accumulatedSamples.length >= 8000) {
// 转换为 16-bit PCM 并发送
const pcm = this.floatToPcm16(this.accumulatedSamples);
this.port.postMessage({ type: 'audioData', data: pcm.buffer }, [pcm.buffer]);
this.accumulatedSamples = [];
}
return true;
}
}
  1. 后端 WebSocket 处理器(C#):
[HttpGet("ws")]
public async Task GetWebSocket()
{
if (HttpContext.WebSockets.IsWebSocketRequest)
{
await _webSocketHandler.HandleAsync(HttpContext);
}
}
  1. 前端 VoiceTextArea 组件
export const VoiceTextArea = forwardRef<HTMLTextAreaElement, VoiceTextAreaProps>(
({ value, onChange, onTextRecognized, maxDuration }, ref) => {
const { isRecording, interimText, volume, duration, startRecording, stopRecording } =
useVoiceRecording({ onTextRecognized, maxDuration });
return (
<div className="flex gap-2">
{/* 语音按钮 */}
<button onClick={handleButtonClick}>
{isRecording ? <VolumeWaveform volume={volume} /> : <Mic />}
</button>
{/* 文本输入框 */}
<textarea value={displayValue} onChange={handleChange} />
</div>
);
}
);

我们做了一个功能完整的图片上传组件,三种上传方式都支持,怎么说呢,算是把用户常用的场景都覆盖到了。

核心特性

  1. 三种上传方式
// 点击上传
const handleClick = () => fileInputRef.current?.click();
// 拖拽上传
const handleDrop = (e: React.DragEvent) => {
const file = e.dataTransfer.files?.[0];
if (file) uploadFile(file);
};
// 剪贴板粘贴
const handlePaste = (e: ClipboardEvent) => {
for (const item of Array.from(e.clipboardData?.items || [])) {
if (item.type.startsWith('image/')) {
const file = item.getAsFile();
if (file) uploadFile(file);
}
}
};
  1. 前端验证
const validateFile = (file: File): { valid: boolean; error?: string } => {
if (!acceptedTypes.includes(file.type)) {
return { valid: false, error: 'Only PNG, JPG, JPEG, WebP, and GIF images are allowed' };
}
if (file.size > maxSize) {
return { valid: false, error: `Maximum file size is ${(maxSize / 1024 / 1024).toFixed(1)}MB` };
}
return { valid: true };
};
  1. 后端上传处理(TypeScript):
export const Route = createFileRoute('/api/upload')({
server: {
handlers: {
POST: async ({ request }) => {
const formData = await request.formData();
const file = formData.get('file') as File;
// 验证
const validation = validateFile(file);
if (!validation.isValid) {
return Response.json({ error: validation.error }, { status: 400 });
}
// 保存文件
const uuid = uuidv4();
const filePath = join(uploadDir, `${uuid}${extension}`);
await writeFile(filePath, buffer);
return Response.json({ url: `/uploaded/${today}/${uuid}${extension}` });
}
}
}
});
  1. 配置语音识别服务

    • 进入语音识别设置页面
    • 配置豆包语音的 AppIdAccessToken
    • (可选)配置热词以提升专业术语识别准确率
  2. 在输入框中使用

    • 点击输入框左侧的麦克风图标
    • 看到波形动画后开始说话
    • 再次点击图标停止录音
    • 识别结果会自动插入到光标位置
  3. 热词配置示例

TypeScript
React
useState
useEffect
  1. 上传方式

    • 点击上传按钮选择文件
    • 直接拖拽图片到上传区域
    • 使用 Ctrl+V 粘贴剪贴板中的截图
  2. 支持的格式:PNG、JPG、JPEG、WebP、GIF

  3. 大小限制:默认 5MB(可配置)

  1. 语音识别

    • 需要麦克风权限
    • 建议在安静环境下使用
    • 支持的最大录音时长为 300 秒(可配置)
  2. 图片上传

    • 仅支持常见图片格式
    • 注意文件大小限制
    • 上传后的图片会自动生成预览 URL
  3. 安全考虑

    • 语音识别凭证存储在后端
    • 图片上传有严格的服务端验证
    • 生产环境建议使用 HTTPS/WSS

加上语音识别和图片上传之后,HagiCode 的用户体验确实提升了不少。用户现在可以用更自然的方式和 AI 交互——说话代替打字,截图代替描述。这种感觉,怎么说呢,就像是终于找到了一种更舒服的沟通方式。

做这个功能的时候,我们遇到了浏览器 WebSocket 不支持自定义 header 的问题,最后还是通过后端代理方案搞定了。这个方案不仅保证了安全性,也为后续集成其他需要认证的 WebSocket 服务打下了基础——也算是个意外收获吧。

图片上传组件也是,用了多种上传方式,让用户可以根据场景选择最方便的那一个。点击也好,拖拽也罢,或者直接粘贴,都能快速完成上传。条条大路通罗马,只是有的路好走一点,有的路稍微曲折一点罢了。

“打字不如说话,说话不如截图”,这话放在这里,倒也贴切。如果你也在做类似的 AI 助手产品,希望这些经验能对你有所帮助,哪怕只是一点点。


如果本文对你有帮助:

感谢您的阅读,如果您觉得本文有用,欢迎点赞、收藏和分享支持。 本内容采用人工智能辅助协作,最终内容由作者审核并确认。

豆包语音识别热词功能实现指南

豆包语音识别热词功能实现指南

Section titled “豆包语音识别热词功能实现指南”

本文将详细介绍如何在 HagiCode 项目中实现豆包语音识别的热词支持功能,通过自定义热词和平台热词表两种方式,显著提升特定领域词汇的识别准确率。

语音识别技术发展这么多年了,其实有个问题一直困扰着开发者们。通用语音识别模型虽然能覆盖日常用语,可对于专业术语、产品名称、人名这些词,识别准确率总差那么点意思。想想看,医疗领域的语音助手要准确识别”高血压”、“糖尿病”、“冠心病”;法律系统要精准捕捉”案由”、“答辩”、“举证责任”——这些场景下,通用模型的表现怎么说呢,也算尽力了。

在 HagiCode 项目中,我们也遇到了同样的挑战。作为一个多功能的 AI 代码助手,HagiCode 需要处理各种技术术语的语音识别场景。然而,豆包语音识别 API 在默认情况下,并不能完全满足我们对专业术语准确率的那些要求。其实也不是豆包不够好,只是每个领域都有自己的一套术语体系。经过一番调研和技术探索,我们发现豆包语音识别 API 实际上提供了热词支持功能,只要简单配置一下,就能显著提升特定词汇的识别准确率。这倒是有点像,你告诉它你要注意什么词,它就会更用心去听那些词。

本文要分享的,就是在 HagiCode 项目中实现豆包语音识别热词功能的完整方案。两种模式,自定义热词和平台热词表,都可以用,也都能组合用。通过这套方案,开发者可以根据业务场景灵活配置热词,让语音识别系统”认识”那些专业、罕见但又至关重要的词汇。

本文分享的方案来自我们在 HagiCode 项目中的实践经验。HagiCode 是一个开源的 AI 代码助手项目,技术栈还算现代化,旨在为开发者提供智能化的编程辅助体验。作为一个多语言、多平台的复杂项目,HagiCode 需要处理各种技术术语的语音识别场景,这也推动了我们对热词功能的研究和实现。

如果你对 HagiCode 的技术实现感兴趣,可以访问 GitHub 仓库 了解更多信息,也可以查看我们的 官方文档 了解完整的安装和使用指南。

豆包语音识别 API 为我们提供了两种热词配置方式,每种方式都有其独特的应用场景和优势。

自定义热词模式允许我们通过 corpus.context 字段直接传递热词文本。这种方式非常适合需要快速配置少量热词的场景,比如临时需要识别某个产品名称或者人名。在 HagiCode 的实现中,我们将用户输入的多行热词文本解析为字符串列表,然后按照豆包 API 的要求格式化为 context_data 数组。怎么说呢,这种方式很直接,就像告诉对方”你要注意这些词”,然后它就去注意了。

平台热词表模式则通过 corpus.boosting_table_id 字段引用豆包自学习平台预配置的热词表。这种方式适合需要管理大量热词的场景,我们可以在豆包自学习平台上创建和维护热词表,然后通过 ID 进行引用。对于 HagiCode 这类需要持续更新和维护专业术语的项目来说,这种模式提供了更好的可管理性。毕竟,热词多了之后,找个地方统一管理,总比每次都要手动输入要好。

有意思的是,这两种模式还可以组合使用。豆包 API 支持在同一个请求中同时包含自定义热词和平台热词表 ID,通过 combine_mode 参数控制组合策略。这种灵活性使得 HagiCode 能够应对各种复杂的专业术语识别需求。这也倒是挺好,有时候多种方式组合一下,效果可能更好。

在 HagiCode 的前端实现中,我们定义了一套完整的热词配置类型和验证逻辑。首先是类型定义部分:

export interface HotwordConfig {
contextText: string; // 多行热词文本
boostingTableId: string; // 豆包平台热词表 ID
combineMode: boolean; // 是否组合使用
}

这个简单的接口包含了热词功能的所有配置项。其中 contextText 是用户最直观感受到的部分——我们允许用户每行输入一个热词短语,这种方式非常符合直觉。毕竟,让用户一行一个词,总比让用户理解复杂的配置规则要好。

接下来是验证函数的实现。考虑到豆包 API 的限制,我们制定了严格的验证规则:热词文本最多 100 行,每行最多 50 个字符,总共最多 5000 个字符;boosting_table_id 最多 200 个字符,只允许字母、数字、下划线和连字符。这些限制不是我们凭空想象的,而是基于豆包官方文档的实际要求。毕竟,API 的限制就是 API 的限制,我们也没办法,只能遵守。

export function validateContextText(contextText: string): HotwordValidationResult {
if (!contextText || contextText.trim().length === 0) {
return { isValid: true, errors: [] };
}
const lines = contextText.split('\n').filter(line => line.trim().length > 0);
const errors: string[] = [];
if (lines.length > 100) {
errors.push(`热词行数不能超过 100 行,当前为 ${lines.length}`);
}
const totalChars = contextText.length;
if (totalChars > 5000) {
errors.push(`热词总字符数不能超过 5000,当前为 ${totalChars}`);
}
for (let i = 0; i < lines.length; i++) {
if (lines[i].length > 50) {
errors.push(`${i + 1} 行热词超过 50 个字符限制`);
}
}
return { isValid: errors.length === 0, errors };
}
export function validateBoostingTableId(boostingTableId: string): HotwordValidationResult {
if (!boostingTableId || boostingTableId.trim().length === 0) {
return { isValid: true, errors: [] };
}
const errors: string[] = [];
if (boostingTableId.length > 200) {
errors.push(`boosting_table_id 不能超过 200 个字符,当前为 ${boostingTableId.length}`);
}
if (!/^[a-zA-Z0-9_-]+$/.test(boostingTableId)) {
errors.push('boosting_table_id 只能包含字母、数字、下划线和连字符');
}
return { isValid: errors.length === 0, errors };
}

这些验证函数在用户配置热词时就会立即执行,确保问题在最早阶段被发现。对于用户体验来说,这种即时反馈是非常重要的。毕竟,用户输入的时候就知道哪里错了,总比提交后才发现要好。

在 HagiCode 的前端实现中,我们选择使用浏览器的 localStorage 来存储热词配置。这个设计决策背后有几点考量:首先,热词配置是非常个性化的设置,不同用户可能有不同的专业领域需求;其次,这种方式简化了后端实现,不需要额外的数据库表和 API 接口;最后,用户在浏览器中配置一次后,后续使用都能自动加载,非常方便。其实说白了,就是省事。

const HOTWORD_STORAGE_KEYS = {
contextText: 'hotword-context-text',
boostingTableId: 'hotword-boosting-table-id',
combineMode: 'hotword-combine-mode',
} as const;
export const DEFAULT_HOTWORD_CONFIG: HotwordConfig = {
contextText: '',
boostingTableId: '',
combineMode: false,
};
// 加载热词配置
export function loadHotwordConfig(): HotwordConfig {
const contextText = localStorage.getItem(HOTWORD_STORAGE_KEYS.contextText) || '';
const boostingTableId = localStorage.getItem(HOTWORD_STORAGE_KEYS.boostingTableId) || '';
const combineMode = localStorage.getItem(HOTWORD_STORAGE_KEYS.combineMode) === 'true';
return { contextText, boostingTableId, combineMode };
}
// 保存热词配置
export function saveHotwordConfig(config: HotwordConfig): void {
localStorage.setItem(HOTWORD_STORAGE_KEYS.contextText, config.contextText);
localStorage.setItem(HOTWORD_STORAGE_KEYS.boostingTableId, config.boostingTableId);
localStorage.setItem(HOTWORD_STORAGE_KEYS.combineMode, String(config.combineMode));
}

这段代码的逻辑非常简单清晰。加载配置时从 localStorage 读取,保存配置时写入 localStorage。我们还提供了默认配置,确保在没有任何配置时系统也能正常工作。毕竟,总得有个默认值吧。

在 HagiCode 的后端实现中,我们需要在 SDK 配置类中添加热词相关的属性。考虑到 C# 的语言特性和使用习惯,我们采用了 List<string> 来存储自定义热词上下文:

public class DoubaoVoiceConfig
{
/// <summary>
/// 应用 ID
/// </summary>
public string AppId { get; set; } = string.Empty;
/// <summary>
/// 访问令牌
/// </summary>
public string AccessToken { get; set; } = string.Empty;
/// <summary>
/// 服务 URL
/// </summary>
public string ServiceUrl { get; set; } = string.Empty;
/// <summary>
/// 自定义热词上下文列表
/// </summary>
public List<string>? HotwordContexts { get; set; }
/// <summary>
/// 豆包平台热词表 ID
/// </summary>
public string? BoostingTableId { get; set; }
}

这个配置类的设计遵循了 HagiCode 一贯的简洁风格。HotwordContexts 是可空的列表类型,BoostingTableId 是可空的字符串,这样在没有任何热词配置时,这些属性不会对请求造成任何影响。毕竟,不用的时候就不应该存在,这才叫干净。

Payload 的构建是整个热词功能的核心。当我们有了热词配置后,需要按照豆包 API 的要求格式化为正确的 JSON 结构。这个过程发生在 SDK 发送请求之前:

private void AddCorpusToRequest(Dictionary<string, object> request)
{
var corpus = new Dictionary<string, object>();
// 添加自定义热词
if (Config.HotwordContexts != null && Config.HotwordContexts.Count > 0)
{
corpus["context"] = new Dictionary<string, object>
{
["context_type"] = "dialog_ctx",
["context_data"] = Config.HotwordContexts
.Select(text => new Dictionary<string, object> { ["text"] = text })
.ToList()
};
}
// 添加平台热词表 ID
if (!string.IsNullOrEmpty(Config.BoostingTableId))
{
corpus["boosting_table_id"] = Config.BoostingTableId;
}
// 只有当 corpus 不为空时才添加到请求中
if (corpus.Count > 0)
{
request["corpus"] = corpus;
}
}

这段代码展示了如何根据配置动态构建 corpus 字段。关键点在于:只有当确实存在热词配置时,我们才会添加 corpus 字段。这种设计确保了向后兼容性——没有配置热词时,请求的结构与之前完全一致。毕竟,兼容性很重要,不能因为加个功能就把之前的逻辑搞乱了。

在前端和后端之间,热词参数通过 WebSocket 控制消息进行传递。HagiCode 的设计是:前端在开始录音时从 localStorage 加载热词配置,然后通过 WebSocket 消息发送给后端。

const controlMessage = {
type: 'control',
payload: {
command: 'StartRecognition',
contextText: '高血压\n糖尿病\n冠心病',
boosting_table_id: 'medical_table',
combineMode: false
}
};

这里有一个细节需要注意:前端传递的是多行文本(用换行符分隔),后端需要进行解析。后端的 WebSocket Handler 会解析这些参数并传递给 SDK:

private async Task HandleControlMessageAsync(
string connectionId,
DoubaoSession session,
ControlMessage message)
{
if (message.Payload is SessionControlRequest controlRequest)
{
// 解析热词参数
string? contextText = controlRequest.ContextText;
string? boostingTableId = controlRequest.BoostingTableId;
bool? combineMode = controlRequest.CombineMode;
// 解析多行文本为热词列表
if (!string.IsNullOrEmpty(contextText))
{
var hotwords = contextText
.Split('\n', StringSplitOptions.RemoveEmptyEntries)
.Select(s => s.Trim())
.Where(s => s.Length > 0)
.ToList();
session.HotwordContexts = hotwords;
}
session.BoostingTableId = boostingTableId;
}
}

通过这样的设计,热词配置从前端到后端的传递变得清晰而高效。其实也没什么特别的,就是一层一层传下去而已。

在实际使用中,配置自定义热词非常简单。打开 HagiCode 的语音识别设置页面,找到”热词配置”区域。在”自定义热词文本”输入框中,每行输入一个热词短语。

比如,如果你正在开发一个医疗相关的应用,可以这样配置:

高血压
糖尿病
冠心病
心绞痛
心肌梗死
心力衰竭

保存配置后,每次开始语音识别时,这些热词都会自动传递给豆包 API。实际测试表明,配置热词后,相关专业术语的识别准确率有了明显提升。怎么说呢,效果还是有的,至少比之前好多了。

如果你需要管理大量的热词,或者热词需要频繁更新,那么平台热词表模式更适合你。首先需要在豆包自学习平台上创建热词表,获取生成的 boosting_table_id,然后在 HagiCode 的设置页面中输入这个 ID。

豆包自学习平台提供了热词的批量导入、分类管理等功能,对于需要管理大量专业术语的团队来说非常实用。通过平台管理热词,可以实现热词的集中维护和统一更新。毕竟,热词多了之后,有个地方统一管理,总比每次都要手动输入要好。

在某些复杂场景下,你可能需要同时使用自定义热词和平台热词表。这时只需要在 HagiCode 中同时配置两种热词,并开启”组合模式”开关。

组合模式下,豆包 API 会同时考虑两种热词来源,识别准确率通常比单独使用任意一种更高。不过需要注意的是,组合模式会增加请求的复杂度,建议在实际测试后再决定是否启用。毕竟,复杂度增加了,是不是真的值得,还是得看实际效果。

在 HagiCode 项目中集成热词功能非常简单。以下是一些常用的代码片段:

import {
loadHotwordConfig,
saveHotwordConfig,
validateHotwordConfig,
parseContextText,
getEffectiveHotwordMode,
type HotwordConfig
} from '@/types/hotword';
// 加载并验证配置
const config = loadHotwordConfig();
const validation = validateHotwordConfig(config);
if (!validation.isValid) {
console.error('热词配置验证失败:', validation.errors);
return;
}
// 解析热词文本
const hotwords = parseContextText(config.contextText);
console.log('解析到的热词:', hotwords);
// 获取有效的热词模式
const mode = getEffectiveHotwordMode(config);
console.log('当前热词模式:', mode);

后端的使用同样简洁:

var config = new DoubaoVoiceConfig
{
AppId = "your_app_id",
AccessToken = "your_access_token",
ServiceUrl = "wss://openspeech.bytedance.com/api/v3/sauc/bigmodel_async",
// 配置自定义热词
HotwordContexts = new List<string>
{
"高血压",
"糖尿病",
"冠心病"
},
// 配置平台热词表
BoostingTableId = "medical_table_v1"
};
var client = new DoubaoVoiceClient(config, logger);
await client.ConnectAsync();
await client.SendFullClientRequest();

在实现和使用热词功能时,有几点需要特别注意。

首先是字符限制。豆包 API 对热词有严格的限制,包括行数、每行字符数、总字符数等。如果超出限制,API 会返回错误。在 HagiCode 的前端实现中,我们通过验证函数在用户输入阶段就进行检查,避免将无效配置发送到后端。毕竟,提前发现问题,总比等 API 返回错误要好。

其次是 boosting_table_id 的格式。这个字段只允许字母、数字、下划线和连字符,不允许包含空格或其他特殊字符。在豆包自学习平台上创建热词表时,需要注意命名规范。其实这也难怪,API 对格式的要求总是比较严格的。

第三是向后兼容性。热词参数是完全可选的,不配置热词时,系统的工作方式与之前完全一致。这种设计确保了现有用户不会受到任何影响,也便于逐步迁移和升级。毕竟,不能因为加个功能就把之前的逻辑搞乱了。

最后是错误处理。当热词配置无效时,豆包 API 会返回相应的错误信息。HagiCode 的实现会记录详细的日志,便于开发者排查问题。同时,前端也会在界面上展示验证错误,帮助用户修正配置。错误处理做得好,用户体验自然也就好了。

通过本文的讲解,我们详细介绍了在 HagiCode 项目中实现豆包语音识别热词功能的完整方案。这套方案涵盖了从需求分析、技术选型到代码实现的全部环节,为开发者提供了可参考的实践范例。

核心要点可以归纳为以下几点:第一,豆包 API 支持自定义热词和平台热词表两种模式,可以独立使用也可以组合使用;第二,前端采用 localStorage 存储配置,简单高效;第三,后端通过动态构建 corpus 字段来传递热词参数,保持了良好的向后兼容性;第四,完善的验证逻辑确保了配置的正确性,避免了无效请求。怎么说呢,这套方案也不复杂,就是按照 API 的要求来而已。

热词功能的实现,让 HagiCode 在语音识别领域的能力得到了进一步增强。通过灵活配置业务相关的专业术语,开发者可以让语音识别系统更好地理解特定领域的内容,从而提供更加精准的服务。毕竟,技术最终是要服务业务的,能解决实际问题才是最重要的。

如果你觉得本文对你有帮助,欢迎来 GitHub 给个 Star 支持一下 HagiCode 项目。你的认可,是我们持续分享技术实践的动力。说到底,写文章分享技术,能帮到人,也算是种快乐了。


感谢您的阅读,如果您觉得本文有用,快点击下方点赞按钮👍,让更多的人看到本文。

本内容采用人工智能辅助协作,经本人审核,符合本人观点与立场。