> ## Documentation Index
> Fetch the complete documentation index at: https://langchain-docs-zh.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# SAP HANA Cloud 向量引擎

> SAP HANA Cloud Vector Engine 是一个完全集成到 SAP HANA Cloud 数据库中的向量存储。

> [SAP HANA Cloud Vector Engine](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-vector-engine-guide/sap-hana-cloud-sap-hana-database-vector-engine-guide) 是一个完全集成到 `SAP HANA Cloud` 数据库中的向量存储。

## 安装

安装 `@sap/hana-langchain` 外部集成包，以及本笔记本中使用的其他包。

```bash theme={null}
npm install @sap/hana-langchain @langchain/core@latest @langchain/classic@latest langchain@latest
```

### 凭证

确保您的 SAP HANA 实例正在运行。从环境变量加载凭证，并使用您首选的 HANA 客户端创建连接。

```typescript theme={null}
import * as dotenv from 'dotenv';
dotenv.config();

import hanaClient from "@sap/hana-client";

const connectionParams = {
  host: process.env.HANA_DB_ADDRESS,
  port: process.env.HANA_DB_PORT,
  user: process.env.HANA_DB_USER,
  password: process.env.HANA_DB_PASSWORD,
};
const client = hanaClient.createConnection(connectionParams);

// connect to hanaDB
await new Promise<void>((resolve, reject) => {
  client.connect((err: Error) => {
    // Use arrow function here
    if (err) {
      reject(err);
    } else {
      console.log("Connected to SAP HANA successfully.");
      resolve();
    }
  });
});
```

