Langchain 消息与提示词模板

1.认识消息

大模型没有记忆,它的输出只和模型输入有关;很多大模型 API 服务也没有在服务端维护会话历史,是 "无状态"的;因此,如果应用需要"记住"会话历史,就需要在程序中维护消息列表

在 Langchain 中,Message(消息)是模型交互的基础单元

注意:

  • 每一轮与大模型的对话,都由一条或多条 Message 构成
  • 每个 Message 不仅包含文字内容(content) ,还携带描述上下文状态的元信息(metadata) ,用于保持对话的一致性和可追踪性。比如,模型在多轮交互中理解"谁在说话"、"说了什么"、"这条信息属于哪一轮对话"

LangChain 在 1.0 中提供了**跨模型统一的 Message 标准;**无论使用的是 OpenAI、Anthropic、Gemini 还是本地模型,这一标准都能保持一致的行为。好处:

  • 兼容性强:不同模型的消息格式自动对齐。
  • 可扩展性高:方便添加多模态内容或自定义字段。
  • 可追踪性好:为 LangSmith 等调试工具提供一致的上下文数据结构。

1.1 消息的结构

LangChain 的消息(Message)对象包含三种字段:

  • Role:消息所属的角色或类型,有四类角色: system、user、assistant,tool
  • Content:消息的文本内容
  • Metadata:(可选)元数据,存储额外信息。如:消息 ID、响应时间、token 消耗量、消息标签等

1.2 消息的类型

LangChain 的消息类型通过 role 区分,常用的有四种:

1、系统消息

系统提示词,用于在对话开始时为模型设定角色、行为准则和上下文背景

复制代码
{"role": "system", "content": "你是个精通编程的软件架构师"}

2、用户消息

用户提示词,在多轮对话中,它**表示用户的一次输入;**可以包含简单的文本问题,也可以是复杂的多模态内容(如图片、音频、文档等)

复制代码
{"role": "user", "content": "你好啊~"}

3、助手 (AI) 消息

模型的回复,包括生成的文本、工具调用、元数据等

复制代码
{"role": "assistant", "content": "我也很高兴认识你"}

4、工具调用信息

工具调用结果匹配的消息类型。将此消息返回给模型,让模型基于这个结果继续生成回复。

复制代码
{"role": "tool", "content": "今天天气很好", "tool_call_id":
"call_00_nUD2NC9QRN5Cg1GaoIkBJQ4s"}

1.3 消息的格式

LangChain支持两种消息格式

格式1:JSON格式

复制代码
{"role": "system", "content": "你是个善解人意的助手",
 "role": "user", "content": "你好啊~",
 "role": "assistant", "content": "我也很高兴认识你",
 "role": "tool", "content": "<工具输出>", "tool_call_id":
 "call_00_nUD2NC9QRN5Cg1GaoIkBJQ4s"
}

格式2:对象格式

复制代码
SystemMessage(content="你是个善解人意的助手")

HumanMessage(content="你好啊~")

AIMessage("我也很高兴认识你")

ToolMessage(
content="<工具输出>",
tool_call_id="call_00_nUD2NC9QRN5Cg1GaoIkBJQ4s" # 一定要和AI消息中的调用ID匹
配
)

小结:

1.4 消息对象字段说明

1.4.1 SystemMessage

content:消息内容,字段名可省略

复制代码
SystemMessage("你好")
SystemMessage(content = "你好")

1.4.2 HumanMessage

content:消息内容,字段名可省略

metadata:元数据字段,不限数量,自定义

复制代码
HumanMessage(
    content="你好",
    name="朱丽叶",
    id="985"
)

name 和 id 都属于元数据字段,当消息类型相同,对消息进行区分。但不是所有模型都支持这一功 能,是否支持取决于模型供应商

1.4.3 AIMessage

content:消息内容,字段名可省略

response_metadata :AIMessage 特有属性,LLM的响应中附加元数据,根据不同模型会有不同,如 可能会包含本次token使用量等信息

tool_calls :AIMessage特有属性,表示**工具调用信息;**当LLM决定调用工具时,在 AIMessage 中就会包含这个属性,没有工具调用则为空,结构如下:

