大语言模型(LLM)应用通常难以跨会话记住用户的关键信息,导致交互体验重复、缺乏个性化。这是因为模型的有效记忆窗口有限,对话历史在会话结束后即会丢失,并且处理长上下文会显著增加延迟和成本。Polar Agent Memory是部署于PolarDB for AI节点的长期记忆引擎,通过向量数据库与知识图谱双模存储,将历史对话中的关键信息持久化,并在后续会话中自动检索、更新和关联,使大模型具备跨会话的个性化记忆能力。
Polar Agent Memory功能目前处于灰度阶段。如需使用该功能或对当前功能有任何疑问,请提交工单联系我们为您处理。
功能简介
Polar Agent Memory将服务部署在PolarDB for AI节点上,通过内置的通用AI服务(大模型、Embedding模型等)和双模存储架构(向量数据库与知识图谱),为上层AI应用提供端到端的记忆管理能力。
相比将全量历史上下文塞入prompt的方案,Polar Agent Memory带来以下可量化改善:
响应时间降低30%。
Token消耗降低20%。
回答效果提升40%。
核心组件
Polar Agent Memory Manager:提供记忆管理的全生命周期服务,包括提取、存储、检索、更新与删除。
通用AI服务:内置私有化大模型服务,支持对话切片与摘要、实体/关系抽取、文本向量化(Embedding)和重排序(Reranker)等能力。
记忆存储:采用双模存储架构,结合向量数据库进行语义相似度搜索(ANN),并利用知识图谱引擎存储和推理实体与事件间的复杂关系。例如:
存储
<实体1,实体2,关系>实体关系三元组,该三元组被定义为一个事件。存储
<事件1,事件2,关系>事件关系三元组。
核心能力
记忆提取与融合
从对话中动态抽取关键信息,将其向量化并构建为知识图谱,最终存入数据库。
动态知识抽取:对长对话进行切片,通过LLM提示工程(Prompt Engineering)提取用户明确表达的事实、偏好或需求,并将这些记忆片段构建为知识图谱的节点。
向量化与存储:将记忆片段进行语义向量化以支持模糊匹配,同时提取关键词用于精确过滤。处理后的记忆将存储至向量数据库和图数据库,并建立索引。
记忆融合:新提取的记忆会暂存以待验证。随后,系统会将临时记忆与持久记忆进行融合,更新已有的记忆内容。
记忆检索
结合语义向量与关键词进行多路检索,并通过知识图谱发现隐式关联,最后使用Rerank模型对结果重排序,确保返回最相关的记忆。
查询处理
将用户的当前查询(Query)使用Embedding模型生成语义向量。
并识别查询中的时间、地点、人物等结构化要素。
多路召回
知识图谱检索(精确检索)
使用向量化后的查询捕获语义相近记忆。
根据LLM提取关键词检索到关键的记忆片段。
采用知识图谱记忆连接关系,发现隐式关联记忆。
事件关系检索(快速检索)
使用向量化后的查询捕获语义相近的记忆。
根据查询检索到关键实体以及对应事件语境。
采用知识图谱记忆连接关系,发现事件联系,例如时序、因果、假设等。
结果处理
多路召回结果经Rerank重排序模型打分。
最终返回Top-K个最相关的记忆条目,并附带记忆来源的对话原文和时间戳。
记忆源管理
当删除某次对话记录时,服务会自动触发关联记忆的失效或重建,保证记忆与源信息的一致性。
删除对话:当删除某次聊天记录时,系统会自动触发关联记忆的失效或删除。
重建记忆:如果一条记忆由多个对话来源构成,在删除其中一个来源后,系统会利用剩余的来源信息对该条记忆进行重建。
功能优势
个性化交互:通过记住用户的历史偏好、个人信息和行为模式,使AI应用(如智能客服、个人AI导师)能够提供高度定制化的回应和建议。
提升业务效率:在企业知识库等场景中,通过记忆员工角色、项目背景等信息,自动关联相关文档和联系人,减少信息检索成本。
增强临床辅助:在医疗健康场景中,能够安全地存储和调用患者的用药史、过敏信息等,为个性化健康建议和预警提供支持。
适用范围
添加至少一个GPU规格的AI节点,并设置AI节点的连接数据库账号:开启PolarDB for AI功能
说明若您在购买集群时已添加AI节点,则可以直接为AI节点设置连接数据库的账号。
AI节点的连接数据库账号需具有读写权限,以确保能够顺利读取和写入目标数据库。
使用集群地址连接PolarDB集群:登录PolarDB for AI
重要使用命令行连接集群时,需增加
-c选项。在使用DMS体验和使用PolarDB for AI功能时,DMS默认使用PolarDB集群的主地址进行连接,无法将SQL语句路由至AI节点。因此,您需要手动将连接地址修改为集群地址【推荐】。
步骤一:部署服务
连接到PolarDB集群,并执行SQL命令来部署服务。
MODEL `polar-agent-memory`:固定关键字,表示部署PolarDB AgentMem服务。deployment_name:为服务自定义部署名称,用于区分多个服务,且不可重复。默认为polar-agent-memory。
/*polar4ai*/DEPLOY MODEL `polar-agent-memory` WITH (
deployment_name = 'my_memory_service'
);步骤二:查看服务状态并获取访问凭证
服务部署需要一些时间,执行以下命令查询部署状态。请将my_memory_service替换为您在上一步中设置的名称。
/*polar4ai*/SHOW DEPLOYMENT `my_memory_service`;当返回结果中的deployment_status字段显示为Serving时,表示服务已准备就绪。请记录下返回的request_path(服务访问地址)和api_key(访问凭证),后续步骤将使用它们。
字段 | 说明 | 示例 |
| 服务状态。
|
|
| 服务的公网访问地址。 |
|
| 服务的访问凭证。 |
|
步骤三:通过HTTP接口访问服务
所有API的请求头都需要包含Authorization和Content-Type。
Header | 说明 | 示例 |
| 服务的访问凭证。需以 |
|
| 请求体格式。 |
|
添加记忆
POST /polar-amem/message/add
该接口用于将一段或多段对话添加到指定会话的记忆中。服务会异步处理这些对话,从中抽取并形成记忆。
请求体
参数 | 类型 | 是否必须 | 描述 |
| string | 是 | 会话的唯一标识符,用于隔离不同对话的记忆。 |
| int | 否 | 每单次抽取记忆的对话数量。默认为10。 |
| array<object> | 是 | 对话消息列表。 |
| string | 是 | 消息发送者的角色,需为 |
| string | 是 | 消息的具体内容。 |
| string | 是 | 消息发送时间的毫秒级时间戳。例如,通过Python的 |
| int | 是 | 对话的轮次,在会话内应保持递增。 |
| string | 是 | 单轮对话的唯一业务ID,可用于后续的精确删除。 |
请求示例
{
"session_id": "session-001",
"messages": [
{
"role": "user",
"content": "你好,我叫小云。刚搬到这个城市。",
"turn": 1,
"business_id": "ee2850b8-xxxx-46be-8580-d5b9dbdf7d37",
"timestamp": "1772436673823"
},
{
"role": "assistant",
"content": "欢迎你,小云!新环境总是充满挑战和机遇,希望你能在这里找到归属感。需要我帮你规划一些生活或工作上的建议吗?",
"turn": 2,
"business_id": "d277be83-xxxx-4896-bea5-1cb89ea2190c",
"timestamp": "1772436673823"
},
{
"role": "user",
"content": "我目前在'阿里云'担任初级软件工程师。",
"turn": 3,
"business_id": "5da7165a-xxxx-4663-9506-3026dafaad23",
"timestamp": "1772436673823"
},
{
"role": "assistant",
"content": "听起来你已经开始了自己的职业生涯了!作为初级工程师,有什么让你感到特别有成就感或者挑战性的工作内容吗?",
"turn": 4,
"business_id": "b35565b8-xxxx-4554-a80c-5a078c4724d1",
"timestamp": "1772436673823"
}
]
}返回体
参数 | 类型 | 描述 |
| int | 状态码。 |
| string | 状态信息。 |
| string | 请求ID。 |
| object | 结果。 |
{
"code": 200,
"data": null,
"message": "",
"request_id": "4e242075f2801625fe79xxxx"
}
查询记忆
POST /polar-amem/message/search
根据当前的查询,从指定会话中检索最相关的记忆。
请求体
参数 | 类型 | 是否必须 | 描述 |
| string | 是 | 会话的唯一标识符,用于隔离不同对话的记忆。 |
| string | 是 | 用于搜索记忆的查询文本,通常是最新提问。 |
| int | 否 | 返回最相关记忆的最大数量。默认为3。 |
| string | 否 | 查询方式:
|
请求示例
{
"session_id": "session-001",
"query": "我在哪里上班?",
"top_k": 1,
"mode": "fast"
}返回体
API行为警告:
event_memories和event_relations字段中使用了中文作为JSON键名(例如事件触发词)。这不符合标准REST API设计规范,可能导致在使用强类型语言(如Java、Go或C#)进行JSON反序列化时出现问题。处理建议:在Python等动态语言中,您可以直接通过中文字符串访问这些键:在强类型语言中,您可能需要使用支持自定义字段名映射的 JSON库(如Jackson的
@JsonProperty,Gson的@SerializedName)或将响应作为通用字典/Map类型处理。
参数 | 类型 | 描述 |
| int | 状态码。 |
| string | 状态信息。 |
| string | 请求ID。 |
| object | 结果。 |
| array | 文本记忆列表。这是最主要的记忆内容。 |
| array | 具体的记忆条目列表。 |
| string | 对一段或多段原始对话的总结性记忆陈述。
|
| object | 记忆的元数据,包含来源、置信度等。 |
| string | 记忆类型
|
| float | 记忆来源的置信度。 |
| string | 来源方式。 |
| array | 构成该记忆的原始对话片段列表。 |
| string | 消息发送者的角色, |
| string | 消息的具体内容。 |
| string | 对话的轮次,在会话内应保持递增。 |
| string | 单轮对话的唯一业务ID,可用于后续的精确删除。 |
| string | 以user视角归纳的总结。 |
| float | 语义相似度。 |
| array<array<object>> | 事件。 |
| int | 事件ID。 |
| string | 事件谓语。 |
| string | 事件主语。 |
| string | 事件宾语。 |
| array<object> | 事件关系,描述事件之间的时序或因果关系。 |
| string | 时序或因果。 |
| array | 事件关系详情。 |
| int | 与 |
| string |
|
{
"code": 200,
"message": "",
"request_id": "a8842dc6f90b6836688xxxx"
"data": {
"text_mem": [
{
"cube_id": "session-001",
"memories": [
{
"id": "f1b96342-xxx-45e2-a5bc-d28caef2240d",
"memory": "[assistant观点]助手在2026年3月2日7:31:13欢迎小云来到新城市,询问其职业生涯的成就感或挑战性工作内容。",
"metadata": {
"confidence": 0.99,
"user_id": "session-001",
"session_id": "ae6f3c9b-xxx-415a-b62a-69190aa0c35a",
"memory_type": "WorkingMemory",
"type": "fact",
"created_at": "2026-03-02T07:31:21.027105",
"updated_at": "2026-03-02T07:31:21.028140",
"tags": [
"欢迎",
"职业询问",
"互动"
],
"background": "小云于2026年3月2日刚搬到新城市,目前在阿里云担任初级软件工程师。助手当日7:31:13向她表示欢迎,并询问其工作中的成就感或挑战。",
"relativity": 0.0,
"embedding": [
0.00958251953125,
-0.0274658203125,
...
],
"key": "助手欢迎信息",
"usage": [
"{\"time\": \"2026-03-02T07:32:47.147367\", \"info\": {\"user_id\": \"session-001\", \"session_id\": \"3e0ea9d2-xxxx-4289-956f-6c3fb0d5e45a\"}}",
"{\"time\": \"2026-03-02T07:34:19.927708\", \"info\": {\"user_id\": \"session-001\", \"session_id\": \"19b99ab1-xxxx-474a-995c-783f2f445891\"}}"
],
"source": null,
"sources": [
{
"role": "user",
"chat_time": "2026-03-02 07:31:13",
"doc_path": null,
"message_id": "ee2850b8-xxxx-46be-8580-d5b9dbdf7d37",
"turn": 1,
"type": "chat",
"business_id": "ee2850b8-xxxx-46be-8580-d5b9dbdf7d37",
"content": "你好,我叫小云。刚搬到这个城市。"
},
{
"role": "assistant",
"chat_time": "2026-03-02 07:31:13",
"doc_path": null,
"message_id": "d277be83-xxxx-4896-bea5-1cb89ea2190c",
"turn": 2,
"type": "chat",
"business_id": "d277be83-xxxx-4896-bea5-1cb89ea2190c",
"content": "欢迎你,小云!新环境总是充满挑战和机遇,希望你能在这里找到归属感。需要我帮你规划一些生活或工作上的建议吗?"
},
{
"role": "user",
"chat_time": "2026-03-02 07:31:13",
"doc_path": null,
"message_id": "5da7165a-xxxx-4663-9506-3026dafaad23",
"turn": 3,
"type": "chat",
"business_id": "5da7165a-xxxx-4663-9506-3026dafaad23",
"content": "我目前在'阿里云'担任初级软件工程师。"
},
{
"role": "assistant",
"chat_time": "2026-03-02 07:31:13",
"doc_path": null,
"message_id": "b35565b8-xxxx-4554-a80c-5a078c4724d1",
"turn": 4,
"type": "chat",
"business_id": "b35565b8-xxxx-4554-a80c-5a078c4724d1",
"content": "听起来你已经开始了自己的职业生涯了!作为初级工程师,有什么让你感到特别有成就感或者挑战性的工作内容吗?"
}
]
}
}
],
"event_relations": [],
"event_memories": []
}
],
"act_mem": [],
"para_mem": []
}
}
删除记忆
POST /polar-amem/message/delete
根据对话轮次(turn)或业务ID(business_id),从指定会话中删除相关的记忆。当组成一条记忆的某个原始对话被删除后,服务会自动使用剩余的原始对话重建该记忆。
请求体
参数 | 类型 | 是否必须 | 描述 |
| string | 是 | 会话的唯一标识符。 |
| array<int> | 否 | 要删除的对话轮次列表。 |
| array<string> | 否 | 要删除的对话业务ID列表。 |
请求示例
{
"session_id": "session-001",
"business_id": [
"22cb460a-xxxx-45f9-a115-fe6d203ba3e1",
"c5a0d26b-xxxx-4249-b23f-33cf016f37d5"
]
}返回体
参数 | 类型 | 描述 |
| int | 状态码。 |
| string | 状态信息。 |
| string | 请求ID。 |
| object | 结果。 |
{
"code": 200,
"data": null,
"message": "",
"request_id": "fc56e76269bc0fbfcxxxx"
}
批量异步添加记忆
POST /polar-amem/message/batch-add
用于一次性提交大量跨会话的对话数据,适用于历史数据导入等场景。这是一个异步接口,会立即返回一个任务ID。
请求体
参数 | 类型 | 是否必须 | 描述 |
| array<object> | 是 | 批量任务的数据列表,每个对象代表一个独立的添加请求。 |
| string | 是 | 会话的唯一标识符。 |
| int | 否 | 每单次抽取记忆的对话数量。默认为10。 |
| array<object> | 是 | 对话消息列表,结构同 |
请求示例
{
"data": [
{
"session_id": "user-session-001",
"messages": [
{
"role": "user",
"content": "我喜欢吃香蕉",
"turn": 1,
"business_id": "289d0bd9-xxxx-445d-8951-775604d4fe1b",
"timestamp": "1772437402969"
},
{
"role": "assistant",
"content": "我也喜欢吃香蕉",
"turn": 2,
"business_id": "c8452aa2-xxxx-461f-a382-b71697e2cfc4",
"timestamp": "1772437402969"
}
]
},
{
"session_id": "user-session-002",
"messages": [
{
"role": "user",
"content": "我喜欢吃香肠",
"turn": 1,
"business_id": "fc6c8411-xxxx-4836-a989-fc3b75f420a9",
"timestamp": "1772437402969"
},
{
"role": "assistant",
"content": "我喜欢吃腊肠",
"turn": 2,
"business_id": "fc494fa2-xxxx-4fe0-9528-adf9863c82ff",
"timestamp": "1772437402969"
}
]
}
]
}返回体
参数 | 类型 | 描述 |
| int | 状态码。 |
| string | 状态信息。 |
| string | 请求ID。 |
| object | 结果。 |
| string | 异步任务的唯一 ID,可用于后续查询任务状态。 |
{
"code": 200,
"data": {
"task_id": "2291d693-xxxx-413a-b9c5-7da22487f45c"
},
"message": "",
"request_id": "0ca08e757b52181xxxx"
}
查询批量任务状态
POST /polar-amem/message/query-task
查询批量添加记忆任务的执行状态。
请求体
参数 | 类型 | 是否必须 | 描述 |
| string | 是 |
|
请求示例
{
"task_id": "2291d693-xxxx-413a-b9c5-7da22487f45c"
}返回体
参数 | 类型 | 描述 |
| int | 状态码。 |
| string | 状态信息。 |
| string | 请求ID。 |
| object | 结果。 |
| string | 任务ID,此字段即为请求中的 |
| string | 任务状态。
|
| int | 任务包含的总记录数。 |
| int | 已成功处理的记录数。 |
| int | 处理失败的记录数。 |
| datetime | 任务创建时间。 |
| datetime | 任务完成时间。 |
{
"code": 200,
"data": {
"created_time": "2026-03-02T07:43:23.167761",
"batch_id": "2291d693-xxxx-413a-b9c5-7da22487f45c",
"total_count": 2,
"success_count": 2,
"failed_count": 0,
"finish_time": "2026-03-02T07:43:34.561627",
"status": "SUCCESS"
},
"message": "",
"request_id": "a5c408e3ce932dedxxxx"
}
结合记忆进行对话
POST /polar-amem/chat
将历史对话、检索到的相关记忆以及系统提示词(System Prompt)结合在一起,请求LLM生成最终回复。
请求体
参数 | 类型 | 是否必须 | 描述 |
| array<object> | 是 | 近期的历史对话列表,格式遵循OpenAI |
| object | 是 | 从 |
| string | 否 | 对助手的角色、语气和行为进行设定的系统提示词。 |
返回体
参数 | 类型 | 描述 |
| int | 状态码。 |
| string | 状态信息。 |
| string | 请求ID。 |
| object | 结果。 |
调用流程示例
假设已有如下对话历史,并已通过 message/add 接口存入记忆库:
messages = [
{
"role": "user",
"content": "最近感觉很焦虑,不知道如何缓解。",
"timestamp": "1767615466347"
},
{
"role": "assistant",
"content": "我明白你的感受。首先,要知道焦虑是一种正常的情绪。但是,当它影响到你的生活质量时,就需要采取行动了。",
"timestamp": "1767615467337"
}
]当发起新一轮提问:“可是我总觉得自己做什么都不够好,很害怕失败。”,完整的调用逻辑如下:
查询记忆:使用新提问作为
query,调用/polar-amem/message/search接口获取相关记忆。// Request to /polar-amem/message/search { "session_id": "your_session_id", "query": "可是我总觉得自己做什么都不够好,很害怕失败。" }假设返回的记忆数据为
retrieved_memories。您可以根据业务需求对返回的记忆进行过滤或修改。// 返回的记忆retrieved_memories,可根据业务需求进行自定义调整 memories = { "text_mem": [ { "cube_id": "session-001", "memories": [ { "id": "f1b96342-35a1-45e2-a5bc-d28caef2240d", "memory": "[assistant观点]助手在2026年3月2日7:31:13欢迎小云来到新城市,询问其职业生涯的成就感或挑战性工作内容。", "metadata": { ... } } ] } ] }执行对话:调用
/polar-amem/chat接口,将系统提示词、查询到的记忆和对话历史(包含最新的提问)一起发送给大模型。// Request to /polar-amem/chat { "memories": retrieved_memories, // 上一步获取的记忆。 "messages": [ { "role": "user", "content": "最近感觉很焦虑,不知道如何缓解。" }, { "role": "assistant", "content": "我明白你的感受。首先,要知道焦虑是一种正常的情绪。但是,当它影响到你的生活质量时,就需要采取行动了。" }, { "role": "user", "content": "可是我总觉得自己做什么都不够好,很害怕失败。" // 最新的提问 } ], "system_message": "你是一位富有同理心的个人陪伴聊天机器人。" }获取回复:接口将返回大模型结合了长期记忆和短期上下文生成的回复。
{ "code": 200, "message": "", "data": "听起来你正在经历一些困难,这种感受是很真实的。你已经尝试了方法,这本身就很不容易,也说明你愿意为自己努力。如果这些方法暂时没有带来明显改善,可能需要调整方向,或者尝试一些新的方式。\n\n有时候,我们可能需要更长时间才能看到变化,或者需要一些不同的方法来应对。比如,可以试着把目标分解得更小一些,每天只专注于一点点,而不是一下子要求自己“好起来”。同时,也可以尝试一些新的放松方式,比如深呼吸、冥想、散步,或者听一些舒缓的音乐。\n\n如果你愿意,也可以尝试写下来自己的感受,或者找一个你信任的人聊聊。有时候,把情绪表达出来,本身就是一种释放。如果情况持续让你感到困扰,寻求专业帮助也是一个非常勇敢和有效的选择。\n\n你并不孤单,我在这里支持你。如果有什么想说的,或者需要更多的建议,随时告诉我。", "request_id": "13f5c70174e74eebxxxx" }
演示示例:构建并运行记忆机器人
以下Python代码演示了如何构建一个简单的、具备记忆功能的情感陪伴机器人。
准备代码: 将以下代码保存为
memory_agent.py文件。该代码实现了一个循环,接收用户输入,先搜索相关记忆,然后结合记忆和当前对话生成回复,最后将新一轮的对话存入记忆库。import requests import os import time import uuid URL = f'http://{服务的公网访问地址}' HEADERS = { "Authorization": "Bearer " + os.getenv("API_KEY", "{服务的访问凭证}"), "Content-Type": "application/json" } SESSION_ID = str(uuid.uuid4()) SYSTEM_PROMPT = """你是一位个人陪伴聊天机器人。""" history_messages = [] current_turn = 1 while True: # 1. 获取输入 input_content = input("user> ") history_messages.append({ "turn": current_turn, "business_id": str(uuid.uuid4()), "role": "user", "content": input_content, "timestamp": str(int(time.time() * 1000)) }) current_turn += 1 # 2. 查询 resp = requests.post(f"{URL}/polar-amem/message/search", headers=HEADERS, json={ "query": input_content, "session_id": SESSION_ID }) memories = resp.json()["data"] # 此处您可以根据业务需要过滤/修改记忆 # 3. 对话 resp = requests.post(f"{URL}/polar-amem/chat", headers=HEADERS, json={ # 上下文对话数量自定义 "messages": history_messages[-5:], "session_id": SESSION_ID, "memories": memories, "system_message": SYSTEM_PROMPT }) print("assistant> ", resp.json()["data"]) # 4. 将刚完成的一轮对话加入到agent mem中 history_messages.append({ "turn": current_turn, "business_id": str(uuid.uuid4()), "role": "assistant", "content": resp.json()["data"], "timestamp": str(int(time.time() * 1000)) }) current_turn += 1 resp = requests.post(f"{URL}/polar-amem/message/add", headers=HEADERS, json={ # 记忆源数量自定义 "messages": history_messages[-4:], "session_id": SESSION_ID })运行并验证: 在终端中运行脚本。
python memory_agent.py尝试进行以下对话,以验证记忆是否生效:
第一轮对话(存入记忆):
user> 你好,我叫张伟,最近因为工作压力很大,感到很焦虑。 assistant> 张伟,听起来你最近真的挺辛苦的。工作压力确实会让人感到焦虑,尤其是当事情堆积的时候。我明白这种感觉,有时候连呼吸都会变得急促。你有没有尝试过一些放松的方法?比如深呼吸或者短暂地离开工作环境走走?有时候小小的调整反而能带来意想不到的缓解。如果愿意的话,可以和我聊聊具体是什么让你感到压力吗?也许我们可以一起想想办法。第二轮对话(验证记忆):
user> 你知道我叫什么名字吗?我最近感觉怎么样? assistant> 当然记得,你叫张伟。最近你提到工作压力让你感到焦虑,甚至有些呼吸急促,这确实不容易。我注意到你一直在努力应对,但压力可能让你有些疲惫。如果需要,我们可以一起聊聊具体的事,或者试试其他放松的方法。你现在的状态怎么样?有没有什么特别想聊的?
如果机器人能够正确回答出您的名字和状态,说明Polar Agent Memory已成功运行。

数据安全
数据隔离:您的所有记忆数据(包括原始对话和提取的结构化记忆)都存储在您自己的PolarDB集群内,实现物理隔离,确保数据隐私。
计算隔离:所有的AI计算(如向量化、摘要、推理)都在您指定的AI节点上完成。数据不会被发送到任何外部公共服务或第三方模型提供商。