RAG 上下文压缩 - 基础概念

Peng Xia

前言

之前遇到一个问题,公司大模型的长度不长,而且业务需要RAG返回不少的相关内容。通常RAG都是取top-10/15,文档由于长度限制不能太多,加上embedding效果不佳,返回的文档不多也不能保证都相关。

我们考虑到返回的文档中真正能被用于求解的文本可能只占总文本的一部分,其他无关部分只会影响大模型,而且会占用上下文长度。

因此我们对RAG文档进行问题相关的总计/query-focused summarization,一方面可以过滤无关内容,另一方面让文本更助于大模型回答问题。此外,同样的top-k设置下,过滤压缩后上下文长度变短了,可以倍数增长top-k,即使总结会有些信息损失,更多的文档可以弥补这方面。

这个文章只介绍基础的总结技术,代码基于langchain实现。

文档摘要的核心原则

在构建摘要生成器时,一个核心问题是:如何将文档呈现给 LLM 的上下文窗口?

主要方法包括:

  1. Stuff(完整输入)
    直接将整个文档一次性放入上下文窗口。方法简单,但在处理长文档时受限。

  2. Map-Reduce(分块合并)
    将文档拆分为多个小块,分别对每个部分进行摘要,然后合并各部分摘要得到最终结果。适用于处理大规模数据集。

  3. Refine(逐步优化)
    按顺序处理文档,并在摘要过程中不断融合先前的摘要和新内容,从而逐步优化总结结果。适用于需要更精细摘要的场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import os
import json
from langchain_core.documents import Document

data = []
file_path = './data/data_100.json'
with open(file_path) as f:
for line in f:
a_record = json.loads(line)
data.append(a_record)

print(len(data))

data_indice = 0
a_query = data[data_indice]['query']
a_docs = data[data_indice]['pos']
a_docs = [Document(item) for item in a_docs]
100

Stuff

它将一组文档直接插入提示(prompt),然后将该提示发送给 LLM。
该方法适用于文档较小、且每次调用仅需处理少量文档的应用场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from langchain_openai import ChatOpenAI
from langchain_core.prompts import PromptTemplate, ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

prompt = PromptTemplate(
input_variables=["context", "query"],
template=(
"请清晰简明地总结以下文本以回答问题。\n\n"
"在总结时,请注意以下几点:\n"
"- 包含关键事件、重要事实和核心信息。\n"
"- 省略不必要的细节。\n\n"
"[问题-开始]:\n{query}\n[问题-结束]\n\n"
"[需要总结的文本-开始]:\n{context}\n[需要总结的文本-结束]\n"
"摘要:"
)
)

llm = ChatOpenAI(
base_url='http://localhost:5551/v1',
api_key='EMPTY',
model_name='Qwen2.5-7B-Instruct',
temperature=0.2,
)

output_parser = StrOutputParser()

map_chain = prompt | llm | output_parser
1
2
3
4
5
6
7
8
9
10
result = map_chain.invoke(
{
"query":a_query,
"context":'\n'.join([item.page_content for item in a_docs])
}
)

print(a_query)
print(20*'=')
print(result)
澳大利亚新任外长黄英贤近日访问了哪个国家,他的目的是什么?
====================
澳大利亚新任外长黄英贤访问了所罗门群岛,目的是加强澳大利亚与太平洋国家的安全合作,并强调澳大利亚在该地区的存在感。黄英贤与所罗门群岛总理索加瓦雷进行了会谈,强调了澳大利亚警方在该国骚乱后的援助,并表示澳大利亚不会在所罗门群岛建立军事基地。黄英贤的访问正值中国与所罗门群岛签署安全协议引发地区关切之际,澳大利亚、新西兰和美国等国也在该地区采取行动,试图抗衡中国在太平洋地区的影响力。

事实上既然直接 stuff 的话, 不一点需要先总结再回答, 可以直接回答问题。
在我看来, stuff 可以算作一个 CoT 环节, 先显示找出相关内容, 再求解问题

Map-Reduce

map-reduce

Map-Reduce 摘要是一种有效的长文档压缩技术,主要包含两个阶段:

  1. Map 阶段:将文档拆分为多个小块,并对每个部分独立生成摘要。
  2. Reduce 阶段:将各个部分的摘要合并,形成连贯的最终摘要。