复制代码
tool_calls=[
    {
        'name': 'get_weather', // 应调用的工具名
        'args': {'city': '杭州'}, // 调用工具的参数
        'id': 'call_00_gIXYOD1Q1OkEXmdDBqXR1578', // 工具调用的唯一标识ID
        'type': 'tool_call'
    },
    {
        'name': 'get_news',
        'args': {},
        'id': 'call_01_jD3phD5PEaIZf0mVLhKt0861',
        'type': 'tool_call'
    }
]

注意:tool_calls 属性是一个 ToolCall 列表,每个 ToolCall 是一个字典,其包含字段如上

usage_metadata:用量信息

1.4.4 ToolMessage

content:消息内容,字段名可省略

name:工具名称

tool_call_id :工具调用唯一 ID,ToolMessage 必须紧邻匹配的 AIMessage,和前者 tool_calls 中的 id 一致

复制代码
ToolMessage(
    content="<工具输出>",
    name="get_weather"
    tool_call_id="call_00_nUD2NC9QRN5Cg1GaoIkBJQ4s" # 一定要和AI消息中的调用ID匹配
)

1.5 消息属性:content、content_blocks

1.5.1 content

消息的content 可以理解为数据内容,它是弱类型,支持字符串和列表(列表元素通常为字典)

注意:

  • 如果只是纯文本内容,直接传递字符串就可以
  • 当 content 内容只有字符串时,可以省略参数名称

如果需要发送多模态内容,则需要 content 的字典列表形式

案例:

复制代码
import base64
from langchain.chat_models import init_chat_model
from langchain.messages import SystemMessage, HumanMessage, AIMessage
from dotenv import load_dotenv
import os

# 将本地图片转换为base64编码的Data URI字符串
def encode_image(img_path, img_type='jpeg'):
    """
    将一张本地图片转换成 Base64 编码的 Data URI 字符串,方便在文本中嵌入图片数据
    """
    with open(img_path, "rb") as f:
        return f"data:image/{img_type};base64,{base64.b64encode(f.read()).decode('utf-8')}"

# 1.加载环境变量
load_dotenv(override=True)
api_key=os.getenv("BAILIAN_API_KEY")
base_url=os.getenv("BAILIAN_API_BASE")

# 2.模型初始化
llm = init_chat_model(
    model_provider="openai",  # 模型提供商
    model="deepseek-v4.1-flash",  # 模型名称
    api_key=api_key,
    base_url=base_url
)

# 3.获取图像base64编码字符串
# 3.1 图像路径
img_path = "./Data/博客封面.jpg"
# 3.2 获取图像base64编码字符串
base64_image = encode_image(img_path, img_type='jpeg')

# 4.调用模型
response = llm.invoke([
    HumanMessage(
        content=[
            {
                'type': 'text', 
                'text': '这张图里有什么?'},
            {
                'type': 'image_url',
                "image_url": base64_image,
            }
        ]
    )
])

print(response.content)

1.7.2 content_blocks

在 LangChain 1.2 版本中,新增了 content_blocks属性,可以将 content 解析为标准、类型安全的表示

  • 数据结构 :它是一个 list[TypedDict]

  • 统一格式 :每个 block 都有一个 type 字段,用于区分内容类型

  • 支持类型 :包括 text(文本)、image(图片)、audio(音频)、video(视频)、tool_call(工具调用)以及 reasoning(推理 / 思维链)

支持的字段类型详见: Messages - Docs by LangChain

① 输入格式化

对于复杂的对话(带图片或工具结果),建议使用 content_blocks 列表形式构建 HumanMessage 或 AIMessage

案例:

复制代码
import base64
from langchain.chat_models import init_chat_model
from langchain.messages import SystemMessage, HumanMessage, AIMessage
from dotenv import load_dotenv
import os

# 将本地图片转换为base64编码的Data URI字符串
def encode_image(img_path, img_type='jpeg'):
    """
    将一张本地图片转换成 Base64 编码的 Data URI 字符串,方便在文本中嵌入图片数据
    """
    with open(img_path, "rb") as f:
        return base64.b64encode(f.read()).decode("utf-8")

# 1.加载环境变量
load_dotenv(override=True)
api_key=os.getenv("BAILIAN_API_KEY")
base_url=os.getenv("BAILIAN_API_BASE")