了解更多关于 SAP HANA 的信息，请参阅 [什么是 SAP HANA?](https://www.sap.com/products/data-cloud/hana/what-is-sap-hana.html)。

## 初始化

要初始化一个 `HanaDB` 向量存储，您需要一个数据库连接和一个嵌入实例。SAP HANA Cloud Vector Engine 支持外部和内部嵌入。

### 使用外部嵌入

```typescript theme={null}
import { OpenAIEmbeddings } from "@langchain/openai";
const embeddings = new OpenAIEmbeddings({ model: "text-embedding-3-large" });
```

### 使用内部嵌入

或者，您可以直接在 SAP HANA 中使用其原生 `VECTOR_EMBEDDING()` 函数计算嵌入。如果您在 TypeScript 环境中提供内部嵌入支持，可以像传递 `HanaDB` 一样初始化并传递它。有关内部嵌入的更多信息，请参阅 [SAP HANA VECTOR\_EMBEDDING 函数](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-vector-engine-guide/vector-embedding-function-vector)。

> 注意：确保您的 SAP HANA Cloud 实例中启用了 NLP。

```typescript theme={null}
import { HanaInternalEmbeddings } from "@sap/hana-langchain";

const internalEmbeddings = new HanaInternalEmbeddings({
  internalEmbeddingModelId:
    process.env.HANA_DB_EMBEDDING_MODEL_ID || "SAP_NEB.20240715",
});
// optionally, you can specify a remote source to use models from your deployed SAP AI CORE instance:
/*
const internalEmbeddings = new HanaInternalEmbeddings({
  internalEmbeddingModelId:
    process.env.HANA_DB_EMBEDDING_REMOTE_MODEL_ID || "YOUR_EMBEDDING_MODEL_ID",
  remoteSourceSchema:
    process.env.HANA_DB_EMBEDDING_REMOTE_SOURCE_SCHEMA || "YOUR_REMOTE_SOURCE_SCHEMA_NAME",
  remoteSource:
    process.env.HANA_DB_EMBEDDING_REMOTE_SOURCE || "YOUR_REMOTE_SOURCE_NAME",
});
*/
```

一旦您有了连接和嵌入实例，可以通过将它们传递给 `HanaDB` 并提供用于存储向量的表名来创建向量存储：

```typescript theme={null}
// define instance args
// check the interface to see all possible options
const args: HanaDBArgs = {
  connection: client,
  tableName: "MY_TABLE",
};
// Create a LangChain VectorStore interface for the HANA database and specify the table (collection) to use in args.
const db = new HanaDB(embeddings, args);
// need to initialize once an instance is created.
await db.initialize()
```

## 管理向量存储

创建向量存储后，我们可以通过添加和删除不同的项目来与之交互。

### 向向量存储添加项目

我们可以通过使用 `addDocuments` 函数向我们的向量存储添加项目。

```typescript theme={null}
import { Document } from "@langchain/core/documents";
const docs = [new Document({ pageContent: "Some text" }), new Document({ pageContent: "Other docs" })];
await db.addDocuments(docs);
```

添加带元数据的文档

```typescript theme={null}
await db.addDocuments([
  { pageContent: "foo", metadata: { start: 100, end: 150, docName: "foo.txt", quality: "bad" } },
  { pageContent: "bar", metadata: { start: 200, end: 250, docName: "bar.txt", quality: "good" } },
]);
```

使用 map merge 插入添加文档

> 注意 `useMapMerge` 仅允许用于 `HanaInternalEmbeddings` 实例。

```typescript theme={null}
// A useMapMerge flag can be supplied in the options for faster insertion
// map merge only works with internal Embeddings
await db.addDocuments(docs, { useMapMerge: true });
```

### 从向量存储删除项目

```typescript theme={null}
await db.delete({ filter: { quality: "bad" } });
```

## 查询向量存储

### 直接查询

#### 相似度搜索

执行简单的相似度搜索并在元数据上过滤，可以按如下方式完成：

```typescript theme={null}
// With filtering on {"quality": "bad"}, only one document should be returned
const docs = await db.similaritySearch("foobar", 2, { quality: "bad" });
console.log(docs);
```

```text theme={null}
[
    {
    pageContent: "foo",
    metadata: { start: 100, end: 150, docName: "foo.txt", quality: "bad" }
    }
]
```

#### MMR 搜索

执行最大边际相关性（MMR）并在元数据上过滤的搜索，可以按如下方式完成：

```typescript theme={null}
const docsMMR = await db.maxMarginalRelevanceSearch("foobar", {
    k: 2,
    fetchK: 5,
    filter: { quality: "bad" },
});
console.log(docsMMR);
```

```text theme={null}
[
    {
    pageContent: "foo",
    metadata: { start: 100, end: 150, docName: "foo.txt", quality: "bad" }
    }
]
```

### 将向量存储转换为检索器

您还可以将向量存储转换为检索器，以便在您的链中更轻松地使用。

```typescript theme={null}
const retriever = db.asRetriever();
const docsRetriever = await retriever.invoke("foobar", {filter: { quality: "good" }});
console.log(docsRetriever);
```

```text theme={null}
[
    {
    pageContent: "bar",
    metadata: { start: 200, end: 250, docName: "bar.txt", quality: "good" }
    }
]
```

## 距离相似度算法

`HanaDB` 支持以下距离相似度算法：

* 余弦相似度（默认）
* 欧几里得距离（L2）

您可以通过使用 `distanceStrategy` 参数在初始化 `HanaDB` 实例时指定距离策略。

```typescript theme={null}
const argsDist: HanaDBArgs = {
  connection: client,
  tableName: "MY_TABLE",
  distanceStrategy: "EUCLIDEAN",
    // distanceStrategy: "COSINE", // (default)
};
const dbDist = new HanaDB(embeddings, argsDist);
await dbDist.initialize();
```

## 创建 HNSW 索引

向量索引可以显著加快向量的 top-k 最近邻查询。用户可以使用 `createHnswIndex` 函数创建分层可导航小世界（HNSW）向量索引。

有关在数据库级别创建索引的更多信息，请参阅 [官方文档](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-vector-engine-guide/create-vector-index-statement-data-definition)。

```typescript theme={null}
const argsHnsw: HanaDBArgs = {
  connection: client,
  tableName: "MY_TABLE",
};
const dbHnsw = new HanaDB(embeddings, argsHnsw);
await dbHnsw.initialize();
dbHnsw.createHnswIndex({
  indexName: "MY_HNSW_INDEX",
  efSearch: 100, // Max number of neighbors per graph node (valid range: 4 to 1000)
  m: 200, // Max number of candidates during graph construction (valid range: 1 to 100000)
  efConstruction: 500, // Min number of candidates during the search (valid range: 1 to 100000)
});
```

如果未指定其他参数，将使用默认值

默认值：m=64, ef\_construction=128, ef\_search=200

默认索引名称将是："\<TABLE\_NAME>\_idx"

## 高级过滤

除了基本的基于值的过滤功能外，还可以使用更高级的过滤。下表显示了可用的过滤运算符。

| 运算符 | 语义 | 支持的操作数数据类型 |
| - | - | - |
| `$eq` | 相等 (==) | number, string, boolean, DateValue, null |
| `$ne` | 不相等 (!=) | number, string, boolean, DateValue, null |
| `$lt` | 小于 (\<) | number, string, DateValue |
| `$lte` | 小于或等于 (\<=) | number, string, DateValue |
| `$gt` | 大于 (>) | number, string, DateValue |
| `$gte` | 大于或等于 (>=) | number, string, DateValue |
| `$in` | 包含在给定值的集合中  (in) | 类型为 (number, string, boolean, DateValue) 的相同值数组 |
| `$nin` | 不包含在给定值的集合中  (not in) | 类型为 (number, string, boolean, DateValue) 的相同值数组 |
| `$between` | 在两个边界值之间的范围内 | 类型为 (number, string, DateValue) 的 2 值数组 |
| `$like` | 基于 SQL 中 "LIKE" 语义的文本相等（使用 "%" 作为通配符） | string |
| `$contains` | 过滤包含特定关键字的文档 | 非空字符串 |
| `$and` | 逻辑 "与"，支持两个或多个操作数 | list(Filter) |
| `$or` | 逻辑 "或"，支持两个或多个操作数 | list(Filter) |

```typescript theme={null}
const docs: Document[] = [
  {
    pageContent: "First",
    metadata: { name: "Adam Smith", is_active: true, id: 1, height: 10.0 },
  },
  {
    pageContent: "Second",
    metadata: { name: "Bob Johnson", is_active: false, id: 2, height: 5.7 },
  },
  {
    pageContent: "Third",
    metadata: { name: "Jane Doe", is_active: true, id: 3, height: 2.4 },
  },
];

const args: HanaDBArgs = {
  connection: client,
  tableName: "LANGCHAIN_DEMO_ADVANCED_FILTER",
};

const vectorStore = new HanaDB(embeddings, args);
// need to initialize once an instance is created.
await vectorStore.initialize();

// Delete already existing documents from the table
await vectorStore.delete({ filter: {} });
await vectorStore.addDocuments(docs);

// Helper function to print filter results
function printFilterResult(result: Document[]) {
  if (result.length === 0) {
    console.log("<empty result>");
  } else {
    result.forEach((doc) => console.log(JSON.stringify(doc.metadata)) );
  }
}
```

使用 `$ne`, `$gt`, `$gte`, `$lt`, `$lte` 进行过滤

```typescript theme={null}
let advancedFilter;

advancedFilter = { id: { $ne: 1 } };
console.log(`Filter: ${JSON.stringify(advancedFilter)}`);
printFilterResult(
  await vectorStore.similaritySearch("just testing", 5, advancedFilter)
);

advancedFilter = { id: { $gt: 1 } };
console.log(`Filter: ${JSON.stringify(advancedFilter)}`);
printFilterResult(
  await vectorStore.similaritySearch("just testing", 5, advancedFilter)
);

advancedFilter = { id: { $gte: 1 } };
console.log(`Filter: ${JSON.stringify(advancedFilter)}`);
printFilterResult(
  await vectorStore.similaritySearch("just testing", 5, advancedFilter)
);

advancedFilter = { id: { $lt: 1 } };
console.log(`Filter: ${JSON.stringify(advancedFilter)}`);
printFilterResult(
  await vectorStore.similaritySearch("just testing", 5, advancedFilter)
);

advancedFilter = { id: { $lte: 1 } };
console.log(`Filter: ${JSON.stringify(advancedFilter)}`);
printFilterResult(
  await vectorStore.similaritySearch("just testing", 5, advancedFilter)
);
```

```text theme={null}
Filter: {"id":{"$ne":1}}
{ name: 'Bob Johnson', is_active: false, id: 2, height: 5.7 }
{ name: 'Jane Doe', is_active: true, id: 3, height: 2.4 }
Filter: {"id":{"$gt":1}}
{ name: 'Bob Johnson', is_active: false, id: 2, height: 5.7 }
{ name: 'Jane Doe', is_active: true, id: 3, height: 2.4 }
Filter: {"id":{"$gte":1}}
{ name: 'Adam Smith', is_active: true, id: 1, height: 10 }
{ name: 'Bob Johnson', is_active: false, id: 2, height: 5.7 }
{ name: 'Jane Doe', is_active: true, id: 3, height: 2.4 }
Filter: {"id":{"$lt":1}}
<empty result>
Filter: {"id":{"$lte":1}}
{ name: 'Adam Smith', is_active: true, id: 1, height: 10 }
```

使用 `$between`, `$in`, `$nin` 进行过滤

```typescript theme={null}
advancedFilter = { id: { $between: [1, 2] } };
console.log(`Filter: ${JSON.stringify(advancedFilter)}`);
printFilterResult(
  await vectorStore.similaritySearch("just testing", 5, advancedFilter)
);

advancedFilter = { name: { $in: ["Adam Smith", "Bob Johnson"] } };
console.log(`Filter: ${JSON.stringify(advancedFilter)}`);
printFilterResult(
  await vectorStore.similaritySearch("just testing", 5, advancedFilter)
);

advancedFilter = { name: { $nin: ["Adam Smith", "Bob Johnson"] } };
console.log(`Filter: ${JSON.stringify(advancedFilter)}`);
printFilterResult(
  await vectorStore.similaritySearch("just testing", 5, advancedFilter)
);

```

```text theme={null}
Filter: {"id":{"$between":[1,2]}}
{ name: 'Adam Smith', is_active: true, id: 1, height: 10 }
{ name: 'Bob Johnson', is_active: false, id: 2, height: 5.7 }
Filter: {"name":{"$in":["Adam Smith","Bob Johnson"]}}
{ name: 'Adam Smith', is_active: true, id: 1, height: 10 }
{ name: 'Bob Johnson', is_active: false, id: 2, height: 5.7 }
Filter: {"name":{"$nin":["Adam Smith","Bob Johnson"]}}
{ name: 'Jane Doe', is_active: true, id: 3, height: 2.4 }
```

使用 `$like` 进行文本过滤

```typescript theme={null}
advancedFilter = { name: { $like: "a%" } };
console.log(`Filter: ${JSON.stringify(advancedFilter)}`);
printFilterResult(
  await vectorStore.similaritySearch("just testing", 5, advancedFilter)
);

advancedFilter = { name: { $like: "%a%" } };
console.log(`Filter: ${JSON.stringify(advancedFilter)}`);
printFilterResult(
  await vectorStore.similaritySearch("just testing", 5, advancedFilter)
);
```

```text theme={null}
Filter: {"name":{"$like":"a%"}}
{ name: 'Adam Smith', is_active: true, id: 1, height: 10 }
Filter: {"name":{"$like":"%a%"}}
{ name: 'Adam Smith', is_active: true, id: 1, height: 10 }
{ name: 'Jane Doe', is_active: true, id: 3, height: 2.4 }
```

使用 `$contains` 进行文本过滤

```typescript theme={null}
advancedFilter = { name: { $contains: "bob" } };
console.log(`Filter: ${JSON.stringify(advancedFilter)}`);
printFilterResult(
  await vectorStore.similaritySearch("just testing", 5, advancedFilter)
);

advancedFilter = { name: { $contains: "bo" } };
console.log(`Filter: ${JSON.stringify(advancedFilter)}`);
printFilterResult(
  await vectorStore.similaritySearch("just testing", 5, advancedFilter)
);

advancedFilter = {"name": {"$contains": "Adam Johnson"}}
console.log(`Filter: ${JSON.stringify(advancedFilter)}`);
printFilterResult(
  await vectorStore.similaritySearch("just testing", 5, advancedFilter)
);

advancedFilter = {"name": {"$contains": "Adam Smith"}}
console.log(`Filter: ${JSON.stringify(advancedFilter)}`);
printFilterResult(
  await vectorStore.similaritySearch("just testing", 5, advancedFilter)
);
```

```text theme={null}
Filter: {"name":{"$contains":"bob"}}
{ name: 'Bob Johnson', is_active: false, id: 2, height: 5.7 }
Filter: {"name":{"$contains":"bo"}}
<empty result>
Filter: {'name': {'$contains': 'Adam Johnson'}}
<empty result>
Filter: {'name': {'$contains': 'Adam Smith'}}
{'name': 'Adam Smith', 'is_active': True, 'id': 1, 'height': 10.0}
```

使用 `$and`, `$or` 进行组合过滤

```typescript theme={null}
advancedFilter = { $or: [{ id: 1 }, { name: "bob" }] };
console.log(`Filter: ${JSON.stringify(advancedFilter)}`);
printFilterResult(
  await vectorStore.similaritySearch("just testing", 5, advancedFilter)
);

advancedFilter = { $and: [{ id: 1 }, { id: 2 }] };
console.log(`Filter: ${JSON.stringify(advancedFilter)}`);
printFilterResult(
  await vectorStore.similaritySearch("just testing", 5, advancedFilter)
);

advancedFilter = { $and: [{ name: { $contains: "bob" } }, { id: 2 }] };
console.log(`Filter: ${JSON.stringify(advancedFilter)}`);
printFilterResult(
  await vectorStore.similaritySearch("just testing", 5, advancedFilter)
);

advancedFilter = { $or: [{ id: 1 }, { id: 2 }, { id: 3 }] };
console.log(`Filter: ${JSON.stringify(advancedFilter)}`);
printFilterResult(
  await vectorStore.similaritySearch("just testing", 5, advancedFilter)
);

advancedFilter = {
  $and: [{ $or: [{ id: 1 }, { id: 2 }] }, { height: { $gte: 5.0 } }],
};
console.log(`Filter: ${JSON.stringify(advancedFilter)}`);
printFilterResult(
  await vectorStore.similaritySearch("just testing", 5, advancedFilter)
);
```

```text theme={null}
Filter: {'$or': [{'id': 1}, {'name': 'bob'}]}
{'name': 'Adam Smith', 'is_active': True, 'id': 1, 'height': 10.0}
Filter: {"$and":[{"id":1},{"id":2}]}
<empty result>
Filter: {"$and":[{"name":{"$contains":"bob"}},{"id":2}]}
{ name: 'Bob Johnson', is_active: false, id: 2, height: 5.7 }
Filter: {"$or":[{"id":1},{"id":2},{"id":3}]}
{ name: 'Adam Smith', is_active: true, id: 1, height: 10 }
{ name: 'Bob Johnson', is_active: false, id: 2, height: 5.7 }
{ name: 'Jane Doe', is_active: true, id: 3, height: 2.4 }
Filter: {"$and":[{"$or":[{"id":1},{"id":2}]},{"height":{"$gte":5.0}}]}
{ name: 'Adam Smith', is_active: true, id: 1, height: 10 }
{ name: 'Bob Johnson', is_active: false, id: 2, height: 5.7 }
```

## 重新排序结果

相似度搜索的结果可以通过将 `rerank_config` 字典传递给 `similaritySearch` 方法来进行重新排序。重新排序使用 SAP HANA 的 `CROSS_ENCODE` 函数和交叉编码模型执行。

`rerank_config` 字典支持以下字段：

* **`query`** *(string, 必填)* — 用于重新排序的查询文本。如果未提供，将使用原始相似度搜索查询。

* **`modelId`** *(string, 必填)* — 用于重新排序的交叉编码模型 ID。

* **`topN`** *(number, 可选)* — 重新排序后返回的顶部结果数量。默认为 3。

* **`rankFields`** *(string\[], 可选)* — 要包含在重新排序中的元数据字段列表。指定时，这些字段将与内容连接起来进行评分。

> **注意：** 确保您的 SAP HANA Cloud 实例中启用了 NLP，以使用重新排序功能。更多详情，请参阅 [SAP HANA 数据库附加功能](https://help.sap.com/docs/hana-cloud/sap-hana-cloud-administration-guide/sap-hana-database-additional-features?version=hanacloud)。

```typescript theme={null}
// Prepare sample documents with metadata
const docs = [
    new Document({
        pageContent: "Python is a programming language",
        metadata: { category: "programming", difficulty: "beginner" },
    }),
    new Document({
        pageContent: "Machine learning uses algorithms to learn patterns",
        metadata: { category: "AI", difficulty: "intermediate" },
    }),
    new Document({
        pageContent: "Neural networks are inspired by the human brain",
        metadata: { category: "AI", difficulty: "advanced" },
    }),
];

// Initialize embeddings
const embeddings = new OpenAIEmbeddings();

const args: HanaDBArgs = {
  connection: client,
  tableName: "testReranking",
};

// Create a LangChain VectorStore interface for the HANA database and specify the table (collection) to use in args.
const vectorStore = new HanaDB(embeddings, args);
// need to initialize once an instance is created.
await vectorStore.initialize();
// Delete already existing documents from the table
await vectorStore.delete({ filter: {} });
await vectorStore.addDocuments(docs);
```

### 基本重新排序

使用 `rerankConfig` 参数执行带重新排序的相似度搜索：

```typescript theme={null}
const rerankConfig: RerankConfigOptions = {
  modelId: process.env.HANA_DB_RERANKING_MODEL_ID || "SAP_CER.20250701",
  topN: 2,
}

const docsReranked = await vectorStore.similaritySearch("AI Technology", 3, undefined, undefined, rerankConfig);
console.log("Reranked Results:");
docsReranked.forEach((doc) => {
  console.log("-".repeat(80));
  console.log(`Content: ${doc.pageContent}`);
  console.log("Metadata:", doc.metadata);
});
```

```text theme={null}
Reranked Results:
--------------------------------------------------------------------------------
Content: Machine learning uses algorithms to learn patterns
Metadata: { category: 'AI', difficulty: 'intermediate' }
--------------------------------------------------------------------------------
Content: Python is a programming language
Metadata: { category: 'programming', difficulty: 'beginner' }
```

### 带元数据字段的重新排序

使用 `rankFields` 在重新排序过程中包含元数据。当元数据包含应影响排名的相关信息时，这很有用：

```typescript theme={null}
const rerankConfigWithFields: RerankConfigOptions = {
  query: "beginner AI Concepts",
  modelId: process.env.HANA_DB_RERANKING_MODEL_ID || "SAP_CER.20250701",
  topN: 2,
  rankFields: ["category", "difficulty"],
};

const docsRerankedwithFields = await vectorStore.similaritySearch("learning algorithm", 3, undefined, undefined, rerankConfigWithFields);
console.log("Reranked results with metadata fields:");
docsRerankedwithFields.forEach((doc) => {
  console.log("-".repeat(80));
  console.log(`Content: ${doc.pageContent}`);
  console.log("Metadata:", doc.metadata);
});
```

```text theme={null}
Reranked results with metadata fields:
--------------------------------------------------------------------------------
Content: Neural networks are inspired by the human brain
Metadata: { category: 'AI', difficulty: 'advanced' }
--------------------------------------------------------------------------------
Content: Machine learning uses algorithms to learn patterns
Metadata: { category: 'AI', difficulty: 'intermediate' }
```

### 带分数的重新排序

使用 `similaritySearchWithScore` 获取与文档一起的重新排序分数：

```typescript theme={null}
const rerankConfigWithScores: RerankConfigOptions = {
  modelId: process.env.HANA_DB_RERANKING_MODEL_ID || "SAP_CER.20250701",
  topN: 3,
};

const docsRerankedwithScores = await vectorStore.similaritySearchWithScore("neural network architecture", 3, undefined, undefined, rerankConfigWithScores);
console.log("Reranked results with scores:");
docsRerankedwithScores.forEach(([doc, score]) => {
  console.log("-".repeat(80));
  console.log("Score:", score.toFixed(4));
  console.log(`Content: ${doc.pageContent}`);
});
```

```text theme={null}
Reranked results with scores:
--------------------------------------------------------------------------------
Score: 0.0435
Content: Neural networks are inspired by the human brain
--------------------------------------------------------------------------------
Score: 0.0146
Content: Python is a programming language
--------------------------------------------------------------------------------
Score: 0.0145
Content: Machine learning uses algorithms to learn patterns
```

### 独立的 HanaReranker 重新排序

为了对重新排序过程有更多控制，您可以直接使用 `HanaReranker` 类作为文档压缩器。当您需要从任何来源重新排序文档，而不仅仅是相似性搜索结果时，这很有用：

```typescript theme={null}
import { HanaReranker, RerankConfigOptions } from "@sap/hana-langchain";

// Documents to rerank (can come from any source)
const docsToCompress = [
  new Document({
    pageContent: "Python programming basics",
  }),
  new Document({
    pageContent: "Advanced machine learning techniques",
  }),
  new Document({
    pageContent: "Introduction to neural networks",
  }),
  new Document({
    pageContent: "Deep learning applications",
  }),
  new Document({
    pageContent: "Reinforcement learning strategies",
  }),
  new Document({
    pageContent: "Natural language processing techniques",
  }),
];
const reranker = new HanaReranker(client, process.env.HANA_DB_RERANKING_MODEL_ID || "SAP_CER.20250701");
await reranker.initialize();

// Rerank documents based on a query using compress_documents (returns top Math.min(5, documents.length))
// Reranking scores will be added to the metadata of each document under the key "relevance_score"
const compressedDocs = await reranker.compressDocuments(docsToCompress, "AI and deep learning");

console.log("Reranked documents:");
compressedDocs.forEach((doc) => {
  console.log("-".repeat(80));
  console.log(`Content: ${doc.pageContent}`);
  console.log(`Relevance score: ${doc.metadata?.relevance_score?.toFixed(4)}`);
});

// Or use the rerank method for more control over topN
const rerankedDocs = await reranker.rerank(docsToCompress, "machine learning", 2);
console.log("Top 2 reranked results:");
rerankedDocs.forEach(([idx, score, doc]) => {
  console.log(`  [${idx}] Score: ${score.toFixed(4)} - ${doc?.pageContent}`);
});
```

```text theme={null}
Compressed documents:
--------------------------------------------------------------------------------
Content: Deep learning applications
Relevance score: 0.2188
--------------------------------------------------------------------------------
Content: Advanced machine learning techniques
Relevance score: 0.0748
--------------------------------------------------------------------------------
Content: Introduction to neural networks
Relevance score: 0.0079
--------------------------------------------------------------------------------
Content: Natural language processing techniques
Relevance score: 0.0040
--------------------------------------------------------------------------------
Content: Reinforcement learning strategies
Relevance score: 0.0036

Top 2 reranked results:
  [1] Score: 0.4351 - Advanced machine learning techniques
  [3] Score: 0.0342 - Deep learning applications
```

## 用于检索增强生成

有关如何使用此向量存储进行检索增强生成（RAG）的指南，请参阅以下章节：

* [教程](https://langchain-docs-zh.mintlify.site/langchain/oss/deepagents/rag.md)
* [如何操作：使用 RAG 进行问答](https://langchain-docs-zh.mintlify.site/langchain/oss/deepagents/rag.md)
* [检索概念文档](https://langchain-docs-zh.mintlify.site/langchain/oss/langchain/retrieval.md)

## 标准表与包含向量数据的自定义表

默认情况下，嵌入表包含三列：

* `VEC_TEXT`：文档文本
* `VEC_META`：文档元数据
* `VEC_VECTOR`：嵌入向量

自定义表必须至少有三列，这些列与标准表的语义相匹配：

* 用于文本/上下文的 `NCLOB`/`NVARCHAR` 列
* 用于元数据的 `NCLOB`/`NVARCHAR` 列
* 用于嵌入向量的 `REAL_VECTOR` 列

允许添加额外的列；确保它们接受新文档插入时的 NULL 值。

```typescript theme={null}
//  Access the vector DB with a new table
const dbDefaultArgs: HanaDBArgs = {
  connection: client,
  tableName: "LANGCHAIN_DEMO_NEW_TABLE"
};

const dbDefault = new HanaDB(embeddings, dbDefaultArgs);
await dbDefault.initialize();

//  Delete already existing entries from the table
await dbDefault.delete({ filter: {} });

//  Add a simple document with some metadata
docs = [
    new Document({
        pageContent: "A simple document",
        metadata: { start: 100, end: 150, docName: "simple.txt" }
    })
]
await dbDefault.addDocuments(docs);
```

显示表 "LANGCHAIN\_DEMO\_NEW\_TABLE" 中的列

```typescript theme={null}
const result = await new Promise<any[]>((resolve, reject) => {
  client.exec(
    `SELECT COLUMN_NAME, DATA_TYPE_NAME FROM SYS.TABLE_COLUMNS WHERE SCHEMA_NAME = CURRENT_SCHEMA AND TABLE_NAME = 'LANGCHAIN_DEMO_NEW_TABLE'`,
    (err: Error, rows: any) => {
      if (err) {
        reject(err);
      } else {
        resolve(rows);
      }
    }
  );
});
row.forEach((r) => {
  console.log(`${r.COLUMN_NAME}: ${r.DATA_TYPE_NAME}`);
});
```

```text theme={null}
VEC_TEXT: NCLOB
VEC_META: NCLOB
VEC_VECTOR: REAL_VECTOR
```

显示插入文档在三个列中的值

由于 HANA 的 dbapi 驱动程序默认将向量列输出为 Buffer 对象，我们将创建一个辅助函数将函数转换为数字列表。

```typescript theme={null}
// Helper function to parse fvecs format for REAL_VECTOR
function parseFvecs(b: ArrayBuffer): number[] {
    const v = new DataView(b)
    const d = v.getUint32(0, true)
    return Array.from({ length: d }, (_, i) => v.getFloat32(4 + i * 4, true))
}

const resultVal = await new Promise<any[]>((resolve, reject) => {
  client.exec(
    `SELECT * FROM LANGCHAIN_DEMO_NEW_TABLE LIMIT 1`,
    (err: Error, rows: any) => {
      if (err) {
        reject(err);
      } else {
        resolve(rows);
      }
    }
  );
});
rowVal.forEach((r) => {
  console.log(`VEC_TEXT: ${r.VEC_TEXT}`); // The text
  console.log(`VEC_META: ${r.VEC_META}`); // The metadata
  const embedding = parseFvecs(r.VEC_VECTOR);
  console.log(`VEC_VECTOR: ${embedding.length, embedding.slice(0, 3).concat(['...']).concat(embedding.slice(-3))}`); // The vector
});
```

```text theme={null}
VEC_TEXT: A simple document
VEC_META: {"start": 100, "end": 150, "docName": "simple.txt"}
VEC_VECTOR: 768 [-0.01989901065826416, 0.02785174734890461, 0.0020877711940556765, '...', 0.0183248370885849, 0.009469633921980858, 0.04312701150774956]
```

自定义表必须至少有三列，这些列与标准表的语义相匹配

* 用于嵌入的文本/上下文的 `NCLOB` 或 `NVARCHAR` 类型列
* 用于元数据的 `NCLOB` 或 `NVARCHAR` 类型列
* 用于嵌入向量的 `REAL_VECTOR` 或 `HALF_VECTOR` 类型列

该表可以包含额外的列。当新 Documents 插入到表中时，这些额外的列必须允许 NULL 值。

```typescript theme={null}
// Create a new table "MY_OWN_TABLE_ADD" with three "standard" columns and one additional column
const myOwnTableName = "MY_OWN_TABLE_ADD";
await new Promise<void>((resolve, reject) => {
  client.exec(
    `CREATE TABLE MY_OWN_TABLE_ADD (
      SOME_OTHER_COLUMN NVARCHAR(42),
      MY_TEXT NVARCHAR(2048),
      MY_METADATA NVARCHAR(1024),
      MY_VECTOR REAL_VECTOR,
    )`,
    (err: Error) => {
      if (err) {
        reject(err);
      } else {
        resolve();
      }
    }
  );
});

// Create a HanaDB instance with the own table
const dbOwnTableArgs: HanaDBArgs = {
  connection: client,
  tableName: myOwnTableName,
  textColumnName: "MY_TEXT",
  metadataColumnName: "MY_METADATA",
  vectorColumnName: "MY_VECTOR",
};
const dbOwnTable = new HanaDB(embeddings, dbOwnTableArgs);
await dbOwnTable.initialize();

// Add a simple document with some metadata
docs = [
    new Document({
        pageContent: "Some other text",
        metadata: {start: 400, end: 450, docName: "other.txt"}
    })
]
await dbOwnTable.addDocuments(docs);

//  Check if data has been inserted into our own table
const resultOwnTable = await new Promise<any[]>((resolve, reject) => {
  client.exec(
    `SELECT * FROM ${myOwnTableName} LIMIT 1`,
    (err: Error, rows: any) => {
      if (err) {
        reject(err);
      } else {
        resolve(rows);
      }
    }
  );
});
rowOwnTable.forEach((r) => {
  console.log(`SOME_OTHER_COLUMN: ${r.SOME_OTHER_COLUMN}`); // should be NULL
  console.log(`MY_TEXT: ${r.MY_TEXT}`); // The text
  console.log(`MY_METADATA: ${r.MY_METADATA}`); // The metadata
  const embedding = parseFvecs(r.MY_VECTOR);
  console.log(`MY_VECTOR: ${embedding.length, embedding.slice(0, 3).concat(['...']).concat(embedding.slice(-3))}`); // The vector
});
```

```text theme={null}
SOME_OTHER_COLUMN: null
MY_TEXT: Some other text
MY_METADATA: {"start":400,"end":450,"docName":"other.txt"}
MY_VECTOR: 768 [0.016170687973499298, -0.01129427831619978, -0.0005921399570070207, '...', 0.017849743366241455, 0.0003932560794055462, -0.00045805066474713385]
```

向自定义表添加另一个文档，并对自定义表执行相似性搜索。

```typescript theme={null}
const moreDocs = [
    new Document({
        pageContent: "ome more text",
        metadata: {start: 800, end: 950, docName: "more.txt"}
    })
]

awwait dbOwnTable.addDocuments(moreDocs);

const foundDocs = await dbOwnTable.similaritySearch("What's up?", 2);
foundDocs.forEach((doc) => {
    console.log("-".repeat(80));
    console.log(doc.pageContent);
});
```

```text theme={null}
--------------------------------------------------------------------------------
Some more text
--------------------------------------------------------------------------------
Some other text
```

### 使用自定义列进行过滤性能优化

为了允许灵活的元数据值，所有元数据默认都存储为 JSON 格式在元数据列中。如果已知一些使用的元数据键和值类型，可以通过创建目标表时将键名作为列名，并通过 `specificMetadataColumns` 列表将它们传递给 HanaDB 构造函数来将它们存储在额外的列中。在插入期间，与这些值匹配的元数据键会被复制到特殊列中。过滤器会使用特殊列而不是元数据 JSON 列来处理 `specificMetadataColumns` 列表中的键。

```typescript theme={null}
// Create a new table "PERFORMANT_CUSTOMTEXT_FILTER" with three "standard" columns and one additional column
const performantTableName = "PERFORMANT_CUSTOMTEXT_FILTER";
await new Promise<void>((resolve, reject) => {
  client.exec(
    `CREATE TABLE ${performantTableName} (
      CUSTOMTEXT NVARCHAR(500),
      MY_TEXT NVARCHAR(2048),
      MY_METADATA NVARCHAR(1024),
      MY_VECTOR REAL_VECTOR,
    )`,
    (err: Error) => {
      if (err) {
        reject(err);
      } else {
        resolve();
      }
    }
  );
});

// Create a HanaDB instance with the table
const dbPerformantArgs: HanaDBArgs = {
  connection: client,
  tableName: performantTableName,
  textColumnName: "MY_TEXT",
  metadataColumnName: "MY_METADATA",
  vectorColumnName: "MY_VECTOR",
  specificMetadataColumns: ["CUSTOMTEXT"],
};
const dbPerformant = new HanaDB(embeddings, dbPerformantArgs);
await dbPerformant.initialize();

// Add a simple document with some metadata

const performantDocs = [
    new Document({
        pageContent: "Some other text",
        metadata: {
            start: 400,
            end: 450,
            docName: "other.txt",
            CUSTOMTEXT: "Filters on this value are very performant"
        }
    })
]

await dbPerformant.addDocuments(performantDocs);
// Check if data has been inserted into our own table
const resultPerformant = await new Promise<any[]>((resolve, reject) => {
  client.exec(
    `SELECT * FROM ${performantTableName} LIMIT 1`,
    (err: Error, rows: any) => {
      if (err) {
        reject(err);
      } else {
        resolve(rows);
      }
    }
  );
});
rowPerformant.forEach((r) => {
  console.log(`CUSTOMTEXT: ${r.CUSTOMTEXT}`); // The custom text metadata
  console.log(`MY_TEXT: ${r.MY_TEXT}`); // The text
  console.log(`MY_METADATA: ${r.MY_METADATA}`); // The metadata
  const embedding = parseFvecs(r.MY_VECTOR);
  console.log(`MY_VECTOR: ${embedding.length, embedding.slice(0, 3).concat(['...']).concat(embedding.slice(-3))}`); // The vector
});
```

```text theme={null}
CUSTOMTEXT: Filters on this value are very performant
MY_TEXT: Some other text
MY_METADATA: {"start":400,"end":450,"docName":"other.txt","CUSTOMTEXT":"Filters on this value are very performant"}
768 [0.016170687973499298, -0.01129427831619978, -0.0005921399570070207, '...', 0.017849743366241455, 0.0003932560794055462, -0.00045805066474713385]
```

特殊列对 langchain 接口的其余部分完全透明。一切如常，只是性能更高。

```typescript theme={null}
const advancedFilter = { CUSTOMTEXT: { $like: "%value%" } };
const foundPerformantDocs = await dbPerformant.similaritySearch("What's up?", 2, advancedFilter);

foundPerformantDocs.forEach((doc) => {
    console.log("-".repeat(80));
    console.log(doc.pageContent);
});
```

```text theme={null}
--------------------------------------------------------------------------------
Some more text
--------------------------------------------------------------------------------
Some other text
```

## 简单示例

加载示例文档 "state\_of\_the\_union.txt" 并从中创建片段。

首先，安装 `@langchain/textsplitters` 包：

```bash theme={null}
npm install @langchain/textsplitters
```

```typescript theme={null}
import { TextLoader } from "@langchain/classic/document_loaders/fs/text";
import { CharacterTextSplitter } from "@langchain/textsplitters";

// Load documents from file
const loader = new TextLoader("./state_of_the_union.txt");
const textDocuments = await loader.load();
const textSplitter = new CharacterTextSplitter({
  chunkSize: 500,
  chunkOverlap: 0,
});
const textChunks = await textSplitter.splitDocuments(textDocuments);
console.log("Number of document chunks:", textChunks.length);
```

```text theme={null}
Number of document chunks: 88
```

将加载的文档片段添加到表中。对于此示例，我们删除表中可能存在的任何先前内容，这些内容可能来自之前的运行。

```typescript theme={null}
// Delete already existing documents from the table
await db.delete({ filter: {} });
// add the loaded document chunks
await db.addDocuments(textChunks);
```

执行查询以获取前两段最匹配的文档片段，这些片段是在上一步中添加的。
默认情况下，搜索使用 "Cosine Similarity"。

```typescript theme={null}
const query = "What did the president say about Ketanji Brown Jackson";
const docs = await db.similaritySearch(query, 2);
docs.forEach((doc) => {
  console.log("-".repeat(80));
  console.log(doc.pageContent);
});
```

```text theme={null}
--------------------------------------------------------------------------------
One of the most serious constitutional responsibilities a President has is nominating
someone to serve on the United States Supreme Court.

And I did that 4 days ago, when I nominated Circuit Court of Appeals Judge Ketanji Brown Jackson.
One of our nation’s top legal minds, who will continue Justice Breyer’s legacy of excellence.
--------------------------------------------------------------------------------
As I said last year, especially to our younger transgender Americans, I will always have your back as your President,
so you can be yourself and reach your God-given potential.

While it often appears that we never agree, that isn’t true. I signed 80 bipartisan bills into law last year.
From preventing government shutdowns to protecting Asian-Americans from still-too-common hate crimes to reforming military justice
```

使用 "Euclidean Distance" 查询相同内容。结果应与 "Cosine Similarity" 相同。

```typescript theme={null}
const argsL2d: HanaDBArgs = {
  connection: client,
  tableName: "STATE_OF_THE_UNION",
  distanceStrategy: "EUCLIDEAN",
};
const dbL2d = new HanaDB(embeddings, argsL2d);
await dbL2d.initialize();

const docsL2d = await dbL2d.similaritySearch(query, 2);
docsL2d.forEach((doc) => {
  console.log("-".repeat(80));
  console.log(doc.pageContent);
});
```

```text theme={null}
--------------------------------------------------------------------------------
One of the most serious constitutional responsibilities a President has is nominating
someone to serve on the United States Supreme Court.

And I did that 4 days ago, when I nominated Circuit Court of Appeals Judge Ketanji Brown Jackson.
One of our nation’s top legal minds, who will continue Justice Breyer’s legacy of excellence.
--------------------------------------------------------------------------------
As I said last year, especially to our younger transgender Americans, I will always have your back as your President,
so you can be yourself and reach your God-given potential.

While it often appears that we never agree, that isn’t true. I signed 80 bipartisan bills into law last year.
From preventing government shutdowns to protecting Asian-Americans from still-too-common hate crimes to reforming military justice
```

### 最大边际相关性搜索 (MMR)

`Maximal marginal relevance` 优化查询的相似性以及选定文档之间的多样性。前 20 个（fetch\_k）项目将从数据库中检索。然后 MMR 算法将找到最佳的 2 个（k）匹配项。

```typescript theme={null}
const docsMMR = await db.maxMarginalRelevanceSearch(query, {
  k: 2,
  fetchK: 20,
});
docsMMR.forEach((doc) => {
  console.log("-".repeat(80));
  console.log(doc.pageContent);
});
```

```text theme={null}
--------------------------------------------------------------------------------
One of the most serious constitutional responsibilities a President has is nominating someone
to serve on the United States Supreme Court.

And I did that 4 days ago, when I nominated Circuit Court of Appeals Judge Ketanji Brown Jackson.
One of our nation’s top legal minds, who will continue Justice Breyer’s legacy of excellence.
--------------------------------------------------------------------------------
Groups of citizens blocking tanks with their bodies. Everyone from students to retirees teachers turned
soldiers defending their homeland.

In this struggle as President Zelenskyy said in his speech to the European Parliament “Light will win over darkness.”
The Ukrainian Ambassador to the United States is here tonight.

Let each of us here tonight in this Chamber send an unmistakable signal to Ukraine and to the world.
```

### 创建 HNSW 向量索引

向量索引可以显著加快向量的 top-k 最近邻查询。用户可以使用 `createHnswIndex` 函数创建分层可导航小世界（HNSW）向量索引。

有关在数据库级别创建索引的更多信息，请参阅 [官方文档](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-vector-engine-guide/create-vector-index-statement-data-definition)。

```typescript theme={null}
// HanaDB instance uses cosine similarity as default
const argsCosine: HanaDBArgs = {
  connection: client,
  tableName: "STATE_OF_THE_UNION",
};

// Initialize both HanaDB instances
const dbCosine = new HanaDB(embeddings, argsCosine);
await dbCosine.initialize();

// Attempting to create the HNSW index with default parameters
await dbCosine.createHnswIndex(); // If no other parameters are specified, the default values will be used
// Default values: m=64, efConstruction=128, efSearch=200
// The default index name will be: STATE_OF_THE_UNION_COSINE_idx

// Second instance using the existing table "STATE_OF_THE_UNION" but with L2 Euclidean distance
const argsL2: HanaDBArgs = {
  connection: client,
  tableName: "STATE_OF_THE_UNION",
  distanceStrategy: "EUCLIDEAN", // Use Euclidean distance for this instance
};

const dbL2 = new HanaDB(embeddings, argsL2);
await dbL2.initialize();

// This will create an index based on L2 distance strategy.
await dbL2.createHnswIndex({
  indexName: "STATE_OF_THE_UNION_L2_index",
  efSearch: 400, // Max number of neighbors per graph node (valid range: 4 to 1000)
  m: 50, // Max number of candidates during graph construction (valid range: 1 to 100000)
  efConstruction: 150, // Min number of candidates during the search (valid range: 1 to 100000)
});

// Use L2 index to perform MMR
const docsL2HNSW = await dbL2.maxMarginalRelevanceSearch(query, {
  k: 2,
  fetchK: 20,
});
docsL2HNSW.forEach((doc) => {
  console.log("-".repeat(80));
  console.log(doc.pageContent);
});
```

```text theme={null}
--------------------------------------------------------------------------------
One of the most serious constitutional responsibilities a President has is nominating someone to serve on the United States Supreme Court.

And I did that 4 days ago, when I nominated Circuit Court of Appeals Judge Ketanji Brown Jackson. One of our nation’s top legal minds, who will continue Justice Breyer’s legacy of excellence.
--------------------------------------------------------------------------------
Groups of citizens blocking tanks with their bodies. Everyone from students to retirees teachers turned soldiers defending their homeland.

In this struggle as President Zelenskyy said in his speech to the European Parliament “Light will win over darkness.” The Ukrainian Ambassador to the United States is here tonight.

Let each of us here tonight in this Chamber send an unmistakable signal to Ukraine and to the world.
```

**要点**：

* **相似性函数**：索引的相似性函数默认为 **余弦相似性**。如果您想使用不同的相似性函数（例如，`L2` 距离），您需要在初始化 `HanaDB` 实例时指定它。
* **默认参数**：在 `createHnswIndex` 函数中，如果用户没有为 `m`、`efConstruction` 或 `efSearch` 等参数提供自定义值，将自动使用默认值（例如，`m=64`、`efConstruction=128`、`efSearch=200`）。这些值确保索引以合理的性能创建，而无需用户干预。

***

> 官方原文：[src/oss/javascript/integrations/vectorstores/sap\_hanavector.mdx](https://github.com/langchain-ai/docs/blob/main/src/oss/javascript/integrations/vectorstores/sap_hanavector.mdx) · 非官方翻译，译文由 AI 生成，以英文原文为准。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.