该方法在处理超长文档时尤为有用,因为它允许在 Map 阶段对各个块并行处理,从而提高效率。此外,它还能有效规避语言模型的 token 限制,确保每个文本块都能适应模型的上下文窗口。

Map 阶段

在 Map 阶段,通常对每个文本块进行摘要生成。

标准方法是对每个块的内容进行总结,但另一种替代方式是提取关键信息。
由于 Reduce 阶段最终会将所有输出合并为最终摘要,因此这两种方法通常都能有效完成任务,并且对最终结果的影响较小。

在 Map 阶段选择摘要生成还是关键信息提取,可以根据具体任务的目标和需求进行调整。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import asyncio
from pydantic import BaseModel, Field
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import PydanticOutputParser
from langchain_core.prompts import PromptTemplate, ChatPromptTemplate

# 定义输出格式
class MapSummary(BaseModel):
reasoning: str = Field(description="关于问题、文本内容、两者之间关联性的思考")
summary: str = Field(description="对文本中与问题相关片段的内容总结")

# 创建解析器
map_parser = PydanticOutputParser(pydantic_object=MapSummary)

# 创建模板
map_prompt = ChatPromptTemplate.from_messages(
[
# role, message
("system", "你是一名专业的内容提取和总结专家。"),
("human", (
"请清晰简明地总结以下文本以回答问题。\n\n"
"在总结时,请注意以下几点:\n"
"- 你的任务是总结问题相关的文本,而不是回答问题。"
"- 包含关键事件、重要事实和核心信息。\n"
"- 省略不必要的细节。\n\n"
"按以下格式要求输出:\n{format_instructions}\n\n"
"[问题-开始]:\n{query}\n[问题-结束]\n\n"
"[需要总结的文本-开始]:\n{context}\n[需要总结的文本-结束]\n"
)
),
]
)

# 固定输出格式指令
map_prompt = map_prompt.partial(format_instructions=map_parser.get_format_instructions())

llm = ChatOpenAI(
base_url='http://localhost:5551/v1',
api_key='EMPTY',
model_name='Qwen2.5-7B-Instruct',
temperature=0.2,
)

map_chain = map_prompt | llm | map_parser