# 2.模型初始化
llm = init_chat_model(
    model_provider="openai",  # 模型提供商
    model="deepseek-v4.1-flash",  # 模型名称
    api_key=api_key,
    base_url=base_url
)

# 3.获取图像base64编码字符串
# 3.1 图像路径
img_path = "./Data/博客封面.jpg"
# 3.2 获取图像base64编码字符串
base64_image = encode_image(img_path, img_type='jpeg')

# 4.调用模型
response = llm.invoke([
    HumanMessage(
        content_blocks=[
            {
                'type': 'text', 
                'text': '这张图里有什么?'},
            {
                'type': 'image',
                'base64': base64_image,
                'mime_type': 'image/jpeg'
            }
        ]
    )
])

print(response.content)

② 输出格式化

content_blocks 提供了统一的输出格式 ,可以将不同格式的响应统一为标准格式

输出:

注意:content_blocks是 懒加载 的,即调用时才会解析

2. 提示词模板

使用 LangChain 提供 ChatPromptTemplate 类

案例:

复制代码
from langchain.chat_models import init_chat_model
from dotenv import load_dotenv
import os
from rich import print as rprint
from langchain_core.prompts import ChatPromptTemplate

# 1.加载配置信息
load_dotenv(override=True)  # 加载.env文件中的环境变量
api_key = os.getenv("DEEPSEEK_API_KEY")  # 获取DEEPSEEK_API_KEY环境变量
base_url = os.getenv("DEEPSEEK_API_BASE")  # 获取DEEPSEEK_API_BASE环境  

# 2.初始化模型
llm = init_chat_model(
    model_provider="openai",  # 模型提供商
    model="deepseek-flash",  # 模型名称
    api_key=api_key,  # API Key
    base_url=base_url,  # API Base URL
)

# 3.调用模型
prompt_template = ChatPromptTemplate([
("system", "你是一个AI开发工程师. 你的名字是 {name}."),
("human", "{user_input}")
])
#调用format()方法,返回字符串
prompt = prompt_template.invoke({"name":"小谷AI", "user_input":"你能帮我做什么?"})
rprint(prompt)

结果如下:

2.1 ChatPromptTemplate 的使用

ChatPromptTemplate 是创建消息列表的提示模板,支持 System / Human / AI 等不同角色的消息模板

2.1.1 两种实例化方式

ChatPromptTemplate 可以通过初始化方法 或 from_messages 方法来实例化提示词模板,实例化 时需要传入 messages 参数。常见类型是:tuple构成的列表,参数类型(role : str,content : str )

方式1(推荐):调用 from_messages()

该方法允许传入一个由元组(Tuple)构成的列表,列表中的每一个元组都代表一条具有特定角色的消息

案例:

复制代码
# 导入相关依赖
from langchain_core.prompts import ChatPromptTemplate
# 定义聊天提示词模版
chat_template = ChatPromptTemplate.from_messages(
[
    ("system", "你是一个有帮助的AI机器人,你的名字是{name}。"),
    ("human", "你好,最近怎么样?"),
    ("ai", "我很好,谢谢!"),
    ("human", "{user_input}"),
]
)
# 格式化聊天提示词模版中的变量
prompt = chat_template.invoke({"name":"小明", "user_input":"你叫什么名字?"})
# 打印格式化后的聊天提示词模版内容
print(prompt)

注意:from_messages()的底层,也是调用的类的 __init( )__方法

方式2:使用实例初始化方法

复制代码
from langchain_core.prompts import ChatPromptTemplate
#参数类型这里使用的是tuple构成的list
prompt_template = ChatPromptTemplate([
# 字符串 role + 字符串 content
    ("system", "你是一个AI开发工程师. 你的名字是 {name}."),
    ("human", "你能开发哪些AI应用?"),
    ("ai", "我能开发很多AI应用, 比如聊天机器人, 图像识别, 自然语言处理等."),
    ("human", "{user_input}")
])
#调用invoke()方法,返回ChatPromptValue
prompt = prompt_template.invoke({"name":"小谷AI", "user_input":"你能帮我做什么?"})
print(prompt)

2.1.2 模型调用的三种方式

方式1:使用 invoke()

返回 ChatPromptValue

方式2:使用 format()

返回字符串

注意:format()要使用关键字传参

方式3:使用 format_messages()

返回消息列表

2.1.3 案例

