Parse Paper (PDF)
使用 Mistral OCR API 获得文本和图片
import { Mistral } from '@mistralai/mistralai' ;
import fs from 'fs' ;
const apiKey = process.env. MISTRAL_API_KEY ;
const client = new Mistral ({ apiKey: apiKey });
function encodeFile ( pdfPath ) {
const fileBuffer = fs. readFileSync (pdfPath);
const base64Pdf = fileBuffer. toString ( 'base64' );
return base64Pdf;
}
const filePath = "path/to/main.pdf" ;
const base64File = encodeFile (filePath);
const ocrResponse = await client.ocr. process ({
document: {
type: "document_url" ,
documentUrl: "data:application/pdf;base64," + base64File
},
model: "mistral-ocr-latest" ,
includeImageBase64: true ,
extractHeader: true ,
extractFooter: true
});
使用 Jina Segmenter API 进行 chunking
import { z } from "zod" ;
// --- 请求参数 (Input) 模式 ---
export const SegmentInputSchema = z. object ({
content: z. string (). describe ( "需要分块的文本内容" ),
tokenizer: z. string (). default ( "o200k_base" ). describe ( "使用的分词器名称" ),
return_chunks: z. boolean (). default ( true ). describe ( "是否在响应中返回具体的分块文本" ),
max_chunk_length: z. number (). int (). positive (). optional (). describe ( "分块的最大长度" ),
});
// 如果你需要提取 TypeScript 类型供其他地方使用:
export type SegmentInput = z . infer < typeof SegmentInputSchema>;
// --- 响应数据 (Output) 模式 ---
export const SegmentOutputSchema = z. object ({
num_tokens: z. number (). int (). describe ( "消耗的总 token 数量" ),
tokenizer: z. string (). describe ( "实际使用的分词器" ),
usage: z. object ({
tokens: z. number (). int (),
}),
num_chunks: z. number (). int (). describe ( "切分的总块数" ),
// chunk_positions 是一个数组,内部是 [start, end] 格式的元组
chunk_positions: z. array (
z. tuple ([z. number (). int (), z. number (). int ()])
). describe ( "每个分块的起始和结束位置索引" ),
chunks: z. array (z. string ()). describe ( "具体的分块文本数组" ),
});
export type SegmentOutput = z . infer < typeof SegmentOutputSchema>;
用 Jina Embedding
import { z } from "zod" ;
// --- 请求参数 (Input) 模式 ---
export const EmbeddingsInputSchema = z. object ({
model: z. string (). describe ( "使用的模型名称,如 jina-embeddings-v5-text-small" ),
task: z. string (). optional (). describe ( "任务类型,例如 retrieval.query" ),
dimensions: z. number (). int (). positive (). optional (). describe ( "输出向量的维度,例如 768" ),
normalized: z. boolean (). optional (). describe ( "是否对输出的向量进行归一化处理" ),
input: z. array (z. string ()). min ( 1 ). describe ( "需要转换为向量的文本数组" ),
});
// 提取请求类型
export type EmbeddingsInput = z . infer < typeof EmbeddingsInputSchema>;
// --- 响应数据 (Output) 模式 ---
export const EmbeddingsOutputSchema = z. object ({
model: z. string (). describe ( "实际使用的模型名称" ),
object: z. string (). describe ( "对象类型,通常为 'list'" ),
usage: z. object ({
total_tokens: z. number (). int (). describe ( "消耗的总 token 数量" ),
}),
data: z. array (
z. object ({
object: z. string (). describe ( "数据项类型,通常为 'embedding'" ),
index: z. number (). int (). nonnegative (). describe ( "对应输入文本在数组中的索引位置" ),
embedding: z. array (z. number ()). describe ( "生成的浮点数向量数组" ),
})
). describe ( "生成的 embedding 数据列表" ),
});
// 提取响应类型
export type EmbeddingsOutput = z . infer < typeof EmbeddingsOutputSchema>;
-- 建索引时,使用 vector_ip_ops (内积) 替代 vector_cosine_ops
CREATE INDEX ON documents USING vchordrq (embedding vector_ip_ops);
-- 查询时,使用 <#> 获取基于内积的相似度排序(配合 L2 归一化,结果与余弦相似度完全一致,但速度更快)
SELECT id, content
FROM documents
ORDER BY embedding < # > '[你的查询向量,且必须也经过了L2归一化]'
LIMIT 10 ;
使用 Vector + vchord-bm25 RRF 混合搜索结果
LLM 使用 structured output 进行回答