map_prompt.pretty_print()
================================[1m System Message [0m================================

你是一名专业的内容提取和总结专家。

================================[1m Human Message [0m=================================

请清晰简明地总结以下文本以回答问题。

在总结时,请注意以下几点:
- 你的任务是总结问题相关的文本,而不是回答问题。- 包含关键事件、重要事实和核心信息。
- 省略不必要的细节。

按以下格式要求输出:
[33;1m[1;3m{format_instructions}[0m

[问题-开始]:
[33;1m[1;3m{query}[0m
[问题-结束]

[需要总结的文本-开始]:
[33;1m[1;3m{context}[0m
[需要总结的文本-结束]
摘要:
1
2
3
4
5
6
7
8
9
10
11
12
13
# 并行运行所有任务
tasks = [
map_chain.ainvoke(
{
"query":a_query,
"context":doc.page_content,
}
)
for doc in a_docs
]

map_results = await asyncio.gather(*tasks)
map_results
[MapSummary(think='文本主要讨论了澳大利亚外长黄英贤访问所罗门群岛的背景和目的,以及美国与马绍尔群岛的经济援助谈判,但未直接回答黄英贤访问的具体国家和目的。', summary='澳大利亚外长黄英贤访问了所罗门群岛,强调了澳大利亚对所罗门群岛的援助,并与总理讨论了安全问题。美国与马绍尔群岛就经济援助进行谈判,以应对中国在太平洋地区的影响力增强。'),
 MapSummary(think='文本主要描述了澳大利亚新任外长黄英贤访问萨摩亚和汤加的目的,以及美日等国在南太平洋的动作,意图抗衡中国的影响力。', summary='澳大利亚新任外长黄英贤访问萨摩亚和汤加,旨在加强与太平洋国家的安全合作。此前,美日等国也在南太平洋频繁活动,意图抗衡中国在该地区的影响力。'),
 MapSummary(think='文本主要报道了澳大利亚外长黄英贤访问中国的背景、目的以及外界对此的评价。', summary='澳大利亚外长黄英贤于12月20日至21日对中国进行访问,这是澳中建交50周年之际,黄英贤四年多来首次访华。多家外媒认为此访标志着中澳关系迈出重要一步,有助于推进共同利益和管控分歧。'),
 MapSummary(think='文本主要讲述了澳大利亚新任外长黄英贤访问中国的目的和背景,以及专家对此的解读。', summary='澳大利亚外长黄英贤访问中国,旨在推动贸易限制措施的取消,并寻求与中国的稳定平等伙伴关系。澳专家认为,这是澳大利亚抓住与中国关系解冻的机会,但中澳关系回暖仍面临美国的影响。')]

Reduce 阶段

Reduce Chain 中,对 Map 阶段生成的结果进行进一步处理,以合并和优化内容,最终生成连贯的摘要。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class ReduceSummary(BaseModel):
reasoning: str = Field(description="关于问题与局部内容总结的思考")
summary: str = Field(description="整合局部内容总结的全局总结")

reduce_parser = PydanticOutputParser(pydantic_object=ReduceSummary)

reduce_prompt = ChatPromptTemplate.from_messages(
[
# role, message
("system", "你是一名专业的摘要专家。你将收到一组文档摘要,并需要将其整合为一个完整的摘要。"),
("human", (
"请将以下局部的总结内容整合,形成一个完整的内容摘要,以作为参考材料回答问题。\n\n"
"在总结时,请注意以下几点:\n"
"- 你的任务是总结问题相关的文本,而不是回答问题。"
"- 包含关键事件、重要事实和核心信息。\n"
"- 省略不必要的细节。\n"
"- 去除重复冗余内容,使语言更加简洁和凝练。\n\n"
"按以下格式要求输出:\n{format_instructions}\n\n"
# "[问题-开始]:\n{query}\n[问题-结束]\n\n"
"[局部内容总结-开始]:\n{map_summary}\n[局部内容总结-结束]\n"
)
),
]
)

reduce_prompt = reduce_prompt.partial(format_instructions=reduce_parser.get_format_instructions())

llm = ChatOpenAI(
base_url='http://localhost:5551/v1',
api_key='EMPTY',
model_name='Qwen2.5-7B-Instruct',
temperature=0.2,
)

reduce_chain = reduce_prompt | llm | reduce_parser
reduce_prompt.pretty_print()
1
2
3
4
5
6
reduce_result = reduce_chain.invoke(
{
"query":a_query,
"map_summary": '\n'.join([f'- {item.summary}' for item in map_results])
}
)
1
print(reduce_result.summary)
澳大利亚外长黄英贤频繁访问太平洋国家,强调安全合作并应对中国影响力。同时,黄英贤访问中国,推动贸易限制措施取消,寻求稳定平等的伙伴关系,但美国的影响仍存。
1
2
3
4
5
6
print(reduce_prompt.invoke(
{
"query":a_query,
"map_summary": '\n'.join([f'- {item.summary}' for item in map_results])
}
).to_messages()[-1].content)
请将以下局部的总结内容整合,形成一个完整的内容摘要,以作为参考材料回答问题。

在总结时,请注意以下几点:
- 你的任务是总结问题相关的文本,而不是回答问题。- 包含关键事件、重要事实和核心信息。
- 省略不必要的细节。
- 去除重复冗余内容,使语言更加简洁和凝练。

按以下格式要求输出:
The output should be formatted as a JSON instance that conforms to the JSON schema below.

As an example, for the schema {"properties": {"foo": {"title": "Foo", "description": "a list of strings", "type": "array", "items": {"type": "string"}}}, "required": ["foo"]}
the object {"foo": ["bar", "baz"]} is a well-formatted instance of the schema. The object {"properties": {"foo": ["bar", "baz"]}} is not well-formatted.

Here is the output schema:
1
{"properties": {"think": {"description": "关于问题与局部内容总结的思考", "title": "Think", "type": "string"}, "summary": {"description": "整合局部内容总结的全局总结", "title": "Summary", "type": "string"}}, "required": ["think", "summary"]}
[局部内容总结-开始]: - 澳大利亚外长黄英贤访问了所罗门群岛,强调了澳大利亚对所罗门群岛的援助,并与总理讨论了安全问题。美国与马绍尔群岛就经济援助进行谈判,以应对中国在太平洋地区的影响力增强。 - 澳大利亚新任外长黄英贤访问萨摩亚和汤加,旨在加强与太平洋国家的安全合作。此前,美日等国也在南太平洋频繁活动,意图抗衡中国在该地区的影响力。 - 澳大利亚外长黄英贤于12月20日至21日对中国进行访问,这是澳中建交50周年之际,黄英贤四年多来首次访华。多家外媒认为此访标志着中澳关系迈出重要一步,有助于推进共同利益和管控分歧。 - 澳大利亚外长黄英贤访问中国,旨在推动贸易限制措施的取消,并寻求与中国的稳定平等伙伴关系。澳专家认为,这是澳大利亚抓住与中国关系解冻的机会,但中澳关系回暖仍面临美国的影响。 [局部内容总结-结束] 摘要:

关于是否在reduce阶段附加query的思考


在这个阶段里, prompt里可以不附加query, 因为map阶段已经过滤内容了, 此阶段只需要整合。
但最好附上query, 不然可能会出现把需要的内容给整合漏了的情况。
以下是一个例子,map阶段内容还很具体,但reduce过于简洁,以致于如果换了具体的问题,就可能无法求解了。

query

1
澳大利亚新任外长黄英贤近日访问了哪个国家,他的目的是什么?

map结果

1
2
3
4
- 澳大利亚外长黄英贤访问了所罗门群岛,强调了澳大利亚对所罗门群岛的援助,并与总理讨论了安全问题。美国与马绍尔群岛就经济援助进行谈判,以应对中国在太平洋地区的影响力增强。
- 澳大利亚新任外长黄英贤访问萨摩亚和汤加,旨在加强与太平洋国家的安全合作。此前,美日等国也在南太平洋频繁活动,意图抗衡中国在该地区的影响力。
- 澳大利亚外长黄英贤于12月20日至21日对中国进行访问,这是澳中建交50周年之际,黄英贤四年多来首次访华。多家外媒认为此访标志着中澳关系迈出重要一步,有助于推进共同利益和管控分歧。
- 澳大利亚外长黄英贤访问中国,旨在推动贸易限制措施的取消,并寻求与中国的稳定平等伙伴关系。澳专家认为,这是澳大利亚抓住与中国关系解冻的机会,但中澳关系回暖仍面临美国的影响。

reduce结果

1
澳大利亚外长黄英贤频繁访问太平洋国家,强调安全合作并应对中国影响力。同时,黄英贤访问中国,推动贸易限制措施取消,寻求稳定平等的伙伴关系,但美国的影响仍存。

Map-Reduce Chain 的完整流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
import asyncio
from pydantic import BaseModel, Field
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import PydanticOutputParser
from langchain_core.prompts import PromptTemplate, ChatPromptTemplate

# ===== map =====

class MapSummary(BaseModel):
reasoning: str = Field(description="关于问题、文本内容、两者之间关联性的思考")
summary: str = Field(description="对文本中与问题相关片段的内容总结")

map_parser = PydanticOutputParser(pydantic_object=MapSummary)

map_prompt = ChatPromptTemplate.from_messages(
[
# role, message
("system", "你是一名专业的内容提取和总结专家。"),
("human", (
"请清晰简明地总结以下文本以回答问题。\n\n"
"在总结时,请注意以下几点:\n"
"- 你的任务是总结问题相关的文本,而不是回答问题。"
"- 包含关键事件、重要事实和核心信息。\n"
"- 省略不必要的细节。\n\n"
"按以下格式要求输出:\n{format_instructions}\n\n"
"[问题-开始]:\n{query}\n[问题-结束]\n\n"
"[需要总结的文本-开始]:\n{context}\n[需要总结的文本-结束]\n"
)
),
]
)

map_prompt = map_prompt.partial(format_instructions=map_parser.get_format_instructions())

llm = ChatOpenAI(
base_url='http://localhost:5551/v1',
api_key='EMPTY',
model_name='Qwen2.5-7B-Instruct',
temperature=0.2,
)

map_chain = map_prompt | llm | map_parser

# ===== reduce =====

class ReduceSummary(BaseModel):
reasoning: str = Field(description="关于问题与局部内容总结的思考")
summary: str = Field(description="整合局部内容总结的全局总结")

reduce_parser = PydanticOutputParser(pydantic_object=ReduceSummary)

reduce_prompt = ChatPromptTemplate.from_messages(
[
# role, message
("system", "你是一名专业的摘要专家。你将收到一组文档摘要,并需要将其整合为一个完整的摘要。"),
("human", (
"请将以下局部的总结内容整合,形成一个完整的内容摘要,以作为参考材料回答问题。\n\n"
"在总结时,请注意以下几点:\n"
"- 你的任务是总结问题相关的文本,而不是回答问题。"
"- 包含关键事件、重要事实和核心信息。\n"
"- 省略不必要的细节。\n"
"- 去除重复冗余内容,使语言更加简洁和凝练。\n\n"
"按以下格式要求输出:\n{format_instructions}\n\n"
# "[问题-开始]:\n{query}\n[问题-结束]\n\n"
"[局部内容总结-开始]:\n{map_summary}\n[局部内容总结-结束]\n"
)
),
]
)

reduce_prompt = reduce_prompt.partial(format_instructions=reduce_parser.get_format_instructions())

llm = ChatOpenAI(
base_url='http://localhost:5551/v1',
api_key='EMPTY',
model_name='Qwen2.5-7B-Instruct',
temperature=0.2,
)

reduce_chain = reduce_prompt | llm | reduce_parser


async def map_reduce_chain(query, docs):

tasks = [
map_chain.ainvoke(
{
"query":a_query,
"context":doc.page_content,
}
)
for doc in a_docs
]

map_results = await asyncio.gather(*tasks)

reduce_result = reduce_chain.invoke(
{
"query":a_query,
"map_summary": '\n'.join([f'- {item.summary}' for item in map_results])
}
)
return reduce_result

result = await map_reduce_chain(a_query, a_docs)
1
print(result.summary)
澳大利亚外长黄英贤访问所罗门群岛、萨摩亚、汤加及中国,旨在加强教育、医疗援助、安全合作,并推动贸易限制措施的取消,重建与中国的友好关系。此访问具有重大象征意义,标志着澳中关系改善的积极信号,但美国可能成为关系改善的障碍。

Map-Refine

map-refine

Map-Refine 方法是一种文档摘要处理方式,类似于 Map-Reduce,但在摘要的处理和合并方式上有所不同。

  1. Map 阶段
  • 将文档拆分为多个小块。
  • 独立对每个块进行摘要。
  1. Refine 阶段
  • 生成的摘要按顺序进行处理。
  • 每次迭代时,将上一次的摘要与下一个文本块的信息结合,对摘要进行更新和优化。
  1. 迭代过程
  • Refine 阶段会持续迭代,直到所有文本块都处理完毕。
  • 每次迭代都会在保留已有信息的基础上,进一步完善摘要。
  1. 最终摘要
  • 所有文本块处理完成后,最终摘要将在最后一次优化步骤后生成。

主要优势:

  • 保持文档顺序:适用于需要保持原始内容顺序的情况,如叙述性或结构化文档。
  • 上下文优化:摘要在每个步骤都会逐步完善,适用于需要递进式构建背景信息的内容。

局限性:

  • 顺序处理:Refine 阶段需要按顺序执行,难以并行化。
  • 时间成本较高:相比 Map-Reduce,由于无法并行处理,大规模数据处理可能较慢。

Map 阶段

和 Map-Reduce 同样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import asyncio
from pydantic import BaseModel, Field
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import PydanticOutputParser
from langchain_core.prompts import PromptTemplate, ChatPromptTemplate

# 定义输出格式
class MapSummary(BaseModel):
reasoning: str = Field(description="关于问题、文本内容、两者之间关联性的思考")
summary: str = Field(description="对文本中与问题相关片段的内容总结")

# 创建解析器
map_parser = PydanticOutputParser(pydantic_object=MapSummary)

# 创建模板
map_prompt = ChatPromptTemplate.from_messages(
[
# role, message
("system", "你是一名专业的内容提取和总结专家。"),
("human", (
"请清晰简明地总结以下文本以回答问题。\n\n"
"在总结时,请注意以下几点:\n"
"- 你的任务是总结问题相关的文本,而不是回答问题。"
"- 包含关键事件、重要事实和核心信息。\n"
"- 省略不必要的细节。\n\n"
"按以下格式要求输出:\n{format_instructions}\n\n"
"[问题-开始]:\n{query}\n[问题-结束]\n\n"
"[需要总结的文本-开始]:\n{context}\n[需要总结的文本-结束]\n"
)
),
]
)

# 固定输出格式指令
map_prompt = map_prompt.partial(format_instructions=map_parser.get_format_instructions())

llm = ChatOpenAI(
base_url='http://localhost:5551/v1',
api_key='EMPTY',
model_name='Qwen2.5-7B-Instruct',
temperature=0.2,
)

map_chain = map_prompt | llm | map_parser

map_prompt.pretty_print()
================================[1m System Message [0m================================

你是一名专业的内容提取和总结专家。

================================[1m Human Message [0m=================================

请清晰简明地总结以下文本以回答问题。

在总结时,请注意以下几点:
- 你的任务是总结问题相关的文本,而不是回答问题。- 包含关键事件、重要事实和核心信息。
- 省略不必要的细节。

按以下格式要求输出:
[33;1m[1;3m{format_instructions}[0m

[问题-开始]:
[33;1m[1;3m{query}[0m
[问题-结束]

[需要总结的文本-开始]:
[33;1m[1;3m{context}[0m
[需要总结的文本-结束]

1
2
3
4
5
6
7
8
9
10
11
12
13
# 并行运行所有任务
tasks = [
map_chain.ainvoke(
{
"query":a_query,
"context":doc.page_content,
}
)
for doc in a_docs
]

map_results = await asyncio.gather(*tasks)
map_results
[MapSummary(reasoning='文本主要描述了澳大利亚外长黄英贤访问所罗门群岛的情况,以及访问的目的和背景。虽然文本中提到了澳大利亚对所罗门群岛的援助,但未明确提及黄英贤访问的具体目的。', summary='澳大利亚外长黄英贤访问了所罗门群岛,强调了澳大利亚对所罗门群岛的教育、医疗援助以及当地骚乱后的恢复工作。访问期间,黄英贤与所罗门群岛总理索加瓦雷进行了会谈,讨论了安全问题。'),
 MapSummary(reasoning='文本主要描述了澳大利亚新任外长黄英贤访问萨摩亚和汤加的目的,以及澳大利亚与其他国家在南太平洋地区的活动,旨在抗衡中国在该地区的影响力。', summary='澳大利亚新任外长黄英贤访问萨摩亚和汤加,目的是加强澳大利亚与太平洋国家的安全合作,抗衡中国在南太平洋的影响力。'),
 MapSummary(reasoning='文本主要报道了澳大利亚外长黄英贤访问中国的事件,包括访问的目的和背景,以及多家媒体对此的报道和评论。', summary='澳大利亚外长黄英贤于12月20日至21日对中国进行访问,这是澳中建交50周年之际,四年多来首次访华。多家媒体认为此访标志着中澳关系迈出重要一步,有助于推进共同利益和管控分歧。'),
 MapSummary(reasoning='文本主要介绍了澳大利亚外长黄英贤访华的目的和背景,以及此次访问的意义。文章提到了澳中关系的现状和未来可能的发展方向。', summary='澳大利亚外长黄英贤访问中国,旨在推动贸易限制措施的取消,并寻求与中国的稳定平等伙伴关系。此次访问被视为中澳关系解冻的重要时刻,但双方仍面临一些棘手问题,未来关系回暖仍面临美国的影响。')]

Refine 阶段

Refine 阶段,Map 阶段生成的文本块会按顺序依次处理,每次迭代都会逐步优化最终摘要。
摘要的更新方式是将上一轮的摘要与下一个文本块的信息结合,从而确保最终摘要更加全面且符合上下文逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class RefineSummary(BaseModel):
reasoning: str = Field(description="关于问题与局部内容总结的思考")
summary: str = Field(description="整合局部内容总结的全局总结")

refine_parser = PydanticOutputParser(pydantic_object=RefineSummary)

refine_prompt = ChatPromptTemplate.from_messages(
[
# role, message
("system", "你是一名专业的摘要专家。你的任务是生成一个最终的内容总结。"),
("human", (
"我提供你一份当前的总结,和一份新文本,你需要结合两者,精炼出一份新的总结,以作为参考材料回答问题。\n\n"
"在总结时,请注意以下几点:\n"
"- 你的任务是总结问题相关的文本,而不是回答问题。"
"- 包含关键事件、重要事实和核心信息。\n"
"- 省略不必要的细节。\n"
"- 去除重复冗余内容,使语言更加简洁和凝练。\n\n"
"按以下格式要求输出:\n{format_instructions}\n\n"
"[问题-开始]:\n{query}\n[问题-结束]\n\n"
"[当前内容总结-开始]:\n{previous_summary}\n[当前内容总结-结束]\n\n"
"[新文本-开始]:\n{current_summary}\n[新文本-结束]\n"
)
),
]
)

refine_prompt = refine_prompt.partial(format_instructions=refine_parser.get_format_instructions())

llm = ChatOpenAI(
base_url='http://localhost:5551/v1',
api_key='EMPTY',
model_name='Qwen2.5-7B-Instruct',
temperature=0.2,
)

refine_chain = refine_prompt | llm | refine_parser
refine_prompt.pretty_print()
================================[1m System Message [0m================================

你是一名专业的摘要专家。你的任务是生成一个最终的内容总结。

================================[1m Human Message [0m=================================

我提供你一份当前的总结,和一份新文本,你需要结合两者,精炼出一份新的总结,以作为参考材料回答问题。

在总结时,请注意以下几点:
- 你的任务是总结问题相关的文本,而不是回答问题。- 包含关键事件、重要事实和核心信息。
- 省略不必要的细节。
- 去除重复冗余内容,使语言更加简洁和凝练。

按以下格式要求输出:
[33;1m[1;3m{format_instructions}[0m

[问题-开始]:
[33;1m[1;3m{query}[0m
[问题-结束]

[当前内容总结-开始]:
[33;1m[1;3m{previous_summary}[0m
[当前内容总结-结束]

[新文本-开始]:
[33;1m[1;3m{current_summary}[0m
[新文本-结束]

1
2
3
4
5
6
7
8
9
10
11
12
13
previous_summary = map_results[0].summary

for item in map_results[1:]:
refine_result = refine_chain.invoke(
{
"query":a_query,
"previous_summary":previous_summary,
"current_summary":item.summary,
}
)
previous_summary = refine_result.summary
print('='*20)
print(previous_summary)
====================
澳大利亚新任外长黄英贤访问萨摩亚和汤加,旨在加强与太平洋国家的安全合作,抗衡中国在南太平洋的影响力。访问期间,黄英贤还与所罗门群岛总理讨论了安全问题并提供了援助。
====================
澳大利亚新任外长黄英贤分别访问了萨摩亚、汤加和中国。他对萨摩亚和汤加的访问旨在加强与太平洋国家的安全合作,抗衡中国在南太平洋的影响力。同时,黄英贤对中国进行访问,这是澳中建交50周年之际,四年多来首次访华,标志着中澳关系迈出重要一步。
====================
澳大利亚新任外长黄英贤访问了萨摩亚、汤加和中国。他对萨摩亚和汤加的访问旨在加强与太平洋国家的安全合作,抗衡中国在南太平洋的影响力。对中国访问则旨在推动贸易限制措施的取消,并寻求与中国的稳定平等伙伴关系。此次访问被视为中澳关系解冻的重要时刻,但未来关系回暖仍面临美国的影响。

具体 refine 的粒度还需要自己通过 prompt 调整, 但大多数下大模型都会简化内容, 因为”总结”就是简化。

Comments