复制代码
from urllib import response

from langchain.chat_models import init_chat_model
from dotenv import load_dotenv
import os
from rich import print as rprint
from langchain_core.prompts import ChatPromptTemplate

# 1.加载配置信息
load_dotenv(override=True)  # 加载.env文件中的环境变量
api_key = os.getenv("BAILIAN_API_KEY")  # 获取BAILIAN_API_KEY环境变量
base_url = os.getenv("BAILIAN_API_BASE")  # 获取BAILIAN_API_BASE环境  

# 2.初始化模型
llm = init_chat_model(
    model_provider="openai",  # 模型提供商
    model="deepseek-v4.1-flash",  # 模型名称
    api_key=api_key,  # API Key
    base_url=base_url,  # API Base URL
)

# 3.调用模型
# 3.1 定义聊天提示词模版
chat_template = ChatPromptTemplate([
    ("system", "你是一个精通Python编程与AI的机器人,你的名字是{name}"),
    ("human", "{user_input}")
])
prompt = chat_template.format(name="小彤AI", user_input="你能帮我做什么?")

# 3.2 调用模型
response = llm.invoke("什么是面向对象编程?")
print(response.content)

2.1.4 更丰富的初始化参数类型

不管使用实例初始化方法,还是使用 from_messages(),参数类型都是列表类型 ,列表中的元素可以是多种类型

源码如下:

结论:参数是列表类型,列表的元素可以是字符串、字典、字符串构成的元组、消息类型、提示词模板类型、消息提示词模板类型等

类型1:str 列表类型

列表参数格式是 str 类型(不推荐),因为默认角色都是 human

复制代码
#1.导入相关依赖
from langchain_core.prompts import ChatPromptTemplate
# 2.定义聊天提示词模版
chat_template = ChatPromptTemplate.from_messages(
[
    "Hello, {name}!" # 等价于 ("human", "Hello, {name}!")
]
)
# 3. 使用invoke执行
messages = chat_template.invoke({"name":"小谷AI"})
# 4.打印格式化后的聊天提示词模版内容
print(messages)

类型2:tuple列表类型

列表参数格式是元组类型

复制代码
#1.导入相关依赖
from langchain_core.prompts import ChatPromptTemplate
# 2.定义聊天提示词模版
chat_template = ChatPromptTemplate.from_messages(
[
    ("human", "Hello, {name}!")  
]
)
# 3. 使用invoke执行
messages = chat_template.invoke({"name":"小谷AI"})
# 4.打印格式化后的聊天提示词模版内容
print(messages)

类型3:dict列表类型

列表参数格式是 dict 类型

复制代码
#1.导入相关依赖
from langchain_core.prompts import ChatPromptTemplate
# 2.定义聊天提示词模版
chat_template = ChatPromptTemplate.from_messages(
[
    {"role":"system", "content":"你是一个精通Python编程与AI的机器人,你的名字是{name}"},
    {"role":"human", "content":"{user_input}"}
]
)
# 3. 使用invoke执行
messages = chat_template.invoke({"name":"小谷AI", "user_input":"什么是面向对象编程?"})
# 4.打印格式化后的聊天提示词模版内容
print(messages)

类型4:Message列表类型

复制代码
#1.导入相关依赖
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.messages import SystemMessage, HumanMessage
# 2.定义聊天提示词模版
chat_template = ChatPromptTemplate.from_messages(
[
    SystemMessage(content="你是一个精通Python编程与AI的机器人,你的名字是{name}"),
    HumanMessage(content="{user_input}")
]
)
# 3. 使用invoke执行
messages = chat_template.invoke({"name":"小谷AI", "user_input":"什么是面向对象编程?"})
# 4.打印格式化后的聊天提示词模版内容
print(messages)

注意:在 Message 中不能有占位符

类型5:MessagePromptTemplate列表类型

LangChain提供不同类型的MessagePromptTemplate。

最常用的是 SystemMessagePromptTemplate、 HumanMessagePromptTemplate 和 AIMessagePromptTemplate ,分别创建系统消息、人工消息和AI消息

HumanMessagePromptTemplate,专用于生成用户消息(HumanMessage) 的模板类

  • 模板化:支持使用变量占位符,可以在运行时填充具体值
  • 格式化:能够将模板与输入变量结合生成最终的聊天消息
  • 输出类型:生成 HumanMessage 对象(content + role="human")
  • 设计目的:简化用户输入消息的模板化构造,避免重复定义角色

