Parse Paper (PDF)

  1. 使用 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
});
  1. 使用 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>;
  1. 用 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;
  1. 使用 Vector + vchord-bm25 RRF 混合搜索结果
  2. LLM 使用 structured output 进行回答

Extract