前言
在当今信息爆炸的时代,快速而高效地阅读文档和整理信息变得极其重要。专业人士、学生和学术研究者通常需要阅读大量的资料,而这些文档往往篇幅冗长、内容专业,需要耗费大量时间才能完全理解。特别是面对技术文档、学术论文或行业报告时,即使是领域专家也常常需要反复阅读才能掌握核心内容。
随着 AI
技术的发展,我们是否可以让它帮忙提升阅读效率?当然可以!
我开发了一款 AI 智能阅读助手 ,它是一个 浏览器插件 ,能够阅读网页内容和 PDF
文件,然后通过 AI
进行即时问答,甚至还能和它多轮对话,深入理解内容。
本文将详细介绍 AI
智能阅读助手的 项目概述、技术架构、核心功能实现,以及 如何借助腾讯云 DeepSeek API
让 AI
在阅读场景里发挥最大作用。
准备好了吗?准备一杯你最喜欢的咖啡或茶,随着本文一探究竟吧。
项目概述
功能介绍
AI
智能阅读助手 是一款 浏览器插件,具备以下核心功能:
-
网页内容解析与问答 :提取网页文本,并基于腾讯云
DeepSeek API
进行即时问答。 -
PDF 解析与内容问答 :上传
PDF
文件,AI
解析内容,并提供精准问答。 -
多轮对话与上下文理解 :支持与
AI
进行多轮交互,理解用户意图,实现更自然的阅读体验。 -
历史对话:支持查看历史对话并继续问答。
应用场景
-
学生与研究人员:快速阅读论文,提取关键信息,进行文献问答。
-
职场人士:高效浏览行业报告和领域文章,获取精准数据和分析。
效果演示
阅读当前网页文档
阅读PDF文件
历史对话
技术架构
AI 智能阅读助手 由 前端浏览器插件 + 后端 API
服务 组成:
-
前端(浏览器插件) :负责内容传递、用户交互。核心框架:
WXT
。 -
后端(Go API) :提供智能问答、文本解析等
AI
对话能力。核心技术:Go
、Gin
和MongoDB
以及 腾讯云-知识引擎原子能力。
腾讯云 DeepSeek API 在助手中的应用
腾讯云 DeepSeek API 介绍
腾讯云知识引擎原子能力 提供了 DeepSeek API
接口,我们可以通过腾讯云提供的 SDK
进行调用。同时,腾讯云还额外封装了DeepSeek OpenAI
对话接口,兼容了 OpenAI
的接口规范,这意味着我们可以直接使用 OpenAI
官方提供的 SDK
来调用。仅需要将 base_url
和 api_key
替换成相关配置,不需要对应用做额外修改。
计费说明:
DeepSeek-R1
模型 | 输入:0.004 元 / 千token
| 输出(含思维链):0.016 元 / 千token
DeepSeek-V3
模型 | 输入:0.002 元/ 千token
| 输出:0.008 元 / 千token
腾讯云 DeepSeek API 的作用
腾讯云 DeepSeek API
在 助手 里的主要作用包括:
-
内容总结:快速对文档进行总结,提高阅读效率。
-
即时问答:迅速回答用户的问题。
-
多轮对话:保持上下文,支持深入理解用户意图。
腾讯云大模型知识引擎的实时文档解析 API 的应用
腾讯云大模型知识引擎的实时文档解析 API
支持将图片或PDF
文件转换成Markdown
格式文件,可解析包括表格、公式、图片、标题、段落、页眉、页脚等内容元素,并将内容智能转换成阅读顺序。
实时文档解析 API
在助手里的作用:
PDF
文档解析:将PDF
文档内容转成大模型更易于理解的Markdown
结构化的格式。
核心功能实现
即时问答与多轮交互
前端与后端通信(SSE 实现流式输出)
腾讯云 DeepSeek API
支持流式响应,为了提升对话体验,AI
智能阅读助手在前端采用 Server-Sent Events(SSE) 方式与后端进行通信,以便 AI
的响应能够以流式形式逐步返回,实现打字机的效果,而不是一次性加载整个回答。这样,我们就可以更快地看到 AI
生成的内容,提高交互的流畅性。
核心代码实现如下所示:
发起对话
typescript
const completions = async (message: string) => {
if (!chatId.value) return;
const requestBody = {
prompt: message,
...(props.sessionData?.type === 'webpage' && message === '' && { web_url: props.sessionData.source }),
...(props.sessionData?.type === 'file' && message === '' &&{
media: {
doc_type: props.sessionData.fileType,
file_name: props.sessionData.fileName,
path: props.sessionData.source
}
})
};
try {
const response = await fetch(`${API_BASE}/chats/${chatId.value}/completions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(requestBody)
});
if (!response.ok) throw new Error(`HTTP错误: ${response.status}`);
if (!response.body) throw new Error('无法获取响应流');
const aiMessageId = generateId();
messages.value.push({
id: aiMessageId,
content: '',
reasoningContent: '',
isUser: false,
timestamp: new Date(),
isComplete: false,
showReasoning: false
});
await processSSEStream(response.body.getReader(), aiMessageId);
} catch (error) {
console.error('API请求失败:', error);
showError(DEFAULT_ERROR_MSG);
throw error;
}
};
代码解释:
-
初始验证检查
-
检查对话
ID
是否存在,如果不存在则终止函数执行 -
确保只在有效会话中进行对话
API
的调用
-
-
构建请求体
-
创建包含用户消息的请求对象
-
根据会话类型动态添加不同参数:
-
网页分析:当消息为空时添加网页
URL
-
文件分析:当消息为空时添加文件类型、名称和路径
-
-
-
发起API请求
-
使用
fetch
向后端API
发送POST
请求 -
设置适当的请求头和序列化的请求体
-
-
错误处理
-
检查
HTTP
响应状态 -
验证响应是否包含可读取的数据流
-
-
创建新的消息体
-
生成唯一的消息
ID
-
向消息列表添加初始为空的
AI
回复 -
设置初始状态标记(未完成、不显示推理过程等)
-
-
处理
SSE
数据流-
调用
processSSEStream
函数处理响应流 -
传入流读取器和
AI
消息ID
-
实时更新
UI
上的AI
回复内容
-
-
异常捕获
-
捕获并记录任何
API
请求过程中的错误 -
向用户显示友好的错误提示
-
将错误向上传播以便进一步处理
-
处理流式响应
typescript
const processSSEStream = async (reader: ReadableStreamDefaultReader, aiMessageId: string) => {
currentReader.value = reader;
isGenerating.value = true;
const decoder = new TextDecoder();
let buffer = '';
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
while (true) {
const eventEndIndex = buffer.indexOf('\n\n');
if (eventEndIndex === -1) break;
const eventData = buffer.substring(0, eventEndIndex);
buffer = buffer.substring(eventEndIndex + 2);
const lines = eventData.split('\n').filter(line => line.trim());
let eventType = 'message';
let jsonData = '';
for (const line of lines) {
if (line.startsWith('event:')) {
eventType = line.substring(6).trim();
} else if (line.startsWith('data:')) {
jsonData += line.substring(5).trim();
}
}
if (eventType === 'close') {
try {
const closeData = JSON.parse(jsonData);
if (closeData.error) showError(`服务端关闭连接: ${closeData.error}`);
} catch (e) {
console.error('关闭事件解析失败:', e);
}
return;
}
if (jsonData) {
try {
const data = JSON.parse(jsonData) as ChatMessage;
const targetIndex = messages.value.findIndex(m => m.id === aiMessageId);
if (targetIndex === -1) return;
const original = messages.value[targetIndex];
// 处理reasoningContent和content的打字机效果
messages.value[targetIndex] = {
...original,
content: data.content ? original.content + data.content : original.content,
reasoningContent: data.reasoning_content ?
(original.reasoningContent || '') + data.reasoning_content :
original.reasoningContent,
timestamp: new Date((data.created_at || Date.now()) * 1000),
showReasoning: original.showReasoning || !!data.reasoning_content
};
scrollToBottom();
} catch (e) {
console.error('SSE数据解析失败:', e, '原始数据:', jsonData);
}
}
}
}
} catch (error) {
console.error('读取流数据失败:', error);
showError('连接异常中断,请重试');
} finally {
reader.releaseLock();
currentReader.value = null;
isLoading.value = false;
isGenerating.value = false;
const targetIndex = messages.value.findIndex(m => m.id === aiMessageId);
if (targetIndex > -1) messages.value[targetIndex].isComplete = true;
}
};
代码解释:
-
初始化
-
记录当前流读取器,并标记
AI
正在生成内容。 -
创建解码器和缓冲区,用于处理流式数据。
-
-
读取和解析
SSE
数据流-
通过
reader.read()
持续读取数据并解码。 -
使用缓冲区存储未完全解析的数据,并按
\n\n
解析完整的SSE
事件。 -
识别事件类型 (
message
或close
),提取data
部分。
-
-
处理关闭事件
-
如果服务器发送
close
事件,检查是否包含错误信息,并向用户提示错误。 -
终止数据流处理。
-
-
更新消息内容
-
查找
aiMessageId
对应的消息对象,并将新内容追加,实现打字机效果。 -
处理
reasoningContent
(推理内容),更新时间戳,并决定是否显示推理信息。 -
滚动到底部,确保用户看到最新消息。
-
-
异常处理
- 处理
JSON
解析失败或数据读取异常,并向用户显示错误提示。
- 处理
-
清理资源
- 释放流读取器,重置状态,标记
AI
生成完成。
- 释放流读取器,重置状态,标记
后端 API 处理(Go + Gin + SSE)
后端采用 Go Gin 框架 处理前端请求,并通过 SSE
返回流式数据。核心代码实现如下所示:
go
streamData, err := h.serv.ChatCompletion(ctx, userId, chatPrompt)
if err != nil {
slog.Error(err.Error())
if errors.Is(err, mongo.ErrNoDocuments) {
ctx.SSEvent("close", apiwrap.NewErrorResponseBody(404, "chat not exist"))
return
} else if errors.Is(err, errors.New("user is chatting")) {
ctx.SSEvent("close", apiwrap.NewErrorResponseBody(400, "user is chatting"))
return
}
ctx.SSEvent("close", apiwrap.NewResponseBody[any](500, err.Error(), nil))
return
}
resp, _ := streamData.(*lkeap.ChatCompletionsResponse)
message := domain.ChatMessage{
CreatedAt: time.Now(),
}
defer func() {
_ = h.serv.SaveChatMessage(ctx, userId, chatId, message)
}()
for {
select {
case <-ctx.Request.Context().Done():
// 客户端主动断开连接
slog.Info("客户端断开连接")
return
case event, ok := <-resp.Events:
if !ok {
// 事件通道关闭,退出循环
ctx.SSEvent("close", apiwrap.SuccessResponse())
return
}
data := tclkep.ChatCompletionsResponse{}
err = json.Unmarshal(event.Data, &data)
if err != nil {
ctx.SSEvent("close", apiwrap.NewResponseBody[any](500, err.Error(), nil))
return
}
choice := data.Choices[0]
message.Role = "assistant"
message.Content += choice.Delta.Content
message.ReasoningContent += choice.Delta.ReasoningContent
cm := ChatMessage{
Role: choice.Delta.Role,
Content: choice.Delta.Content,
ReasoningContent: choice.Delta.ReasoningContent,
CreatedAt: data.Created,
}
ctx.SSEvent("message", cm)
ctx.Writer.Flush()
slog.Info("chat completion response: ", cm)
}
}
代码解释:
-
处理请求并调用聊天服务
-
通过
h.serv.ChatCompletion(ctx, userId, chatPrompt)
调用聊天服务,获取流式响应数据。 -
如果请求失败,根据错误类型返回不同的
SSE
事件close
(例如:聊天不存在、用户正在聊天等)。
-
-
初始化响应消息
-
解析
streamData
,并创建message
结构体用于存储AI
回复。 -
使用
defer
关键字确保在函数返回前,持久化message
数据到数据库。
-
-
监听
SSE
事件并发送响应-
进入
for
循环,不断从resp.Events
读取AI
生成的内容。 -
处理客户端断开:
- 监听
ctx.Request.Context().Done()
,如果客户端主动断开,则退出循环。
- 监听
-
解析
AI
返回的数据:-
从
resp.Events
读取event
,如果通道关闭,发送close
事件并终止循环。 -
使用
json.Unmarshal(event.Data, &data)
解析JSON
数据,提取Choices
内容。
-
-
更新并发送
AI
回复:-
解析
choice.Delta
,更新message.Content
和message.ReasoningContent
。 -
组装
ChatMessage
结构体,发送SSE
事件message
,并调用ctx.Writer.Flush()
确保数据立即推送到前端。
-
-
-
终止逻辑
-
发生错误时,发送
close
事件,返回错误信息。 -
当
resp.Events
关闭时,发送close
事件并退出循环。 -
日志记录每次
AI
生成的响应,便于调试和监控。
-
多轮交互实现(MongoDB 记录历史对话)
为了让 AI
能够理解对话上下文,每次用户发送消息时,后端系统需要 查询 MongoDB 里的历史对话 ,并将其与新问题封装后发送给 DeepSeek API
。
存储聊天记录
-
采用 MongoDB 存储每个用户的对话记录。
-
每条消息存入
chats
集合,并记录 用户 ID、对话标题,对话消息,对话时间 等信息。
MongoDB 数据结构示例:
json
{
"_id": {"$oid": "67d799fc32fa24462017e415"},
"created_at": {"$date": "2025-03-17T03:41:48.941Z"},
"title": "Go 语言 mongox 库:简化操作、安全、高效、可扩展、BSON 构建.pdf",
"updated_at": {"$date": "2025-03-17T03:42:46.570Z"},
"user_id": "chenmingyong",
"messages": [
{
"_id": {"$oid": "67d799fc32fa24462017e315"},
"role": "system",
"content": "\n你是一位专业的文档助手,负责为用户提供关于文档的详细、准确和清晰的回答。你的任务是帮助用户理解、分析和解决与文档相关的任何问题。\n任务目标: \n1. 回答问题 :针对用户提出的问题,提供详细、准确和易于理解的回答。 \n2. 保持专业性 :在回答问题时,始终保持专业、礼貌和客观的态度。 \n回答要求:\n1. 简洁明了 :回答应简洁明了,避免冗长和不必要的细节。 \n2. 结构化 :使用段落、列表或标题来组织信息,确保易于阅读和理解。 \n3. 引用文档 :在回答中引用文档中的具体部分,以支持你的观点或解释。 \n4. 提供上下文 :如果问题涉及文档的特定部分,提供足够的上下文信息,帮助用户理解。\n注意事项: \n1. 准确性 :确保所有回答都基于文档中的实际内容,避免猜测或假设。 \n2. 用户友好 :使用用户易于理解且与文档关联语言,避免使用超纲或复杂的术语。\n",
"created_at": {"$date": "2025-03-17T03:41:57.648Z"},
"is_hidden": true
},
{
"content": "\n# 问题描述\n \n帮我总结一下文档的内容,字数不超过 300 字。\n# 文档\n\n",
"created_at": {"$date": "2025-03-17T03:41:59.728Z"},
"is_hidden": false,
"_id": {"$oid": "67d799fc32fa24462017e215"}
}
]
}
组装对话历史并发送给 DeepSeek API
当用户发送新的问题时,后端会从 MongoDB
获取所有历史对话信息,然后将历史对话合并,之后发送给 DeepSeek API
,以保持上下文对话能力。
核心代码实现如下所示:
go
// 查询历史对话
chat, err := s.repo.FindChatByUserIdAndChatId(ctx, userId, chatPrompt.ChatId)
if err != nil {
return nil, err
}
// 封装新的对话信息
if len(chat.Messages) == 0 {
chat.Messages = append(chat.Messages, domain.ChatMessage{
Role: "system",
Content: promptInfo.SystemPrompt,
CreatedAt: now,
IsHidden: true,
}, domain.ChatMessage{
Role: "user",
Content: promptInfo.UserPrompt,
CreatedAt: now,
IsHidden: true,
})
} else {
chat.Messages = append(chat.Messages, domain.ChatMessage{
Role: "user",
Content: promptInfo.UserPrompt,
CreatedAt: now,
})
}
messages := slice.Map(chat.Messages, func(idx int, message domain.ChatMessage) *lkeap.Message {
return &lkeap.Message{
Content: gkit.ToPtr(message.Content),
Role: gkit.ToPtr(message.Role),
}
})
// 与大模型进行对话
resp, err := s.client.NewChatCompletions("deepseek-r1").
WithStream(true).
WithMessages(messages).
Do()
腾讯云 SDK 封装
虽然腾讯云提供的 SDK
能够帮助我们快速接入 API
,但为了更符合我自己的编码习惯,我对其进行进一步封装。代码示例如下所示:
-
client
对象封装gopackage tclkep import ( "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" lkeap "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/lkeap/v20240522" ) // Client 封装腾讯云 LKEAP 客户端 type Client struct { client *lkeap.Client } // NewLKEAPClient 创建一个新的 LKEAP 客户端 func NewLKEAPClient(secretId, secretKey, region string) (*Client, error) { // 实例化一个认证对象 credential := common.NewCredential(secretId, secretKey) // 实例化一个client选项 cpf := profile.NewClientProfile() cpf.HttpProfile.Endpoint = "lkeap.tencentcloudapi.com" // 实例化要请求产品的client对象 client, err := lkeap.NewClient(credential, region, cpf) if err != nil { return nil, err } return &Client{client: client}, nil } // NewChatCompletions 创建一个新的聊天完成请求对象 func (c *Client) NewChatCompletions(model string) *ChatCompletions { request := lkeap.NewChatCompletionsRequest() request.Model = common.StringPtr(model) return &ChatCompletions{ client: c, request: request, } } func (c *Client) NewDocumentParser() *DocumentParser { request := lkeap.NewReconstructDocumentSSERequest() return &DocumentParser{ client: c, request: request, } }
-
腾讯云
DeepSeek API
调用封装gopackage tclkep import ( "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" lkeap "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/lkeap/v20240522" ) // ChatCompletions 聊天完成接口的构建器,支持流式调用 type ChatCompletions struct { client *Client request *lkeap.ChatCompletionsRequest } // WithMessages 设置消息 func (b *ChatCompletions) WithMessages(messages []*lkeap.Message) *ChatCompletions { b.request.Messages = messages return b } // AddMessage 添加单条消息 func (b *ChatCompletions) AddMessage(role, content string) *ChatCompletions { message := &lkeap.Message{Role: common.StringPtr(role), Content: common.StringPtr(content)} if b.request.Messages == nil { b.request.Messages = []*lkeap.Message{message} } else { b.request.Messages = append(b.request.Messages, message) } return b } // WithStream 设置是否使用流式响应 func (b *ChatCompletions) WithStream(stream bool) *ChatCompletions { b.request.Stream = common.BoolPtr(stream) return b } // Do 执行请求 func (b *ChatCompletions) Do() (*lkeap.ChatCompletionsResponse, error) { return b.client.client.ChatCompletions(b.request) } type ChatCompletionsResponse struct { Choices []Choice `json:"Choices"` Created int64 `json:"Created"` Id string `json:"Id"` Model string `json:"Model"` Object string `json:"Object"` Usage Usage `json:"Usage"` } type Choice struct { Delta Delta `json:"Delta"` Index int `json:"Index"` } type Delta struct { Content string `json:"Content,omitempty"` ReasoningContent string `json:"ReasoningContent,omitempty"` Role string `json:"Role,omitempty"` } type Usage struct { CompletionTokens int `json:"CompletionTokens"` PromptTokens int `json:"PromptTokens"` TotalTokens int `json:"TotalTokens"` }
-
腾讯云大模型知识引擎的实时文档解析
API
封装gopackage tclkep import ( "context" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" lkeap "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/lkeap/v20240522" ) type DocumentParser struct { client *Client request *lkeap.ReconstructDocumentSSERequest } func (b *DocumentParser) WithFileType(fileType string) *DocumentParser { b.request.FileType = common.StringPtr(fileType) return b } func (b *DocumentParser) WithFileUrl(fileUrl string) *DocumentParser { b.request.FileUrl = common.StringPtr(fileUrl) return b } func (b *DocumentParser) WithFileBase64(fileBase64 string) *DocumentParser { b.request.FileBase64 = common.StringPtr(fileBase64) return b } // Do 执行请求 func (b *DocumentParser) Do(ctx context.Context) (*lkeap.ReconstructDocumentSSEResponse, error) { return b.client.client.ReconstructDocumentSSEWithContext(ctx, b.request) }
以上封装的目的是为了方便实现链式调用,使代码更加简洁、易读,并提高开发效率,例如:
-
与
DeepSeek
进行对话:goresp, err := s.client.NewChatCompletions("deepseek-r1"). WithStream(true). WithMessages(messages). Do()
-
实时文档解析:
godocumentSSEResponse, err := s.client.NewDocumentParser(). WithFileType(strings.ToUpper(media.DocType)). WithFileBase64(base64.StdEncoding.EncodeToString(b)).Do(ctx)
网页内容解析与转换
通过结合 Go
语言的 go-readability
和 html-to-markdown
两个库,我们可以高效地实现网页内容的解析与格式转换。核心代码实现如下所示:
go
var article readability.Article
// 读取网页内容
article, err = readability.FromURL(url, 30*time.Second)
if err != nil {
return promptInfo, fmt.Errorf("failed to readability.FromURL: %w", err)
}
// 将 html 转成 markdown
content, err = htmltomarkdown.ConvertString(article.Content)
if err != nil {
return promptInfo, fmt.Errorf("failed to htmltomarkdown.ConvertString: %w", err)
}
将网页内容转换为 markdown
格式是因为 AI
大语言模型在处理 结构化文字(如 Markdown)时表现更好。
PDF 文件解析与转换
对于 PDF
文件,首先需要上传到服务器得到文件存储的路径,然后在对话时传递给对话接口,接下来通过路径读取文件内容,然后通过 腾讯云知识引擎原子能力 提供的 实时文档解析 API 将 PDF
的内容转换成 markdown
格式,以便大模型更易于理解文档内容。
核心代码实现如下所示:
go
func (s *ChatService) toMarkdown(ctx context.Context, media domain.Media) (string, string, error) {
var content string
filePath := strings.Replace(media.Path, "/static/", "./static/files/", 1)
// 读取 PDF 文件内容
b, fileErr := os.ReadFile(filePath)
if fileErr != nil {
return "", "", fmt.Errorf("failed to os.ReadFile: %w", fileErr)
}
// 调用腾讯云文档解析服务
documentSSEResponse, err := s.client.NewDocumentParser().
WithFileType(strings.ToUpper(media.DocType)).
WithFileBase64(base64.StdEncoding.EncodeToString(b)).Do(ctx)
if err != nil {
return "", "", fmt.Errorf("failed to s.client.NewDocumentParser.Do: %w", err)
}
// 流式响应
var documentRecognizeResultUrl string
for event := range documentSSEResponse.Events {
type EventResponse struct {
ProgressMessage string `json:"ProgressMessage"`
DocumentRecognizeResultUrl string `json:"DocumentRecognizeResultUrl"`
StatusCode string `json:"StatusCode"`
}
eResp := EventResponse{}
err = json.Unmarshal(event.Data, &eResp)
if err != nil {
panic(err)
}
if eResp.ProgressMessage == "完成文档解析" && eResp.StatusCode == "Success" {
documentRecognizeResultUrl = eResp.DocumentRecognizeResultUrl
break
}
}
// 下载文件
get, hErr := http.Get(documentRecognizeResultUrl)
if hErr != nil {
return "", "", fmt.Errorf("failed to http.Get: %w", hErr)
}
defer get.Body.Close()
// 2. 读取 ZIP 文件内容
body, err := io.ReadAll(get.Body)
if err != nil {
return "", "", err
}
// 3. 解压 ZIP 文件
reader := bytes.NewReader(body)
zipReader, err := zip.NewReader(reader, int64(len(body)))
if err != nil {
return "", "", err
}
// 4. 遍历 ZIP 文件中的文件
for _, file := range zipReader.File {
// 检查文件扩展名是否为 .md
if strings.HasSuffix(file.Name, ".md") {
// 5. 打开文件
fileReader, err := file.Open()
if err != nil {
return "", "", err
}
defer fileReader.Close()
// 6. 读取文件内容
md, err := io.ReadAll(fileReader)
if err != nil {
return "", "", err
}
content = string(md)
break
}
}
return media.FileName, content, nil
}
代码解释:
-
读取
PDF
文件-
解析
media.Path
生成本地文件路径filePath
。 -
使用
os.ReadFile(filePath)
读取PDF
文件内容,并进行错误处理。
-
-
调用腾讯云文档解析
API
-
通过
s.client.NewDocumentParser()
调用API
,发送PDF
数据(Base64
编码)。 -
监听流式响应:
-
遍历
documentSSEResponse.Events
,等待解析完成的事件。 -
提取
DocumentRecognizeResultUrl
(解析结果的下载链接)。
-
-
-
下载解析结果(
ZIP
文件)-
通过
http.Get(documentRecognizeResultUrl)
下载ZIP
文件,并读取内容。 -
使用
io.ReadAll(get.Body)
解析ZIP
数据。
-
-
解压
ZIP
并提取Markdown
文件-
通过
zip.NewReader()
解析ZIP
文件内容。 -
遍历
zipReader.File
查找.md
文件。 -
读取
Markdown
文件内容,并存入content
变量。
-
-
返回结果
-
返回文件名
media.FileName
和解析出的Markdown
内容content
。 -
处理可能的错误(文件读取、
API
调用、ZIP
解压等)。
-
system prompt 与 user prompt 的封装
在 AI 智能阅读助 里,需要封装两种类型的 prompt
:system prompt
和 user prompt
。
-
system prompt
:-
由开发者或系统设定的隐藏指令,用于定义
AI
的角色、行为准则或回答风格。 -
在和腾讯云
DeepSeek
第一次对话时,需要携带system prompt
参数,以便让AI
提供更为精确和定制化的回答。 -
system prompt
参考内容如下:你是一位专业的文档助手,负责为用户提供关于文档的详细、准确和清晰的回答。你的任务是帮助用户理解、分析和解决与文档相关的任何问题。
任务要求:
-
回答问题 :针对用户提出的问题,提供详细、准确和易于理解的回答。
-
保持专业性 :在回答问题时,始终保持专业、礼貌和客观的态度。
回答要求:
-
简洁明了 :回答应简洁明了,避免冗长和不必要的细节。
-
结构化 :使用段落、列表或标题来组织信息,确保易于阅读和理解。
-
引用文档 :在回答中引用文档中的具体部分,以支持你的观点或解释。
-
提供上下文 :如果问题涉及文档的特定部分,提供足够的上下文信息,帮助用户理解。
注意事项:
-
准确性 :确保所有回答都基于文档中的实际内容,避免猜测或假设。
-
用户友好 :使用用户易于理解且与文档关联语言,避免使用超纲或复杂的术语。
-
-
-
user prompt
:用户直接输入的请求或问题,明确告知AI
需要执行的任务。-
用户选择 阅读当前网页 或 阅读PDF文件 时,第一次对话的
user prompt
由后端系统封装,格式如下:问题描述
帮我总结文档的内容。
文档
${document content}
-
在之后的对话里,user prompt
会被直接指定为 用户输入 的内容。
小结
在这个 AI
智能阅读助手项目中,我使用了腾讯云提供的 DeepSeek API
,充分利用了 DeepSeek
大语言模型的自然语言处理能力。这个模型在理解文本内容、提取关键信息方面表现得非常出色,成为了整个项目的核心技术支撑。腾讯云的 API
服务在稳定性和响应速度上也满足了项目需求,接口调用体验非常流畅,价格方面也是非常实惠。
除了腾讯云的DeepSeek API
,我还借助了腾讯云大模型知识引擎的 实时文档解析 API
将 PDF
的内容转换成 markdown
格式,以便大模型更易于理解文档内容。腾讯云大模型知识引擎除了 DeepSeek
和文档解析 API
以外,还提供了很多与 AI
应用相关的 API
,例如 获取特征 和 向量多轮改写 等。
尽管这个阅读助手目前仍处于原型阶段,功能还有待完善,但通过这次开发实践,我对如何将 AI
能力与实际阅读需求相结合有了更深入的思考。这篇文章的主要目的是记录设计思路和技术选择,希望能为有类似需求的开发者提供一些参考和启发。
你好,我是陈明勇,一名热爱技术、乐于分享的开发者,同时也是开源爱好者。
我专注于分享 Go
语言相关的技术知识,同时也会深入探讨 AI
领域的前沿技术。
成功的路上并不拥挤,有没有兴趣结个伴?