SystemMessagePromptTemplate、AIMessagePromptTemplate 类似

案例:

复制代码
#1.导入相关依赖
from langchain_core.prompts import ChatPromptTemplate, SystemMessagePromptTemplate, HumanMessagePromptTemplate
from langchain_core.messages import SystemMessage, HumanMessage
# 2.定义聊天提示词模版
# 2.1 定义系统消息提示词模版
system_message_prompt = SystemMessagePromptTemplate.from_template("你是一个精通Python编程与AI的机器人,你的名字是{name}")
# 2.2 定义用户消息提示词模版
human_message_prompt = HumanMessagePromptTemplate.from_template("{user_input}")
# 2.3 组合模板
chat_template = ChatPromptTemplate.from_messages(
[
    system_message_prompt,
    human_message_prompt
]
)
# 3. 使用invoke执行
messages = chat_template.invoke({"name":"小谷AI", "user_input":"什么是面向对象编程?"})
# 4.打印格式化后的聊天提示词模版内容
print(messages)

类型6:BaseChatPromptTemplate列表类型

使用 BaseChatPromptTemplate,可以理解为 ChatPromptTemplate 里嵌套了 ChatPromptTemplate

复制代码
#1.导入相关依赖
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.messages import SystemMessage, HumanMessage

# 2.定义聊天提示词模版
# 2.1 定义系统消息提示词模版
system_message_prompt = ChatPromptTemplate([
    ("system", "你是一个精通Python编程与AI的机器人,你的名字是{name}")
])
# 2.2 定义用户消息提示词模版
human_message_prompt = ChatPromptTemplate([
    ("human", "{user_input}")
])
# 2.3 组合模板
chat_template = ChatPromptTemplate.from_messages(
[
    system_message_prompt,
    human_message_prompt
]
)
# 3. 使用invoke执行
messages = chat_template.invoke({"name":"小谷AI", "user_input":"什么是面向对象编程?"})
# 4.打印格式化后的聊天提示词模版内容
print(messages)

2.2 高级特性

2.2.1 部分变量预填充:partial()

预填充某些固定不变的变量,创建模板的变体

使用场景:

  • 某些变量在所有调用中都相同
  • 需要为不同用户/场景创建定制模板

案例如下:

复制代码
#1.导入相关依赖
from os import name

from langchain_core.prompts import ChatPromptTemplate
# 2.定义聊天提示词模版
chat_template = ChatPromptTemplate.from_messages(
[
    {"role":"system", "content":"你是一个精通Python编程与AI的机器人,你的名字是{name}"},
    {"role":"human", "content":"{user_input}"}
]
)
chat_template_partial = chat_template.partial(name="小谷AI")

# 3. 使用invoke执行
messages = chat_template_partial.invoke({"user_input":"什么是面向对象编程?"})
# 4.打印格式化后的聊天提示词模版内容
print(messages)

2.2.2 消息占位符

当不确定消息提示模板使用什么角色,或者希望在格式化过程中插入消息列表时,需要使用消息占位符,负责在特定位置添加消息列表

使用场景:多轮对话系统存储历史消息以及Agent的中间步骤处理此功能非常有用

使用方式:

方式1:JSON形式

复制代码
#1.导入相关依赖
from os import name

from langchain_core.prompts import ChatPromptTemplate
# 2.定义聊天提示词模版
chat_template = ChatPromptTemplate.from_messages(
[
    {"role":"system", "content":"你是一个精通Python编程与AI的机器人,你的名字是{name}"},
    {"role":"human", "content":"{conversation}"}
]
)
chat_template_partial = chat_template.partial(name="小谷AI")

# 3. 使用invoke执行
messages = chat_template_partial.invoke({
    "conversation":
        [
            ("human", "你好,最近怎么样?"),
            ("ai", "我很好,谢谢!"),
            ("human", "你能帮我做什么?")
        ]

})
# 4.打印格式化后的聊天提示词模版内容
print(messages)

方式2:MessagesPlaceholder 实例

复制代码
#1.导入相关依赖
from os import name
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

