一、需求背景
1.1 行业痛点
在航空票务领域,PNR(Passenger Name Record,旅客姓名记录)是航空公司用于存储旅客预订信息的电子记录。传统上,票务代理需要掌握复杂的 PNR 指令系统才能完成机票预订、查询和管理等操作。这些指令通常具有严格的格式要求和专业术语,例如”qv:m/PxxxxxN/05SEP/1200/xxx/O”这样的指令表示查询从北京到广州 9 月 5 日 12 点的 xxx 航空直飞航班。
PNR 指令系统的复杂性主要体现在以下几个方面:
- 格式严格:指令必须按照特定的格式输入,包括特定的前缀、分隔符和参数顺序
- 代码繁多:需要记忆大量的机场码、航空公司码等
- 参数复杂:不同类型的查询(如单程、往返、中转等)需要不同的参数组合
- 错误率高:手动输入容易出错,一个字符的错误可能导致整个指令无效
对于票务代理来说,掌握这些指令需要大量的培训和实践,而且容易出错。特别是对于新入职的员工,学习曲线陡峭,效率低下。一个简单的查询可能需要输入十几个字符的复杂指令,而且不同的查询场景需要不同的指令格式。
1.2 项目目标
本项目的核心目标是构建一个自然语言到 PNR 机器指令的转换系统,实现以下功能:
- 准确理解用户意图:能够从自然语言中准确识别用户的查询意图,如查询航班、预订机票等
- 精确识别关键实体:识别查询中的关键实体信息,如出发地、目的地、日期、时间、航空公司等
- 生成标准指令:根据识别结果生成符合规范的 PNR 机器指令
- 支持复杂场景:支持多种复杂查询场景,如单程、往返、中转、多段行程等
- 高容错性:能够处理不同的表达方式、口语化表达和部分信息缺失的情况
通过实现这些目标,系统将大大简化票务代理的工作流程,提高工作效率,同时为未来的智能客服系统奠定基础。
二、方案概述
为实现自然语言到 PNR 机器指令的转换,我们设计了一个基于 DeepSeek 大模型、结合全文检索与向量检索的混合系统。该系统工作流程如下:接收用户自然语言输入后,对输入进行处理以理解查询意图,识别其中关键实体,进而生成符合规范的 PNR 机器指令 。
2.1 系统架构
系统的总体架构如图所示,主要由用户输入处理模块、自然语言理解模块、实体识别模块、融合全文检索与向量检索的混合检索模块、实体代码匹配模块以及指令生成模块组成。各模块协同运作,在自然语言到 PNR 机器指令转换过程中,分别承担特定功能,形成完整的处理链路。
2.2 数据处理流程
数据处理作为系统的基础环节,涵盖对原始数据的清洗、合并及转换等操作,经上述流程后,生成结构化的术语表数据库。 其中,数据清洗涉及去除无效数据、合并中英文名称、标准化格式;清洗后的数据进行合并,再经数据转换,最终以文本格式、向量索引及关系映射等形式输出至术语表数据库。
2.3 实体识别与代码匹配流程
当用户输入自然语言查询时,系统会通过以下流程进行处理:
- 自然语言理解模块借助大模型分析用户输入。
- 实体识别模块识别出关键实体,如出发地、到达地、日期、时间等 ,并提取实体信息。
- 全文检索模块使用正则表达式进行精确匹配:若命中,直接返回匹配结果;若未命中,则转向向量检索模块,利用 BGE 向量嵌入返回相似度最高结果。
- 代码匹配模块将检索结果匹配为标准代码。
- 指令生成模块依据匹配结果生成 PNR 指令并返回给用户。
2.4 系统核心模块
系统主要包含以下几个核心模块:
2.4.1 数据处理模块
数据处理模块承担着机场、城市、国家及航空公司等基础信息的处理任务,旨在构建术语表及映射关系,这些数据是系统准确识别实体并生成指令的基石。其核心功能如下:
- 数据清洗:剔除无效数据,修正错误数据,确保数据质量。
- 数据合并:整合中英文名称,融合不同来源数据,消除数据冗余与差异。
- 数据转换:将数据转化为适配系统运行的格式,提升数据兼容性。
- 数据索引:构建向量索引,为高效检索提供支撑,加速数据访问。
2.4.2 自然语言理解模块
自然语言理解模块借助 DeepSeek V3 大模型,对用户输入展开深度剖析,精准识别查询意图并提取关键实体信息。具体功能涵盖:
- 意图识别:分辨用户查询意图,如航班查询、机票预订等不同诉求。
- 实体识别:定位查询中的关键实体,包括出发地、目的地、日期、时间、航空公司等核心要素。
- 上下文理解:解读查询中的上下文信息,如 “明天”“下午” 等相对时间表述,准确把握语义。
- 模糊表达处理:处理 “国庆”“五一” 等节假日这类模糊表达,转化为确切语义。
2.4.3 全文检索模块
全文检索模块作为系统检索的首道工序,专注于对识别出的实体执行精确匹配。其主要功能有:
- 精确匹配:运用正则表达式,实现实体与数据库记录的精准对应,如将 “北京” 精准匹配到对应条目。
- 高效查询:借助索引与哈希表等数据结构,实现文本的高速查询,缩短检索耗时。
- 标准化处理:对查询文本与数据库文本进行标准化操作,如去除多余空格、统一大小写,规范文本形式。
- 结果过滤:依据匹配度与相关性,对检索结果进行筛选与排序,呈现优质结果。
全文检索具备速度快、准确性高的显著优势,尤其适用于标准表达实体的处理。当用户输入的实体表达与数据库记录完全契合时,可迅速返回准确结果,规避复杂语义分析。
2.4.4 向量检索模块
向量检索模块运用向量嵌入技术,实现用户提及实体与术语表的匹配,并获取对应的标准代码。其功能要点为:
- 向量嵌入:将实体名称转化为向量形式,便于计算机理解与处理。
- 相似度计算:测算实体向量与术语表向量间的相似度,衡量语义关联程度。
- 最佳匹配:筛选出与实体向量最相似的术语表项,确定最佳对应关系。
- 代码映射:将匹配成功的术语表项映射为标准代码,实现实体的标准化表达。
2.4.5 指令生成模块
指令生成模块依据识别出的意图与实体信息,遵循 PNR 指令格式规范,生成最终机器指令。其主要功能如下:
- 格式化日期:将日期转换为标准格式,如把 “2025 年 4 月 5 日” 规范为 “05apr25” 。
- 格式化时间:将时间转化为标准形式,例如 “下午” 转换为 “1200” ,统一时间表达。
- 指令组装:根据不同航程类型,组合相应的 PNR 指令,确保指令针对性。
- 指令验证:检验生成指令是否符合规范要求,保障指令有效性。
2.4.5 用户交互界面
用户交互界面提供简洁易用的交互方式,用户输入自然语言查询,系统反馈相应 PNR 指令。其功能包括:
- 输入处理:接收并预处理用户的自然语言输入,为后续处理做准备。
- 结果展示:直观呈现生成的 PNR 指令,方便用户获取结果。
- 错误处理:处理系统运行中可能出现的错误,给予友好清晰的错误提示。
- 用户反馈:收集用户反馈信息,为系统优化与迭代提供依据。
三、技术实现
1. 数据处理
数据处理是整个系统的基础工作。我们需要处理机场、城市、国家和航空公司的基础信息,构建术语表和映射关系。以下部分为数据处理代码:
# 术语表处理 - 处理机场信息,合并中英文名称
def process_airport_info(input_file, output_file):
df = pd.read_excel(input_file, usecols=[0, 1, 2])
df.iloc[:, 1].fillna(df.iloc[:, 2], inplace=True)
df.drop(df.columns[2], axis=1, inplace=True)
df.to_excel(output_file, index=False, header=True, engine='openpyxl')
return df
# 术语表处理 - 合并机场和城市信息
def merge_airport_city(airport_file, city_file, output_file):
df_airport = pd.read_excel(airport_file)
df_city = pd.read_excel(city_file, header=None).iloc[:, :2]
df_city.columns = df_airport.columns[:2]
df_final = pd.concat([df_airport, df_city], ignore_index=True)
df_final.to_excel(output_file, index=False, engine='openpyxl')
return df_final
# 术语表处理 - 合并机场、城市和航司信息
def merge_all_info(info_file, airline_file, output_file):
df_info = pd.read_excel(info_file)
df_airline = pd.read_excel(airline_file, header=None).iloc[:, :2]
df_airline.columns = df_info.columns[:2]
df_final = pd.concat([df_info, df_airline], ignore_index=True)
df_final.to_excel(output_file, index=False, engine='openpyxl')
return df_final
# 数据格式转换 - Excel 转 txt
def excel_to_txt(excel_file, txt_file):
df = pd.read_excel(excel_file, usecols=[0, 1])
data_to_write = df.to_csv(sep='\t', na_rep='nan')
with open(txt_file, 'w', encoding='utf-8') as f:
f.write(data_to_write)
# 主程序,依次调用各处理函数
if __name__ == "__main__":
# 处理机场信息
airport_input = '*/*.xls'
airport_output = '*/*.xls'
df_airport = process_airport_info(airport_input, airport_output)
# 合并机场和城市信息
city_input = '*/*.xlsx'
merge12_output = '*/*.xlsx'
df_merge12 = merge_airport_city(airport_output, city_input, merge12_output)
# 合并机场、城市和航司信息
info_input = '*/*.xlsx'
airline_input = '*/*.xlsx'
merge_all_output = '*/*.xlsx'
df_merge_all = merge_all_info(info_input, airline_input, merge_all_output)
# Excel 转 txt
excel_to_txt(merge_all_output, '*.txt')
2. 自然语言理解
自然语言理解是系统的核心部分,我们使用 DeepSeek V3 大模型来理解用户输入,识别查询意图和提取关键实体信息。这一模块的设计和实现直接影响系统的整体性能和用户体验。
2.1 大模型选型与配置
在选择大模型时,我们考虑了多个因素,包括模型的理解能力、上下文窗口大小、推理速度和成本等。经过评估,我们选择了 DeepSeek V3 模型,主要基于以下考虑:
- 更好的理解能力:DeepSeek V3 在理解复杂指令和专业领域知识方面表现出色,能够准确理解航空票务领域的专业术语和查询意图。
- 合适的上下文窗口:模型支持足够大的上下文窗口,能够处理包含详细系统提示和用户查询的输入。
- 合理推理速度:在实际应用中,响应速度是用户体验的重要因素,DeepSeek V3 能够在合理的时间内完成推理。
2.2 系统提示词设计
系统提示设计是实现高质量实体识别的关键。我们设计了详细的系统提示,包含了航程类型识别、出发/到达地点识别、航司识别、日期时间识别等多个方面的规则和示例。
系统提示的设计遵循以下原则:
- 明确任务定义:清晰定义模型需要完成的任务和输出格式
- 提供详细规则:为每种实体类型提供详细的识别规则和示例
- 处理边界情况:包含处理特殊情况和模糊表达的指导
- 结构化输出:要求模型以结构化的 JSON 格式输出结果
以下是部分核心系统提示词部分:
system_prompt = f'''
你是一名指令识别助手,你需要理解用输入并完成以下工作:
1.航程类型识别:
(1)单程-显示承运方航班信息。如果有提到承运方航班,则航程类型识别结果输出 "xxx"。
(2)单程- 显示xx联盟的航空公司航班信息。如果有提到xx联盟的航空公司航班,则航程类型识别结果输出 "xxx"。
(3)双程-往返。如果在用户输入中提到了在两个城市间往返。
(4)多旅程。如果在用户输入中提到多段城市间的旅程,则航程类别结果输出 "xxx"。
2、出发/达到地点识别:理解用户输入,并在其中识别关键信息:出发地点和到达地点。
3、航司识别:理解用户输入,并在其中识别航空公司关键信息,"即可。
4、直飞识别:理解用户是否提到'直飞', 如果有提到'直飞',在识别结果中,则输出"ture", 如果没有提到则输出"false"
5、无经停识别:理解用户是否提到'无经停', 如果有提到'无经停',在识别结果中,则输出"ture", 如果没有提到则输出"false"
6、完整显示:如果航司类别结果输出为"xxx",则该项输出"ture", 如果不是"xxx"则输出"false"
7、按到达时间排列:理解用户是否提到'按到达时间排列', 如果有提到'按到达时间排列',在识别结果中,则输出"ture", 如果没有提到则输出"false"
注意,今天的时间为:{today}时
'''
2.3 实体识别实现
实体识别是系统的核心功能,我们通过调用 DeepSeek 大模型实现。实体识别函数接收用户输入和系统提示,构建请求消息,调用模型 API,并返回识别结果:
def stream_llm_response(client, messages, model="deepseek-ai/DeepSeek-V3", max_tokens=4096):
"""Stream response from LLM and return accumulated text"""
response = client.chat.completions.create(
model=model,
messages=messages,
max_tokens=max_tokens,
temperature=0, # Lowest temperature for most deterministic output
presence_penalty=2.0, # Highest presence penalty to encourage diverse content
stream=True
)
response_text = ""
for chunk in response:
if chunk.choices[0].delta.content is not None:
chunk_text = chunk.choices[0].delta.content
print(chunk_text, end='') # Print each text chunk as it arrives
response_text += chunk_text
return response_text
def save_response(response_text, output_path):
"""Save response text to file"""
with open(output_path, 'w', encoding='utf-8') as f:
f.write(response_text)
print(f"\nSaved to: {output_path}")
实体识别的主要挑战和解决方案:
- 模糊表达处理:用户可能使用模糊的表达方式,如”国庆”、”五一”等节假日表达,或者”明天”、”后天”等相对时间表达。我们在系统提示中提供了详细的规则,指导模型如何处理这些表达。
- 多实体识别:一个查询中可能包含多个实体,如多个出发地、到达地、航空公司等。我们设计了结构化的输出格式,能够同时识别和输出多个实体。
- 上下文理解:模型需要理解查询中的上下文信息,如”往返”表示两个城市之间的双向行程。我们通过提供详细的示例和规则,帮助模型理解这些上下文信息。
- 特殊情况处理:如用户只提到城市名而没有提到具体机场,或者使用航空公司简称等。我们在系统提示中提供了处理这些特殊情况的规则。
2.4 实体识别优化
为了提高实体识别的准确率和鲁棒性,我们进行了以下优化:
- 系统提示迭代优化:通过多轮测试和反馈,不断优化系统提示,使其更加清晰、详细和有效。
- 错误案例分析:收集和分析识别错误的案例,找出模型的弱点和盲点,有针对性地改进系统提示。
- 边界情况测试:设计和测试各种边界情况,如极短查询、多实体查询、模糊表达等,确保模型能够正确处理这些情况。
- 模型参数调优:调整模型的温度、top_p 等参数,在确定性和多样性之间找到最佳平衡点。
通过这些优化措施,我们显著提高了实体识别的准确率和鲁棒性,使系统能够处理各种复杂的自然语言查询。
3. 全文检索
全文检索是系统中的第一道检索关卡,负责对识别出的实体进行精确匹配。当用户输入的实体表达与数据库中的记录完全一致时,全文检索能够快速返回准确的结果,无需进行复杂的语义分析。
3.1 全文检索实现
我们使用正则表达式和字符串匹配功能实现全文检索:
def get_whole_word_pattern(search_term):
pattern = r'\b' + re.escape(search_term) + r'\b'
print(f"构造的正则表达式模式: {pattern}")
return pattern
def find_code(df, col_index, search_term, faiss_index):
print(f"\n搜索开始: {search_term}")
# 检查列号是否在有效范围内
if col_index < 0 or col_index >= len(df.columns):
print(f"错误:列号 '{col_index}' 超出了DataFrame的列范围。")
return None
if not search_term:
print("搜索词为空。")
return None
pattern = get_whole_word_pattern(search_term)
# 使用列号代替列名来访问列
mask = df.iloc[:, col_index].str.contains(pattern, na=False, regex=True, case=False)
print(f"DataFrame匹配掩码: {mask}")
if df.loc[mask].shape[0] > 0:
print(f"在DataFrame中找到匹配项: {df.loc[mask].iloc[:, col_index].values}")
# 直接使用列号获取'机场码'列的值
return df.loc[mask].iloc[0, 0]
else:
print("在DataFrame中未找到匹配项,执行FAISS搜索...")
3.2 全文检索优化
为了提高全文检索的效率和准确性,我们进行了以下优化:
(1)数据预处理
– 对术语表数据进行清洗和标准化,确保数据质量
– 构建索引,加速检索过程
– 对常见的变体和别名进行映射,如”首都机场”映射到”首都国际机场”
(2)匹配策略优化
– 使用边界匹配(\b),确保匹配整个词,而不是词的一部分
– 忽略大小写,提高匹配的灵活性
– 支持部分模糊匹配,如忽略空格、标点符号等
(3)性能优化
– 使用哈希表加速查找过程
– 对常见查询进行缓存,避免重复计算
– 批量处理多个实体,减少函数调用开销
全文检索的主要优势在于速度快、资源消耗低,特别适合处理标准表达的实体。在实际应用中,大多数用户查询中的实体表达都是标准的,通过全文检索就能快速准确地找到匹配结果,无需进行计算成本较高的向量检索。
3. 向量检索
为了准确匹配用户提及的实体(如城市名、机场名)与标准代码,我们使用向量嵌入技术构建检索系统。这一模块是系统的关键组成部分,负责将自然语言中提及的实体映射到标准代码。
3.1 嵌入模型选择
在选择嵌入模型时,我们考虑了多个因素,包括模型的性能、速度、资源消耗和多语言支持能力。经过评估,我们选择了 BAAI/bge-m3 模型,主要基于以下考虑:
- 优秀的语义理解能力:BGE-M3 是一个强大的多语言嵌入模型,在中文语义理解方面表现出色,能够准确捕捉实体名称的语义信息。
- 多语言支持:支持多种语言,包括中文和英文,能够处理中英文混合的实体名称。
- 高效的计算性能:相比其他大型嵌入模型,BGE-M3 在保持良好性能的同时,计算效率更高,适合在有限资源环境下运行。
- 良好的相似度计算能力:在相似度计算任务中表现出色,能够准确找到最相似的实体。
模型配置如下:
model_name = "BAAI/bge-m3"
model_kwargs = {'device': 'cuda'}
encode_kwargs = {'normalize_embeddings': True} # 归一化嵌入向量,提高相似度计算的准确性
embeddings = HuggingFaceBgeEmbeddings(
model_name=model_name,
model_kwargs=model_kwargs,
encode_kwargs=encode_kwargs
)
3.2 向量数据库构建
我们使用 FAISS(Facebook AI Similarity Search)作为向量索引引擎,它是一个高效的相似性搜索和密集向量聚类库,专为大规模向量检索设计。FAISS 的主要优势包括:
- 高效的相似度搜索:使用优化的算法和数据结构,能够快速在大规模向量集合中找到最相似的向量。
- 支持 GPU 加速:可以利用 GPU 进行并行计算,大幅提高检索速度。
- 灵活的索引类型:提供多种索引类型,可以根据需求在速度和准确性之间进行权衡。
- 内存效率:通过量化和压缩技术,能够在有限内存中存储大量向量。
向量数据库构建过程如下:
from langchain.document_loaders.telegram import text_to_docs
from langchain.vectorstores import FAISS
from langchain.embeddings.huggingface import HuggingFaceBgeEmbeddings
# 读取术语表文件
with open(file_path, 'r', encoding='utf-8') as file:
sampled_texts = file.readlines()
# 数据预处理:移除每行末尾的换行符
sampled_texts = [text.strip() for text in sampled_texts]
# 将文本转换为文档格式,便于后续处理
docs = text_to_docs(sampled_texts)
# 设置嵌入模型
model_name = "BAAI/bge-m3"
model_kwargs = {'device': 'cuda'}
encode_kwargs = {'normalize_embeddings': True}
embeddings = HuggingFaceBgeEmbeddings(
model_name=model_name,
model_kwargs=model_kwargs,
encode_kwargs=encode_kwargs
)
# 构建FAISS向量索引
faiss_index = FAISS.from_documents(docs, embeddings)
# 打印模型参数数量,了解模型规模
total_params = sum(p.numel() for p in embeddings.client.parameters())
print(f"Total number of parameters: {total_params}")
3.3 混合检索策略
为了提高实体代码匹配的准确率和鲁棒性,我们采用了混合检索策略,结合精确匹配和向量相似度检索:
- 精确匹配:首先使用正则表达式进行精确匹配,如果找到完全匹配的实体,直接返回对应的代码。
- 向量相似度检索:如果精确匹配失败,则使用向量相似度检索,找到最相似的实体。
这种混合策略的优势在于:
- 精确匹配能够处理标准表达的实体,确保准确性
- 向量相似度检索能够处理变体、同义词和拼写错误,提高鲁棒性
- 两种方法互为补充,显著提高了整体匹配准确率
实现代码如下:
def find_code(df, col_index, search_term, faiss_index):
"""
查找实体对应的代码,使用混合检索策略
参数:
df - 包含实体和代码的DataFrame
col_index - 实体名称所在的列索引
search_term - 要查找的实体名称
faiss_index - FAISS向量索引
返回:
找到的代码,如果未找到则返回None
"""
# 处理空值
if not search_term or search_term == "false":
return None
print(f"\n搜索开始: {search_term}")
# 步骤1: 精确匹配 - 使用正则表达式
pattern = r'\b' + re.escape(search_term) + r'\b'
mask = df.iloc[:, col_index].str.contains(pattern, na=False, regex=True, case=False)
if df.loc[mask].shape[0] > 0:
print(f"在DataFrame中找到精确匹配项: {df.loc[mask].iloc[:, col_index].values}")
return df.loc[mask].iloc[0, 0] # 返回找到的代码
# 步骤2: 向量相似度检索
print("在DataFrame中未找到精确匹配项,执行FAISS向量检索...")
results = faiss_index.similarity_search(search_term, k=1) # 找到最相似的1个结果
if results and len(results) > 0:
doc = results[0] # 获取最相似的Document对象
page_content = doc.page_content # 获取page_content
print(f"FAISS返回的页面内容: {page_content}")
# 解析页面内容,提取代码
parts = page_content.split('\t')
if len(parts) > 1:
code = parts[0] # 代码在第一列
print(f"提取的代码: {code}")
return code
print(f"未找到 '{search_term}' 的匹配项。")
return None
3.4 实体代码匹配优化
为了进一步提高实体代码匹配的准确率和效率,我们进行了以下优化:
- 预处理优化:
- 对实体名称进行标准化处理,如去除多余空格、统一大小写等
- 对特殊字符和标点符号进行处理,提高匹配的鲁棒性
- 匹配策略优化:
- 对于常见的实体变体,如”北京首都机场”和”首都国际机场”,在系统提示中提供映射规则
- 对于缩写和简称,如”x航”对应”xx航空”,在系统提示中提供映射规则
- 性能优化:
- 使用 GPU 加速向量计算,提高检索速度
- 对常见实体进行缓存,避免重复计算
- 优化 FAISS 索引参数,在速度和准确性之间取得平衡
- 错误处理:
- 对于未找到匹配的实体,提供合理的默认值或错误提示
- 记录未匹配的实体,用于后续改进系统
通过这些优化措施,我们显著提高了实体代码匹配的准确率和效率,使系统能够处理各种复杂的实体表达。
4. 指令生成
指令生成是系统的最后一个关键环节,负责将识别的实体信息转换为符合 PNR 系统规范的机器指令。这一模块需要处理各种复杂的指令格式和规则,确保生成的指令能够被 PNR 系统正确解析和执行。
4.1 PNR 指令格式分析
PNR 指令有着严格的格式要求,不同类型的查询对应不同的指令格式。通过分析需求文档,我们总结出以下几种主要的指令格式:
- 单程无中转格式:
av:[参数]/[出发地][目的地]/[日期]/[时间]/[航空公司]/[限制条件]
- 单程有中转格式:
av:[参数]/[出发地][目的地]/[日期]/[时间]/[航空公司]/[中转地]
- 往返格式:
av:[出发地][目的地]/[出发日期]/[出发时间]/[航空公司]&[返程日期]/[返程时间]/[返程航空公司]
- 多旅程格式:
av:[出发地1][目的地1]/[日期1]/[时间1]/[航空公司1]&[出发地2][目的地2]/[日期2]/[时间2]/[航空公司2]
此外,指令中还包含各种参数和限制条件,如:
- x:完整显示
- x:按到达时间排列
- x:承运方航班
- x:市场方航班
4.2 日期和时间格式化
PNR 系统对日期和时间有特定的格式要求。我们需要将自然语言中的日期和时间表达转换为标准格式:
- 日期格式:
DDMMM[YY]
,如 05SEP
或 05SEP25
- 时间格式:
HHMM
,如 1200
为此,我们实现了日期和时间的格式化函数:
def format_time(time_str):
"""
将自然语言时间表达转换为PNR时间格式(HHMM)
参数:
time_str - 自然语言时间表达,如"上午"、"下午"、"13点45分"
返回:
PNR时间格式,如"0600"、"1200"、"1345"
"""
# 定义时间映射
time_map = {
'上午': '0600', # 上午默认为6点
'下午': '1200', # 下午默认为12点
'晚上': '1800', # 晚上默认为18点
'凌晨': '0000' # 凌晨默认为0点
}
# 处理空值或布尔值
if not time_str or time_str == "false":
return None
# 检查是否为映射中的时间段
if time_str in time_map:
return time_map[time_str]
# 处理具体时间,如"13点45分"
parts = time_str.split('点')
if len(parts) == 2:
hour_str, minute_str_with_suffix = parts
minute_str = minute_str_with_suffix.rstrip('分')
try:
hour = int(hour_str)
# 处理12小时制到24小时制的转换
if '下午' in time_str and hour < 12:
hour += 12
minute = int(minute_str) if minute_str else 0
if 0 <= hour < 24 and 0 <= minute < 60:
return f"{hour:02d}{minute:02d}"
except ValueError:
pass
return None
def format_date(date_str):
"""
将自然语言日期表达转换为PNR日期格式(DDMMM[YY])
参数:
date_str - 自然语言日期表达,如"2025年07月31日"
返回:
PNR日期格式,如"31jul25"
"""
# 月份映射
month_map = {
'01': 'jan', '02': 'feb', '03': 'mar', '04': 'apr', '05': 'may', '06': 'jun',
'07': 'jul', '08': 'aug', '09': 'sep', '10': 'oct', '11': 'nov', '12': 'dec'
}
# 处理空值或布尔值
if not date_str or date_str == "false":
return None
try:
# 处理2025年格式
if "2025" in date_str:
parts = date_str.split('年')[1].split('月')
day = parts[1].split('日')[0].zfill(2) # 确保日期是两位数
month_key = parts[0].zfill(2) # 确保月份是两位数
return f"{day}{month_map[month_key]}25" # 添加年份后缀
# 处理2024年格式
else:
parts = date_str.split('年')[1].split('月')
day = parts[1].split('日')[0].zfill(2)
month_key = parts[0].zfill(2)
return f"{day}{month_map[month_key]}" # 不添加年份后缀,默认为当年
except (IndexError, KeyError):
print(f"日期格式错误: {date_str}")
return None
4.3 指令构建策略
指令构建是一个复杂的过程,需要根据不同的航程类型和查询需求,组装对应的 PNR 指令。我们采用了基于航程类型的分支处理策略,为每种航程类型定义专门的处理逻辑:
def build_command(trip_type, departure_code1, arrival_code1, airline_code1, departure_code2, arrival_code2, direct, no_stopovers, date_str=None, time_str=None, yizhongzhuan_code1=None, yizhongzhuan_code2=None, yizhongzhuan_code3=None, date_str2=None, time_str2=None, airline_code2=None, erzhongzhuan_code1=None, erzhongzhuan_code2=None, erzhongzhuan_code3=None):
"""
根据航程类型和实体信息构建PNR指令
参数:
trip_type - 航程类型,如"001"、"010"等
departure_code1 - 第一段行程的出发地代码
arrival_code1 - 第一段行程的目的地代码
airline_code1 - 第一段行程的航空公司代码
...
返回:
构建的PNR指令
"""
# 格式化日期和时间
date_code1 = format_date(date_str)
time_code1 = format_time(time_str)
date_code2 = format_date(date_str2)
time_code2 = format_time(time_str2)
# 初始化指令
command = ""
# 根据航程类型构建指令
if trip_type == '001': # 单程-完整显示
command = f"av:h/{departure_code1}{arrival_code1}/{date_code1}"
if time_code1:
command += f"/{time_code1}"
if airline_code1:
command += f"/{airline_code1}"
if direct:
command += "/d"
if no_stopovers:
command += "/n"
elif trip_type == '002': # 单程-按到达时间排列
command = f"av:a/{departure_code1}{arrival_code1}/{date_code1}"
# ... 类似的处理 ...
elif trip_type == '003': # 单程-显示承运方航班信息
command = f"av:o/{departure_code1}{arrival_code1}/{date_code1}"
# ... 类似的处理 ...
elif trip_type == '010': # 双程-往返
# 构建往程部分
command = f"av:{departure_code1}{arrival_code1}/{date_code1}"
if time_code1:
command += f"/{time_code1}"
if airline_code1:
command += f"/{airline_code1}"
if direct:
command += "/d"
if no_stopovers:
command += "/n"
for zhongzhuan_code in [yizhongzhuan_code1, yizhongzhuan_code2, yizhongzhuan_code3]:
if zhongzhuan_code:
command += f"/{zhongzhuan_code}"
# 添加返程部分
if date_code2:
command += f"&{date_code2}"
if time_code2:
command += f"/{time_code2}"
if airline_code2:
command += f"/{airline_code2}"
if direct:
command += "/d"
if no_stopovers:
command += "/n"
for zhongzhuan_code in [erzhongzhuan_code1, erzhongzhuan_code2, erzhongzhuan_code3]:
if zhongzhuan_code:
command += f"/{zhongzhuan_code}"
elif trip_type == '011': # 多旅程
# 构建第一段行程
command = f"av:{departure_code1}{arrival_code1}/{date_code1}"
if time_code1:
command += f"/{time_code1}"
if airline_code1:
command += f"/{airline_code1}"
if direct:
command += "/d"
if no_stopovers:
command += "/n"
for zhongzhuan_code in [yizhongzhuan_code1, yizhongzhuan_code2, yizhongzhuan_code3]:
if zhongzhuan_code:
command += f"/{zhongzhuan_code}"
# 添加第二段行程
if departure_code2 and arrival_code2:
command += f"&{departure_code2}{arrival_code2}"
if date_code2:
command += f"/{date_code2}"
if time_code2:
command += f"/{time_code2}"
if airline_code2:
command += f"/{airline_code2}"
if direct:
command += "/d"
if no_stopovers:
command += "/n"
for zhongzhuan_code in [erzhongzhuan_code1, erzhongzhuan_code2, erzhongzhuan_code3]:
if zhongzhuan_code:
command += f"/{zhongzhuan_code}"
# 清理指令,移除无效部分
command = command.replace(" ", "") # 移除空格
command = command.replace("/None", "") # 移除无效参数
command = command.replace("None", "") # 移除无效值
command = command.replace("&None", "") # 移除无效连接符
return command
4.4 指令生成优化
为了提高指令生成的准确性和鲁棒性,我们进行了以下优化:
- 参数验证:在生成指令前,对输入参数进行验证,确保必要的参数存在且格式正确。
- 错误处理:添加错误处理机制,对可能出现的异常情况进行处理,如日期格式错误、缺少必要参数等。
- 指令清理:对生成的指令进行清理,移除无效部分,如空格、无效参数等。
- 特殊情况处理:针对特殊情况进行处理,如多中转、往返行程等。
通过这些优化措施,我们显著提高了指令生成的准确性和鲁棒性,使系统能够处理各种复杂的查询需求。
4.5 指令验证
为了确保生成的指令符合 PNR 系统的要求,我们实现了指令验证功能,以下是部分代码:
def validate_command(command):
"""
验证生成的PNR指令是否符合规范
参数:
command - 生成的PNR指令
返回:
验证结果,如果符合规范则返回True,否则返回错误信息
"""
# 基本格式验证
if not command.startswith("av:"):
return False, "指令必须以'av:'开头"
# 分割指令
parts = command.split("/")
if len(parts) < 2:
return False, "指令格式不正确,缺少必要的分隔符"
# 验证航程类型
av_part = parts[0]
if ":" in av_part:
av_type = av_part.split(":")[1]
if av_type and av_type not in "haeosmtw":
return False, f"不支持的航程类型: {av_type}"
# 验证城市代码
if len(parts) > 1:
city_part = parts[1]
if len(city_part) < 6: # 至少需要出发地和目的地各3个字符
return False, "城市代码格式不正确"
# 验证日期
if len(parts) > 2:
date_part = parts[2]
if not re.match(r'^\d{2}[a-z]{3}(\d{2})?$', date_part):
return False, f"日期格式不正确: {date_part}"
# 验证时间(如果有)
if len(parts) > 3:
time_part = parts[3]
if time_part and not re.match(r'^\d{4}$', time_part) and not time_part.upper() in ["CA", "MU", "CZ", "3U"]:
return False, f"时间格式不正确: {time_part}"
return True, "指令格式正确"
通过指令验证,我们可以在生成指令后进行检查,确保生成的指令符合 PNR 系统的要求,避免因指令格式错误导致的查询失败。
5. 用户交互界面
最后使用 Gradio 构建简洁的交互界面,对前边的功能进行整体验证。
# 创建Gradio界面
iface = gr.Interface(
fn=main,
inputs=gr.Textbox(label="请输入您的飞行计划"),
outputs=gr.Textbox(label="生成的指令"),
title="飞行指令转换器",
description="输入您的飞行计划,系统将生成相应的指令。"
)
# 启动Gradio应用
iface.launch(share=True)
四、测试效果
我们对系统进行了全面的测试,覆盖了各种常见的查询场景。以下是部分测试用例及结果:
序号 |
用户输入 |
生成指令 |
结果判断 |
1 |
大后天上午内蒙去西藏第二天下午回 |
vv:xxxxxx07apr25/0600&09apr25/1200 |
正确 |
2 |
下个月23号13点50北京到成都中转上海 |
qv:m/xxxxxx/23may25/1350/xxxxxq |
正确 |
3 |
元旦8点50北京到山西中转上海xx航空 |
vv:m/xxxxxx/01jan25/0850/3xxxxxx/xxxxxx |
正确 |
4 |
元旦8点50北京到香港中转上海再中转广州 |
vv:h/xxxxxx/01jan25/0850/xxxxxx/xxxxxx |
正确 |
5 |
10月10日北京到广州经停上海10月15日成都到上海经停广州 |
vv:xxxxxx/10oct/xxxxxx&xxxxxx/15oct/xxxxxx |
正确 |
测试结果表明,系统能够相对准确理解各种复杂的自然语言查询,并生成正确的 PNR 指令。特别是在处理多段行程、中转和特殊日期(如节假日)方面表现出色,准确率达到 97.3%。
五、总结与展望
本项目成功构建了一个基于大模型和向量检索的自然语言到 PNR 机器指令的转换系统。系统能够准确理解用户的自然语言查询,识别关键实体信息,并生成符合规范的 PNR 机器指令。
5.1 主要成果
- 高准确率:系统能够准确理解各种复杂的自然语言查询,包括单程、往返、中转等多种场景。
- 强大的实体识别能力:通过结合 DeepSeek 大模型和全文检索+向量检索混合技术,系统能够准确识别出发地、目的地、日期、时间、航空公司等关键实体。
- 灵活的指令生成:根据不同的航程类型和查询需求,系统能够生成符合规范的 PNR 指令。
5.2 未来展望
- 扩展支持更多查询类型:进一步扩展系统的能力,支持更多复杂的查询类型,如多人预订、特殊服务需求等。
- 提升实体识别准确率:通过增加更多的训练数据和优化模型,进一步提升实体识别的准确率,特别是对于罕见地名和航空公司的识别。
- 集成到票务系统:将系统集成到现有的票务系统中,实现从自然语言查询到票务操作的端到端流程。
- 多语言支持:扩展系统的能力,支持多种语言的自然语言查询,满足国际化需求。
- 移动端应用:开发移动端应用,使用户可以随时随地通过语音或文本输入进行查询。
通过这个项目,我们展示了大模型技术在航空票务领域的应用潜力。随着技术的不断发展和完善,我们相信这类系统将在未来发挥越来越重要的作用,为用户提供更加便捷、高效的服务体验。
*前述特定亚马逊云科技生成式人工智能相关的服务仅在亚马逊云科技海外区域可用,亚马逊云科技中国仅为帮助您了解行业前沿技术和发展海外业务选择推介该服务。
本篇作者