# 2.定义聊天提示词模版
chat_template = ChatPromptTemplate.from_messages(
[
    {"role":"system", "content":"你是一个精通Python编程与AI的机器人,你的名字是{name}"},
    MessagesPlaceholder("conversation")
]
)
chat_template_partial = chat_template.partial(name="小谷AI")

# 3. 使用invoke执行
messages = chat_template_partial.invoke({
    "conversation":
        [
            HumanMessage(content="你好,最近怎么样?")
        ]

})
# 4.打印格式化后的聊天提示词模版内容
print(messages)

2.2.3 可复用模板库

在实际项目中,推荐创建模板库:即创建一个 template.py 文件专门用来声明相关模板

举例如下:

复制代码
from langchain_core.prompts import ChatPromptTemplate

class PromptTemplateLibrary:
    """
    可复用的提示词模板库
    """
    translator = ChatPromptTemplate([
        ("system", "你是专业翻译,精通{source_lang}和{target_lang}")
        ("user", f"请翻译以下文本:{user_input}")
    ])
    
    code_master = ChatPromptTemplate([
        ("system", "你是一位编程专家,精通编程语言:{code_language}")
        ("user", f"请回答以下问题:{user_input}")
    ])
    
    ...

然后在其他文件中使用:

复制代码
from template import PromptTemplateLibrary
from langchain.chat_models import init_chat_model
from dotenv import load_dotenv
import os
from rich import print as rprint

# 1.加载配置信息
load_dotenv(override=True)  # 加载.env文件中的环境变量
api_key = os.getenv("BAILIAN_API_KEY")  # 获取BAILIAN_API_KEY环境变量
base_url = os.getenv("BAILIAN_API_BASE")  # 获取BAILIAN_API_BASE环境  

# 2.初始化模型
llm = init_chat_model(
    model_provider="openai",  # 模型提供商
    model="deepseek-v4.1-flash",  # 模型名称
    api_key=api_key,  # API Key
    base_url=base_url,  # API Base URL
)

# 3.调用提示词模板
prompt = PromptTemplateLibrary.code_master.invoke({
    "code_language": "python",
    "user_input": "什么是面向对象编程?"
})

# 4.调用模型
response = llm.invoke(prompt)
print(response.content)

2.2.4 模板组合

将多个模板片段组合成复杂的提示词

方式1:字符串组合

复制代码
# 定义可复用部分
role_part = "你是一个{domain}专家。"
style_part = "回答风格:{style}。"
constraint_part = "限制:{constraint}。"

# 组合
full_system = role_part + style_part + constraint_part

template = ChatPromptTemplate.from_messages([
    ("system", full_system),
    ("user", "{question}")
])

方式2:使用 + 运算符

复制代码
template1 = ChatPromptTemplate.from_messages([
    ("system", "你是助手")
])
template2 = ChatPromptTemplate.from_messages([
    ("user", "{input}")
])
# 组合(LangChain 1.0 支持)
combined = template1 + template2
相关推荐
一千柯橘4 小时前
通过 langchain 开发一个很 small 的 agent 来了解基本流程 - py 版本
langchain
YIAN4 小时前
入门篇:LangGraph 入门实战:从零搭建最简单的 RAG 问答系统
javascript·langchain
YIAN4 小时前
高级篇:进阶多跳 RAG:子问题拆解 + 循环检索 + 智能规划,搞定复杂链式问题
javascript·langchain
YIAN4 小时前
前置篇:手把手用 Milvus + LangChain 搭建小说向量知识库,《天龙八部》EPUB 一键入库
javascript·langchain
YIAN4 小时前
进阶篇:RAG 优化第一步:给你的问答系统加上智能路由,实现双分支分流
javascript·langchain
Shockang5 小时前
LangChain 智能体治理实战:预算、权限与故障恢复
langchain
一木 之林8 小时前
LangChain 第9章 RAG 与项目实战:文档切分、嵌入、Milvus 与客服知识库
人工智能·计算机视觉·langchain·milvus
haluhalu.10 小时前
深入理解 AI Agent(1.2):Harness 工程——模型之外的竞争力
前端·数据结构·人工智能·python·langchain·个人开发
小何好运暴富开心幸福10 小时前
LangChain 与 LangGraph 核心要点总结
深度学习·语言模型·langchain·知识图谱