Compare commits

..

2 Commits

Author SHA1 Message Date
ly be98bb1495 更新最新代码 2025-07-04 13:52:08 +08:00
ly af64d962eb 合并 2025-07-02 10:32:38 +08:00
52 changed files with 139990 additions and 5794 deletions
+4 -6
View File
@@ -1,7 +1,5 @@
BoweiAgent.log
*.log
__pycache__/*
.vscode/launch.json
src/__pycache__/*
.cursor/rules/use.mdc
test_runcode.log
BoweiAgent1.log
.vscode/*
.cursor/*
logs/*
+25
View File
@@ -8,8 +8,21 @@ neo4j:
business_object_structure_path: ./data/business_object_structure.md
bowei_api_docs_path: ./data/bowei_api_docs.md
max_retries: 0
openai:
api_keys:
- sk-gycpzfjfxldpmqmqvrtgtvtpusnsrscowhscqfbppmjdlzoi
- sk-lhvfliriyohnaspzuoddgulmdiovpbjcdmfdfdvghapprfru
- sk-tzxcxzberpdpvszhjfyikepjlgslbtgssypxpmwllgithtwz
- sk-ybhbyjkzdydsllxredzntqdyrderxighdieiayudcubvhxsw
- sk-xtjzktvgrkzikkvetzygzozfyjxzgasednowyjqjeudfqiha
- sk-ufjdohciujtmpfwrrqhiocufexdzqpiotnjwzhbumcpvkpcw
- sk-nvnuzkdgxwzvibtuymwhatfajnerekgsbieqmqadjaqyhkob
- sk-erdnqkazveyiaojrpojawtwlxpnrikhedurlphmyviaixyrg
- sk-xhddshwxrhejbzitozgieightaecdeozqzdgypmedzdydthr
- sk-czilnjaxqwicwoyvfaxgzvqcjhmdzpwvzhbxvnqnvaeocojk
api_key: sk-xlrnesfcuwrpevdwbuhthivpygwyzwbxxsyvhzzwrkpzjduk
api_base: https://api.siliconflow.cn/v1
#api_version: "" # 可选,某些API版本需要指定
@@ -20,6 +33,18 @@ openai:
#model_name: deepseek-ai/DeepSeek-V3
openai_coder:
api_keys:
- sk-gycpzfjfxldpmqmqvrtgtvtpusnsrscowhscqfbppmjdlzoi
- sk-lhvfliriyohnaspzuoddgulmdiovpbjcdmfdfdvghapprfru
- sk-tzxcxzberpdpvszhjfyikepjlgslbtgssypxpmwllgithtwz
- sk-ybhbyjkzdydsllxredzntqdyrderxighdieiayudcubvhxsw
- sk-xtjzktvgrkzikkvetzygzozfyjxzgasednowyjqjeudfqiha
- sk-ufjdohciujtmpfwrrqhiocufexdzqpiotnjwzhbumcpvkpcw
- sk-nvnuzkdgxwzvibtuymwhatfajnerekgsbieqmqadjaqyhkob
- sk-erdnqkazveyiaojrpojawtwlxpnrikhedurlphmyviaixyrg
- sk-xhddshwxrhejbzitozgieightaecdeozqzdgypmedzdydthr
- sk-czilnjaxqwicwoyvfaxgzvqcjhmdzpwvzhbxvnqnvaeocojk
api_key: sk-xlrnesfcuwrpevdwbuhthivpygwyzwbxxsyvhzzwrkpzjduk
api_base: https://api.siliconflow.cn/v1
#api_version: "" # 可选,某些API版本需要指定
+5 -215
View File
@@ -17,6 +17,7 @@ class ProjectToolkit(ABC):
self.project_division_set = ProjectDivisionItem() # 项目划分集对象
# 项目划分查询方法
@abstractmethod
def get_division_by_name(self, name):
"""
@@ -37,7 +38,6 @@ class ProjectToolkit(ABC):
"""
pass
# 项目划分查询方法
@abstractmethod
def get_division_item_by_path(self, path):
"""
@@ -58,7 +58,6 @@ class ProjectToolkit(ABC):
"""
pass
# 项目划分查询方法
@abstractmethod
def get_division_node_by_parent_and_name(self, parent_path, partial_name):
"""
@@ -95,7 +94,6 @@ class ProjectToolkit(ABC):
"""
pass
# 工程量查询方法
@abstractmethod
def get_quantities_node_by_parent_and_code(self, parent_path, quantity_type=None, code=None):
"""
@@ -115,7 +113,6 @@ class ProjectToolkit(ABC):
"""
pass
# 工程量查询方法
@abstractmethod
def get_quantities_node_by_parent_and_name(self, parent_path, partial_name, quantity_type=None):
"""
@@ -155,13 +152,13 @@ class ProjectToolkit(ABC):
pass
@abstractmethod
def get_material_equipment_by_parent_and_code(self, parent_path, code):
def get_material_equipment_by_parent_and_name(self, parent_path, partial_name):
"""
通过父节点路径和模糊名称获取材机对象
Args:
parent_path (str): 父节点的路径,以'/'分隔的多级节点路径
code (str): 目标节点编码
partial_name (str): 目标节点的模糊或不完整名称
Returns:
Tuple[str, Dict[str, Any], str, List[Any]]: 包含状态、数据、错误信息和辅助信息的元组
@@ -192,7 +189,6 @@ class ProjectToolkit(ABC):
"""
pass
# 取费查询方法
@abstractmethod
def get_fee_template_by_parent_and_name(self, parent_path, partial_name):
"""
@@ -200,7 +196,7 @@ class ProjectToolkit(ABC):
Args:
parent_path (str): 父节点的路径,以'/'分隔的多级节点路径
node_name (str): 目标节点的名称
node_name (str): 目标节点的名称,例如:直接费
Returns:
Tuple[str, Dict[str, Any], str, List[Any]]: 包含状态、数据、错误信息和辅助信息的元组
@@ -238,7 +234,6 @@ class ProjectToolkit(ABC):
"""
pass
# 费用表查询方法
@abstractmethod
def get_fee_schedule_on_other_expense_table(self, table_name, fee_name, fee):
"""
@@ -262,7 +257,6 @@ class ProjectToolkit(ABC):
"""
pass
# 费用表查询方法
@abstractmethod
def get_fee_schedule_on_land_acquisition_fee_table_table(self, table_name, fee_name, fee):
"""
@@ -286,7 +280,6 @@ class ProjectToolkit(ABC):
"""
pass
# 费用表查询方法
@abstractmethod
def get_fee_schedule_on_installation_price_difference_table(self, table_name, fee_name, fee):
"""
@@ -310,11 +303,10 @@ class ProjectToolkit(ABC):
"""
pass
# 费用表查询方法
@abstractmethod
def get_fee_schedule_on_Engineering_Cost_table(self, table_name, fee_name, fee):
"""
在工程费用表中查找费用
在工程费用表中查找费用
Args:
table_name (str): 费用表名称
@@ -334,147 +326,6 @@ class ProjectToolkit(ABC):
"""
pass
# 工程属性查询方法
@abstractmethod
def get_project_property(self, property_name):
"""
通过属性名称获取工程属性
Args:
property_name (str): 属性名称
Returns:
Tuple[str, Dict[str, Any], str, List[Any]]: 包含状态、数据、错误信息和辅助信息的元组
- status (str): 状态,'success'或'error'
- data (Dict[str, Any]): 成功时返回包含属性值的数据
- error (str): 错误时的错误信息
- helper_info (List[Any]): 辅助信息列表
"""
pass
# 项目划分节点下查找费用预览中的费用
@abstractmethod
def get_fee_table_by_project_division(self, project_division_path, fee_name):
"""
通过项目划分名称和费用名称在费用预览中查找费用
Args:
project_division_path (str): 项目划分节点路径
fee_name (str): 费用名称
Returns:
Tuple[str, Dict[str, Any], str, List[Any]]: 包含状态、数据、错误信息和辅助信息的元组
- status (str): 状态,'success'或'error'
- data (Dict[str, Any]): 成功时返回包含属性值的数据
- error (str): 错误时的错误信息
- helper_info (List[Any]): 辅助信息列表
"""
pass
# 清单查找取费表
@abstractmethod
def get_fee_table_by_list(self, parent_path, list_code, list_unit, fee_name):
"""
通过清单名称和取费名称查找费用
Args:
parent_path (str): 父级路径
list_code (str): 清单编号
list_unit (str): 清单单位
fee_name (str): 取费名称
Returns:
Tuple[str, Dict[str, Any], str, List[Any]]: 包含状态、数据、错误信息和辅助信息的元组
- status (str): 状态,'success'或'error'
- data (Dict[str, Any]): 成功时返回包含属性值的数据
- error (str): 错误时的错误信息
- helper_info (List[Any]): 辅助信息列表
"""
pass
# 定额节点查找合价
@abstractmethod
def get_fee_table_by_quoto_code(self, parent_path, quantity_type, code, fee_name):
"""
通过父级路径、工程量类型、定额编码和取费名称查找费用
Args:
parent_path (str): 父级路径
quantity_type (str): 工程量类型
code (str): 定额编码
fee_name (str): 取费名称
Returns:
Tuple[str, Dict[str, Any], str, List[Any]]: 包含状态、数据、错误信息和辅助信息的元组
- status (str): 状态,'success'或'error'
- data (Dict[str, Any]): 成功时返回包含属性值的数据
- error (str): 错误时的错误信息
- helper_info (List[Any]): 辅助信息列表
"""
pass
# 工程量节点查找合价
@abstractmethod
def get_fee_table_by_quantities_name(self, parent_path, quantity_type, quantity_name, fee_name):
"""
通过父级路径、工程量类型、工程量名称和取费名称查找费用
Args:
parent_path (str): 父级路径
quantity_type (str): 工程量类型
quantity_name (str): 工程量名称
fee_name (str): 取费名称
Returns:
Tuple[str, Dict[str, Any], str, List[Any]]: 包含状态、数据、错误信息和辅助信息的元组
- status (str): 状态,'success'或'error'
- data (Dict[str, Any]): 成功时返回包含属性值的数据
- error (str): 错误时的错误信息
- helper_info (List[Any]): 辅助信息列表
"""
pass
# 材机节点查找市场价
@abstractmethod
def get_fee_by_material_equipment_code(self, parent_path, material_equipment_code, fee_name):
"""
通过父级路径、材机编码和取费名称查找费用
Args:
parent_path (str): 父级路径
material_equipment_code (str): 材机编码
fee_name (str): 取费名称
Returns:
Tuple[str, Dict[str, Any], str, List[Any]]: 包含状态、数据、错误信息和辅助信息的元组
- status (str): 状态,'success'或'error'
- data (Dict[str, Any]): 成功时返回包含属性值的数据
- error (str): 错误时的错误信息
- helper_info (List[Any]): 辅助信息列表
"""
pass
# 查找清单节点
@abstractmethod
def get_fee_table_by_list_name(self, parent_path, list_code, unit):
"""
通过父级路径、清单编号和单位查找清单
Args:
parent_path (str): 父级路径
list_code (str): 清单编号
unit (str): 单位
Returns:
Tuple[str, Dict[str, Any], str, List[Any]]: 包含状态、数据、错误信息和辅助信息的元组
- status (str): 状态,'success'或'error'
- data (Dict[str, Any]): 成功时返回包含属性值的数据
- error (str): 错误时的错误信息
- helper_info (List[Any]): 辅助信息列表
"""
pass
class ProjectDivisionSet:
"""
@@ -544,28 +395,6 @@ class ProjectQuantity:
self.cost_set = None # xsd:CostSet
class CostItem:
"""
费用预览集
描述: 代表一个费用预览集,通常包含多个费用预览项
"""
def __init__(self):
self.GUID = None # xsd:string
self.name = None # xsd:string
class CostSet:
"""
费用预览项
描述: 代表一个费用预览
"""
def __init__(self):
self.cost = None # xsd:string
self.name = None # xsd:string
class Ration(ProjectQuantity):
"""
定额
@@ -748,42 +577,3 @@ class Fee:
self.施工费 = None # xsd:string (可选)
self.单位投资 = None # xsd:string (可选)
class ProjectBuilder:
# 存储注册的工具类
_registry = None
_config = {}
"""项目工具工厂类"""
def __init__(self):
pass
@classmethod
def register(cls, toolkit_class: type, config: dict):
"""
注册工具类到工厂
参数:
toolkit_class: 继承自ProjectToolkit的具体工具类
"""
if not issubclass(toolkit_class, ProjectToolkit):
raise TypeError(f"{toolkit_class.__name__} 必须继承自 ProjectToolkit")
cls._config = config
cls._registry = toolkit_class
@classmethod
def build(cls) -> ProjectToolkit:
"""
创建工具实例
参数:
返回:
实例化的工具对象
"""
if cls._registry is None:
raise KeyError(f"未注册的类,请先注册类")
return cls._registry(cls._config)
+235 -123
View File
@@ -24,17 +24,14 @@ class ProjectToolkit(ABC):
通过名称获取项目划分对象
Args:
path (str): 项目划分节点名称
name_part (str): 项目划分节点名称
Returns:
Tuple[str, Dict[str, Any], str, List[Any]]: 包含状态、数据、错误信息和辅助信息的元组
- status (str): 状态,'success'或'error'
- data (Dict[str, Any]): 成功时返回的项目划分对象数据
- error (str): 错误时的错误信息
- helper_info (List[Any]): 辅助信息列表,如果未找到节点,则包含父节点下所有ProjectDivisionItem节点
Note:
当路径为空字符串时,会返回None
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (List[Dict[str, Any]]): 成功时返回的数据列表,失败时为空列表
"""
pass
@@ -47,14 +44,11 @@ class ProjectToolkit(ABC):
path (str): 项目划分节点的路径,以'/'分隔的多级节点路径
Returns:
Tuple[str, Dict[str, Any], str, List[Any]]: 包含状态、数据、错误信息和辅助信息的元组
- status (str): 状态,'success'或'error'
- data (Dict[str, Any]): 成功时返回的项目划分对象数据
- error (str): 错误时的错误信息
- helper_info (List[Any]): 辅助信息列表,如果未找到节点,则包含父节点下所有ProjectDivisionItem节点
Note:
当路径为空字符串时,会返回None
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Dict[str, Any]): 成功时返回的项目划分对象数据,失败时为空字典
"""
pass
@@ -68,11 +62,11 @@ class ProjectToolkit(ABC):
partial_name (str): 目标节点的模糊或不完整名称
Returns:
Tuple[str, List[Dict[str, Any]], str, List[Any]]: 包含状态、数据、错误信息和辅助信息的元组
- status (str): 状态,'success'或'error'
- data (List[Dict[str, Any]]): 成功时返回的数据列表
- error (str): 错误时的错误信息
- helper_info (List[Any]): 辅助信息列表
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (List[Dict[str, Any]]): 成功时返回的数据列表,失败时为空列表
"""
pass
@@ -86,11 +80,11 @@ class ProjectToolkit(ABC):
paths_str (str): 以'/'分隔的多级节点路径
Returns:
Tuple[str, Dict[str, Any], str, List[Any]]:
- status: 'success'或'error'
- data: 成功时返回的节点数据字典,失败时为{}
- error: 错误信息,成功时为''
- helper_info: 辅助信息列表(当目标节点不存在时返回父节点下的所有ProjectQuantity节点名称)
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Dict[str, Any]): 成功时返回的节点数据字典,失败时为空字典 {}
"""
pass
@@ -105,11 +99,11 @@ class ProjectToolkit(ABC):
code (str): 工程量编码,以'/'分隔的多个编码
Returns:
Tuple[str, List[Dict[str, Any]], str, List[Any]]: 包含状态、数据、错误信息和辅助信息的元组
- status (str): 状态,'success'或'error'
- data (List[Dict[str, Any]]): 成功时返回的数据列表
- error (str): 错误时的错误信息
- helper_info (List[Any]): 辅助信息列表
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Union[List[Dict[str, Any]], Dict]): 成功时返回的数据列表或字典,失败时为空列表 []
"""
pass
@@ -121,14 +115,14 @@ class ProjectToolkit(ABC):
Args:
parent_path (str): 父节点的路径,以'/'分隔的多级节点路径
partial_name (str): 目标节点的模糊或不完整名称
quantity_type (str): 工程量类型('定额'、'主材'、'设备'或None表示所有类型)
quantity_type (str): 工程量类型('定额'、'主材'、'设备')
Returns:
Tuple[str, List[Dict[str, Any]], str, List[Any]]: 包含状态、数据、错误信息和辅助信息的元组
- status (str): 状态,'success'或'error'
- data (List[Dict[str, Any]]): 成功时返回的数据列表
- error (str): 错误时的错误信息
- helper_info (List[Any]): 辅助信息列表
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Union[List[Dict[str, Any]], Dict]): 成功时返回的数据列表,失败时为空列表 []
"""
pass
@@ -142,31 +136,29 @@ class ProjectToolkit(ABC):
path (str): 项目划分节点的路径,以'/'分隔的多级节点路径
Returns:
Tuple[str, Dict[str, Any], str, List[Any]]: 包含状态、数据、错误信息和辅助信息的元组
- status (str): 状态,'success'或'error'
- data (Dict[str, Any]): 成功时返回的节点属性数据
- error (str): 错误时的错误信息
- helper_info (List[Any]): 辅助信息列表:
- 如果未找到目标节点,则包含父节点下所有 MaterialOrEquipment 节点名称
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Union[Dict, List]): 成功时返回的节点属性数据字典,失败时为空字典 {}
"""
pass
@abstractmethod
def get_material_equipment_by_parent_and_name(self, parent_path, partial_name):
def get_material_equipment_by_parent_and_code(self, parent_path, code):
"""
通过父节点路径和模糊名称获取材机对象
通过父节点路径和编码获取材机对象
Args:
parent_path (str): 父节点的路径,以'/'分隔的多级节点路径
partial_name (str): 目标节点的模糊或不完整名称
parent_path (str): 父节点的部分路径,以'/'分隔的多级节点路径
code (str): 目标节点的编码属性值
Returns:
Tuple[str, Dict[str, Any], str, List[Any]]: 包含状态、数据、错误信息和辅助信息的元组
- status (str): 状态,'success'或'error'
- data (Dict[str, Any]): 成功时返回的节点属性数据
- error (str): 错误时的错误信息
- helper_info (List[Any]): 辅助信息列表:
- 如果未找到目标节点,则包含父节点下所有 MaterialOrEquipment 节点名称
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Union[Dict, List]): 成功时返回的节点属性数据字典,失败时为空字典 {}
"""
pass
@@ -174,39 +166,37 @@ class ProjectToolkit(ABC):
@abstractmethod
def get_fee_template_by_path(self, paths_str):
"""
通过路径获取取费节点
通过路径获取取费表模板节点
Args:
path (str): 项目划分节点的路径,以'/'分隔的多级节点路径
例如:工程数据/取费表模板/余物清理/线路取费表(余物清理)/直接费
Returns:
Tuple[str, Dict[str, Any], str, List[Any]]: 包含状态、数据、错误信息和辅助信息的元组
- status (str): 状态,'success'或'error'
- data (Dict[str, Any]): 成功时返回的节点属性数据
- error (str): 错误时的错误信息
- helper_info (List[Any]): 辅助信息列表:
- 如果未找到目标节点,则包含父节点下所有 FeeCollection 节点名称
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Union[Dict, List]): 成功时返回的节点属性数据字典,失败时为空字典 {}
"""
pass
@abstractmethod
def get_fee_template_by_parent_and_name(self, parent_path, partial_name):
"""
通过父节点路径和节点名称获取取费节点
通过父节点路径和节点名称获取取费表模板节点
Args:
parent_path (str): 父节点的路径,以'/'分隔的多级节点路径
例如:工程数据/取费表模板/余物清理/线路取费表(余物清理)
node_name (str): 目标节点的名称,例如:直接费
Returns:
Tuple[str, Dict[str, Any], str, List[Any]]: 包含状态、数据、错误信息和辅助信息的元组
- status (str): 状态,'success'或'error'
- data (Dict[str, Any]): 成功时返回的节点属性数据
- error (str): 错误时的错误信息
- helper_info (List[Any]): 辅助信息列表:
- 如果未找到父节点,则包含父节点的父节点下所有节点名称
- 如果未找到目标节点,则包含父节点下所有 FeeCollection 节点名称
- 否则为空
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Union[Dict, List]): 成功时返回的节点属性数据字典,失败时为空字典 {}
"""
pass
@@ -222,15 +212,11 @@ class ProjectToolkit(ABC):
fee_attribute (str): 费用值属性名
Returns:
Tuple[str, Dict[str, Any], str, List[Any]]: 包含状态、数据、错误信息和辅助信息的元组
- status (str): 状态,'success'或'error'
- data (Dict[str, Any]): 成功时返回包含属性值的数据
- error (str): 错误时的错误信息
- helper_info (List[Any]): 辅助信息列表:
- 第一步失败:返回"工程数据"节点下所有子节点名称
- 第二步失败:返回费用表节点下所有子节点名称(递归查找)
- 第三步失败:返回费用节点的所有可用属性名称
- 否则为空
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Union[Dict, List, Any]): 成功时返回属性值(可能是 str/int/float 等),失败时返回 {}
"""
pass
@@ -245,22 +231,18 @@ class ProjectToolkit(ABC):
fee_attribute (str): 费用值属性名
Returns:
Tuple[str, Dict[str, Any], str, List[Any]]: 包含状态、数据、错误信息和辅助信息的元组
- status (str): 状态,'success'或'error'
- data (Dict[str, Any]): 成功时返回包含属性值的数据
- error (str): 错误时的错误信息
- helper_info (List[Any]): 辅助信息列表:
- 第一步失败:返回"工程数据"节点下所有子节点名称
- 第二步失败:返回费用表节点下所有子节点名称(递归查找)
- 第三步失败:返回费用节点的所有可用属性名称
- 否则为空
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Union[Dict, List, Any]): 成功时返回属性值(可能是 str/int/float 等),失败时返回 {}
"""
pass
@abstractmethod
def get_fee_schedule_on_land_acquisition_fee_table_table(self, table_name, fee_name, fee):
"""
在其中:地征用费表中查找费用
在其中:地征用费表中查找费用
Args:
table_name (str): 费用表名称
@@ -268,22 +250,18 @@ class ProjectToolkit(ABC):
fee_attribute (str): 费用值属性名
Returns:
Tuple[str, Dict[str, Any], str, List[Any]]: 包含状态、数据、错误信息和辅助信息的元组
- status (str): 状态,'success'或'error'
- data (Dict[str, Any]): 成功时返回包含属性值的数据
- error (str): 错误时的错误信息
- helper_info (List[Any]): 辅助信息列表:
- 第一步失败:返回"工程数据"节点下所有子节点名称
- 第二步失败:返回费用表节点下所有子节点名称(递归查找)
- 第三步失败:返回费用节点的所有可用属性名称
- 否则为空
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Union[Dict, List, Any]): 成功时返回属性值(可能是 str/int/float 等),失败时返回 {}
"""
pass
@abstractmethod
def get_fee_schedule_on_installation_price_difference_table(self, table_name, fee_name, fee):
"""
在安装价差表中查找费用
在安装价差费用表中查找费用
Args:
table_name (str): 费用表名称
@@ -291,15 +269,11 @@ class ProjectToolkit(ABC):
fee_attribute (str): 费用值属性名
Returns:
Tuple[str, Dict[str, Any], str, List[Any]]: 包含状态、数据、错误信息和辅助信息的元组
- status (str): 状态,'success'或'error'
- data (Dict[str, Any]): 成功时返回包含属性值的数据
- error (str): 错误时的错误信息
- helper_info (List[Any]): 辅助信息列表:
- 第一步失败:返回"工程数据"节点下所有子节点名称
- 第二步失败:返回费用表节点下所有子节点名称(递归查找)
- 第三步失败:返回费用节点的所有可用属性名称
- 否则为空
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Union[Dict, List, Any]): 成功时返回属性值(可能是 str/int/float 等),失败时返回 {}
"""
pass
@@ -314,15 +288,151 @@ class ProjectToolkit(ABC):
fee_attribute (str): 费用值属性名
Returns:
Tuple[str, Dict[str, Any], str, List[Any]]: 包含状态、数据、错误信息和辅助信息的元组
- status (str): 状态,'success'或'error'
- data (Dict[str, Any]): 成功时返回包含属性值的数据
- error (str): 错误时的错误信息
- helper_info (List[Any]): 辅助信息列表:
- 第一步失败:返回"工程数据"节点下所有子节点名称
- 第二步失败:返回费用表节点下所有子节点名称(递归查找)
- 第三步失败:返回费用节点的所有可用属性名称
- 否则为空
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Union[Dict, List, Any]): 成功时返回属性值(可能是 str/int/float 等),失败时返回 {}
"""
pass
# 工程属性查询方法
@abstractmethod
def get_project_property(self, property_name):
"""
通过属性名称获取工程属性
Args:
property_name (str): 属性名称
Returns:
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Union[Dict, List, Any]): 成功时返回属性数据字典,失败时返回空字典 {}
"""
pass
# 项目划分查找取费表
@abstractmethod
def get_fee_table_by_project_division(self, project_division_path, fee_name):
"""
通过项目划分路径和取费名称查找费用
Args:
project_division_path (str): 项目划分节点的路径
fee_name (str): 取费名称
Returns:
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Union[Dict, List, Any]): 成功时返回属性数据字典,失败时返回空字典 {}
"""
pass
# 清单查找取费表
@abstractmethod
def get_fee_table_by_list(self, parent_path, list_code, list_unit, fee_name):
"""
通过清单名称和取费名称查找费用
Args:
parent_path (str): 父级路径
list_code (str): 清单编号
list_unit (str): 清单单位
fee_name (str): 取费名称
Returns:
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Union[Dict, List, Any]): 成功时返回属性数据字典,失败时返回空字典 {}
"""
pass
# 定额节点查找合价
@abstractmethod
def get_fee_table_by_quoto_code(self, parent_path, quantity_type, code, fee_name):
"""
通过父级路径、工程量类型、定额编码和取费名称查找费用
Args:
parent_path (str): 父级路径
quantity_type (str): 工程量类型
code (str): 定额编码
fee_name (str): 取费名称
Returns:
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Union[Dict, List, Any]): 成功时返回属性数据字典,失败时返回空字典 {}
"""
pass
# 工程量节点查找合价
@abstractmethod
def get_fee_table_by_quantities_name(self, parent_path, quantity_type, quantity_name, fee_name):
"""
通过父级路径、工程量类型、工程量名称和取费名称查找费用
Args:
parent_path (str): 父级路径
quantity_type (str): 工程量类型
quantity_name (str): 工程量名称
fee_name (str): 取费名称
Returns:
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Union[Dict, List, Any]): 成功时返回属性数据字典,失败时返回空字典 {}
"""
pass
# 材机节点查找市场价
@abstractmethod
def get_fee_by_material_equipment_code(self, parent_path, material_equipment_code, fee_name):
"""
通过父级路径、材机编码和取费名称查找费用
Args:
parent_path (str): 父级路径
material_equipment_code (str): 材机编码
fee_name (str): 取费名称
Returns:
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Union[Dict, List, Any]): 成功时返回属性数据字典,失败时返回空字典 {}
"""
pass
# 查找清单节点
@abstractmethod
def get_fee_table_by_list_name(self, parent_path, list_code, unit):
"""
通过父级路径、清单编号和单位查找清单
Args:
parent_path (str): 父级路径
list_code (str): 清单编号
unit (str): 单位
Returns:
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Union[Dict, List, Any]): 成功时返回属性数据字典,失败时返回空字典 {}
"""
pass
@@ -583,34 +693,36 @@ class ProjectBuilder:
_config = {}
"""项目工具工厂类"""
def __init__(self):
pass
@classmethod
def register(cls, toolkit_class: type, config: dict):
"""
注册工具类到工厂
参数:
toolkit_class: 继承自ProjectToolkit的具体工具类
"""
if not issubclass(toolkit_class, ProjectToolkit):
raise TypeError(f"{toolkit_class.__name__} 必须继承自 ProjectToolkit")
cls._config = config
cls._registry = toolkit_class
@classmethod
def build(cls) -> ProjectToolkit:
"""
创建工具实例
参数:
返回:
实例化的工具对象
"""
if cls._registry is None:
raise KeyError(f"未注册的类,请先注册类")
return cls._registry(cls._config)
+49 -38
View File
@@ -1,59 +1,70 @@
EngineeringData(工程)
├── ProjectPropertySet(工程属性集)
── ProjectProperty(项目编码、电压等级、主变规模)
── ProjectProperty(工程属性)[name:项目划分, value: 架空输电线路工程]
│ ├── ProjectProperty(工程属性)[name:电压等级, value: 220]
│ └── ProjectProperty(工程属性)[name:专业类型, value: 送电]
├── ProjectDivisionSet(项目划分集)
│ │
│ ├── ProjectDivisionTree(安装)
│ │ ├── ProjectDivisionItem(架空输电线路本体工程)[父节点]
│ │ │ ├── ProjectDivisionItem(基础工程)[叶节点]
│ │ │ │ ├── ProjectQuantity(现浇混凝土)[类型:Quota, 量:1500m³, 定额:GD5-2023-025]
│ │ │ │ └── ProjectQuantity(钢筋)[类型:MainMaterial, 量:950t, 型号:HRB400]
│ │ │ │
│ │ │ └── ProjectDivisionItem(GIS室)[叶节点]
│ ├── ProjectDivisionTree(项目划分树)[name: 安装]
│ │ ├── ProjectDivisionItem(项目划分)[name: 架空输电线路本体工程]
│ │ │ ├── ProjectDivisionItem(项目划分)[name: 基础工程]
│ │ │ │ ├──ProjectQuantity(工程量)[name: 汽车运输 金具、绝缘子、零星钢材 装卸, 类型: 定额, 量: 84.486092, 单位: t, 编码: YX1-97]
│ │ │ │ │ └── CostSet(费用预览集)
│ │ │ │ │ ├──CostItem(费用项)[name: 直接工程费_FFZ1, cost: 354010.98]
│ │ │ │ │ ├──CostItem(费用项)[name: 编制年价差_机械价差_JJC, cost: 21869.0211]
│ │ │ │ │ └──CostItem(费用项)[name: 社会保险费_BZHF, cost: 16758.8798]
│ │ │ │ ├── ProjectQuantity(工程量)[name: 商品混凝土(立柱), 类型: 主材, 数量: 39.944, 单位: m³, 编码: SP1004]
│ │ │ │ └── CostSet(费用预览集)
│ │ │ │ ├──CostItem(费用项)[name: 直接工程费_FFZ1, cost: 6843369.393079]
│ │ │ │ ├──CostItem(费用项)[name: 编制年价差_机械价差_JJC, cost: 40402.974077]
│ │ │ │ └──CostItem(费用项)[name: 社会保险费_BZHF, cost: 379304.02941]
│ │ │ │
│ │ │ └── ProjectDivisionItem(项目划分)[name: GIS室]
│ │ │
│ │ └── ProjectDivisionItem(辅助设施工程)[父节点]
│ │ ├── ProjectDivisionItem(事故油池)[叶节点]
│ │ └── ProjectDivisionItem(防火墙)[叶节点]
│ │ └── ProjectDivisionItem(项目划分)[name: 辅助设施工程]
│ │ ├── ProjectDivisionItem(项目划分)[name: 事故油池]
│ │ └── ProjectDivisionItem(项目划分)[name: 施工道路]
│ │
│ └── ProjectDivisionTree(清理项目安装)
│ ├── ProjectDivisionItem(主变压器系统)[父节点]
│ │ ├── ProjectDivisionItem(本体安装)[叶节点]
│ │ │ └── ProjectQuantity(主变压器)[类型:Equipment, 量:4, 型号:SZ11-1000MVA]
│ └── ProjectDivisionTree(项目划分树)[name: 清理项目安装]
│ ├── ProjectDivisionItem(项目划分)[name: 安装过程]
│ │ ├── ProjectDivisionItem(项目划分)[name: 本体安装]
│ │ │ └── ProjectQuantity(工程量)[name:锅炉(烟煤), 类型: 设备, 量:4, 单位: 台]
│ │ │
│ │ └── ProjectDivisionItem(冷却系统)[叶节点]
│ │ └── ProjectDivisionItem(项目划分)[name: 冷却系统]
│ │
│ └── ProjectDivisionItem(配电装置)[父节点]
│ ├── ProjectDivisionItem(500kV GIS)[叶节点]
│ │ └── ProjectQuantity(GIS间隔)[类型:Equipment, 量:8间隔, 单价:580万]
│ └── ProjectDivisionItem(项目划分)[name: 建筑工程]
│ ├── ProjectDivisionItem(项目划分)[name: 辅助工程]
│ │ └── ProjectQuantity(工程量)[name:柴油发电机, 类型: 设备, 量:1, 单位: 台]
│ │
│ └── ProjectDivisionItem(35kV开关柜)[叶节点]
│ └── ProjectDivisionItem(项目划分)[name: 基础永久性围堰]
├── FeeScheduleSet(费用表集)
│ │
│ ├── FeeScheduleItem(工程费用表)
│ │ ├── Fee(直接费)[值:32500万元]
│ │ │ └── Fee(机械费)[值:7850万元]
│ ├── FeeScheduleItem(费用表项)[name: 工程费用表]
│ │ ├── Fee(费用)[name: 架空输电线路本体工程, code: BTGC, 安装费(AZF): 11155128.078755]
│ │ │ └── Fee(费用)[name: 一般线路本体工程, code: XLBT, 安装费(AZF): 11155128.078755]
│ │ │
│ │ └── Fee(措施费)[值:4875万元]
│ │ ├── Fee(安全文明施工费)[费率:3.5%, 值:2800万元]
│ │ └── Fee(夜间施工增加费)[费率:1.2%, 值:2075万元]
│ │ └── Fee(费用)[name: 动态费用, code: DTFY, 安装费(AZF): 0]
│ │ ├── Fee(费用)[name: 建设期贷款利息, code: DKLX, 安装费(AZF): 0]
│ │ └── Fee(费用)[name: 价差预备费, code: JCYBF, 安装费(AZF): 0]
│ │
│ └── FeeScheduleItem(其他费用表)
│ ├── Fee(企业管理费)[费率:5.8%, 值:1885万元]
│ └── Fee(税金)[费率:9%, 值:2925万元]
│ └── FeeScheduleItem(费用表项)[name: 其他费用表]
│ ├── Fee(费用)[name: 工程保险费, code: B6, rate: 100, amount: 0]
│ └── Fee(费用)[name: 招标费, code: B2, rate: 0.28, amount: 31234.358621]
├── FeeTableTemplateSet(取费表模板集)
├── FeeTableTemplateItem(建筑取费表)
│ ├── FeeCollection(直接费)
│ │ └── FeeCollection(人工费调整)[系数:1.25]
├── FeeTableTemplateItem(取费表模板项)[name: 线路取费表]
│ ├── FeeCollection(取费) [name: 设备费, code: SBF, rate: 100]
│ │ └── FeeCollection(取费)[name: 乙供设备不含税价, code: SBY, base: 乙供设备费不含税+乙供设备税金, rate: 100]
│ │
│ └── FeeCollection(措施费)
── FeeCollection(安全文明施工费)[费率:3.5%]
── FeeCollection(夜间施工增加费)[费率:1.2%]
│ └── FeeCollection(取费)[name:直接费, code: FFZ, rate: 100]
── FeeCollection(取费)[name: 直接工程费, code: FFZ1]
── FeeCollection(取费)[name: 人工费, code: RGF, base: 取费定额人工费, rate: 100]
│ └── FeeCollection(取费)[name: 材料费, code: CLF, base: 取费定额乙供材料费不含税, rate: 100]
└── FeeTableTemplateItem(安装取费表)
├── FeeCollection(利润)[费率:7%]
└── FeeCollection(税金)[费率:9%]
└── FeeTableTemplateItem(取费表模板项) [name: 大型土石方取费表]
├── FeeCollection(取费)[name: 税金, code: TFFS, rate: 9]
└── FeeCollection(取费)[name: 合计, code: THJ, rate: 100]
File diff suppressed because it is too large Load Diff
Binary file not shown.
+42 -53
View File
@@ -1,63 +1,53 @@
# main.py
import os
import logging
from datetime import datetime
import logging
import os
import asyncio
import os
current_file = os.path.splitext(os.path.basename(__file__))[0]
now_str = datetime.now().strftime("%Y%m%d%H%M%S")
log_filename = f"{current_file}_{now_str}.log"
logging.basicConfig(
level=logging.DEBUG, # 生产环境可改为 INFO 或 WARNING
level=logging.DEBUG,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[logging.FileHandler("BoweiAgent.log", encoding="utf-8"), logging.StreamHandler()],
handlers=[
logging.FileHandler(os.path.join("logs", log_filename), encoding="utf-8"),
logging.StreamHandler()
],
)
logger = logging.getLogger("BoweiAgent")
logger = logging.getLogger(current_file)
import logging
def setup_logger(logger_name):
"""
设置指定名称的logger,将其级别设置为WARNING并禁用传播
:param logger_name: logger的名称
"""
logger = logging.getLogger(logger_name)
logger.setLevel(logging.WARNING) # 设置httpcore及其子模块的级别
logger.propagate = False # 可选:禁用传播(防止被根logger处理)
return logger
# 获取logger并设置级别
httpx_logger = logging.getLogger("httpx")
httpx_logger.setLevel(logging.WARNING) # 设置httpcore及其子模块的级别
# 可选:禁用传播(防止被根logger处理)
httpx_logger.propagate = False
# 获取logger并设置级别
openai_logger = logging.getLogger("openai")
openai_logger.setLevel(logging.WARNING) # 设置httpcore及其子模块的级别
# 可选:禁用传播(防止被根logger处理)
openai_logger.propagate = False
# 获取logger并设置级别
langsmith_logger = logging.getLogger("langsmith.client")
langsmith_logger.setLevel(logging.WARNING) # 设置httpcore及其子模块的级别
# 可选:禁用传播(防止被根logger处理)
langsmith_logger.propagate = False
# 获取logger并设置级别
neo4j_logger = logging.getLogger("neo4j")
neo4j_logger.setLevel(logging.WARNING) # 设置httpcore及其子模块的级别
# 可选:禁用传播(防止被根logger处理)
neo4j_logger.propagate = False
# 获取logger并设置级别
urllib3_logger = logging.getLogger("urllib3")
urllib3_logger.setLevel(logging.WARNING) # 设置httpcore及其子模块的级别
# 可选:禁用传播(防止被根logger处理)
urllib3_logger.propagate = False
# 获取logger并设置级别
httpcore_logger = logging.getLogger("httpcore")
httpcore_logger.setLevel(logging.WARNING) # 设置httpcore及其子模块的级别
# 可选:禁用传播(防止被根logger处理)
httpcore_logger.propagate = False
logger_names = ["httpx", "openai", "langsmith.client", "neo4j", "urllib3", "httpcore"]
for name in logger_names:
setup_logger(name)
from src.config import Config
from src.document_loader import load_file
from src.llm_client import LLMClient
from src.multi_llm_client import MultiAPIKeyChatOpenAI
from src.prompt_manager import PromptManager
from src.code_executor import CodeExecutor
from src.dialog_manager import DialogManager
from src.neo4j_raw_retriever import Neo4jRawRetriever
from src.embedding_client import EmbeddingClient
from project import ProjectBuilder, ProjectToolkit
from project_implementation import ProjectToolkitNeo4j
from src.project import ProjectBuilder, ProjectToolkit
from src.project_implementation import ProjectToolkitNeo4j
@@ -69,14 +59,11 @@ def main():
os.environ["LANGSMITH_TRACING"] = "true" if tracing_enabled else "false"
os.environ["LANGSMITH_API_KEY"] = config.langsmith.get("api_key")
#os.environ["LANGSMITH_API_KEY"] = config.langsmith.get("api_url")
business_structure = load_file(config.business_object_structure_path)
bowei_api_docs = load_file(config.bowei_api_docs_path)
llm_client = LLMClient(config.openai)
llm_client_coder = LLMClient(config.openai_coder)
llm_client = MultiAPIKeyChatOpenAI(config.openai)
llm_client_coder = MultiAPIKeyChatOpenAI(config.openai_coder)
prompt_manager = PromptManager()
neo4j_conf = config.neo4j_conf
@@ -85,12 +72,10 @@ def main():
embedding_client = EmbeddingClient(embedding_conf)
# 创建Neo4j检索器
knowledge_retriever = Neo4jRawRetriever(neo4j_conf)
ProjectBuilder.register(ProjectToolkitNeo4j, knowledge_retriever.driver)
code_executor = CodeExecutor(prompt_manager.prompts, llm_client_coder)
dialog_manager = DialogManager(
@@ -102,10 +87,14 @@ def main():
prompt_manager,
)
pre_input_question = "查找名称中包含“工程”的项目划分项,并返回其人工费乘以1000的值。"
pre_input_question = "查找名称中包含“工程”的项目划分项,并返回单位。"
pre_input_question = '查找名称中包含"工程"的项目划分项,并返回其人工费乘以1000的值。'
pre_input_question = '查找名称中包含"工程"的项目划分项,并返回单位。'
asyncio.run(dialog_manager.run_async(pre_input=pre_input_question))
try:
asyncio.run(dialog_manager.run_async(pre_input=pre_input_question))
finally:
neo4j_driver.close()
if __name__ == "__main__":
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -3,4 +3,6 @@ langchain-core
langchain-experimental
pyyaml
neo4j
langchain-neo4j
langchain-neo4j
langgraph
chardet
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+114 -46
View File
@@ -1,21 +1,39 @@
# src/code_executor.py
import os
import logging
from datetime import datetime
import logging
from langchain_core.output_parsers import StrOutputParser
from langchain_experimental.utilities import PythonREPL
from langchain_core.tools import Tool
from langchain_experimental.tools import PythonREPLTool
from project import ProjectBuilder, ProjectToolkit
from src.project import ProjectBuilder, ProjectToolkit
import sys
import io
import traceback
logger = logging.getLogger("BoweiAgent.CodeExecutor")
current_file = os.path.splitext(os.path.basename(__file__))[0]
now_str = datetime.now().strftime("%Y%m%d%H%M%S")
log_filename = f"{current_file}_{now_str}.log"
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[
logging.FileHandler(os.path.join("logs", log_filename), encoding="utf-8"),
logging.StreamHandler()
],
)
logger = logging.getLogger(current_file)
class CodeExecutor:
def __init__(self, prompts, llm_client, max_retries=3):
def __init__(self, prompts, llm_client, max_retries):
self.llm_client = llm_client
self.prompts = prompts
self.max_retries = max_retries
self.max_retries = max_retries if max_retries >= 1 else 1
self.output_parser = StrOutputParser()
def generate_code(self, user_request: str, context: str = "", bowei_api_docs: str = "") -> str:
@@ -27,7 +45,13 @@ class CodeExecutor:
response = self.llm_client.invoke(prompt.to_messages())
code = self.output_parser.parse(response)
logger.debug(f"生成的代码内容:\n{code}")
return code
return {
"code": 20000,
"message": 'ok',
"status": True,
"data": code.content
}
def fix_code(self, code: str, error: str) -> str:
logger.warning(f"代码执行出错,开始修复。错误信息:{error}")
@@ -35,11 +59,19 @@ class CodeExecutor:
response = self.llm_client.invoke(prompt.to_messages())
fixed_code = self.output_parser.parse(response)
logger.debug(f"修复后的代码内容:\n{fixed_code}")
return fixed_code
def execute_code(self, code_str):
return {
"code": 20000,
"message": 'ok',
"status": True,
"data": fixed_code.content
}
def execute_code(self, code_str) -> dict:
"""封装代码执行逻辑"""
logger.debug(f"开始执行代码: {code_str}")
logger.debug(f"开始执行代码:\n {code_str}")
old_stdout = None
try:
namespace = {
"project": __import__("project"),
@@ -56,65 +88,101 @@ class CodeExecutor:
if "project_get_calculate_function" not in namespace:
raise ValueError("代码中未定义project_get_calculate_function函数")
result_tuple = namespace["project_get_calculate_function"]()
result_dict = namespace["project_get_calculate_function"]()
sys.stdout = old_stdout
output = redirected_output.getvalue().strip()
if not isinstance(result_tuple, tuple) or len(result_tuple) != 4:
raise ValueError("函数应返回包含4个元素的元组(status, data, error, helper_info)")
if not isinstance(result_dict, dict) or len(result_dict) != 4:
raise ValueError("函数应返回包含4个元素的字典(status, data, error, helper_info)")
status, data, error, helper_info = result_tuple
logger.info(f"执行结果: status={status}, data={data}, error={error}")
return {
"status": status,
"data": data,
"error": error,
"helper_info": helper_info,
"output": output,
}
logger.debug(f"执行结果: {result_dict}")
return result_dict
except Exception as e:
# 确保恢复stdout
sys.stdout = old_stdout
logger.error(f"执行代码时出错: {e}")
logger.error(traceback.format_exc())
return {
"status": "error",
"error": str(e),
"helper_info": [],
"traceback": traceback.format_exc(),
"code": 50000,
"message": str(e),
"status": False,
"data": []
}
def generate_and_run_code(self, user_request: str, context: str = "", bowei_api_docs: str = "") -> str:
code = self.generate_code(user_request, context, bowei_api_docs)
logger.info("开始执行生成的代码")
def generate_and_run_code(self, user_request: str, context: str = "", bowei_api_docs: str = "") -> dict:
code = ""
pre_code = ""
error_msg = ""
prev_happend_error = False
result = self.generate_code(user_request, context, bowei_api_docs)
if isinstance(result, dict) and result.get('status', False):
code = result['data']
elif isinstance(result, dict):
error_msg = result.get("message", "返回结果中没有错误信息")
logger.error(error_msg)
return {
"code": 50000,
"message": error_msg,
"status": False,
"data": code
}
else:
error_msg = "返回结果格式未知"
logger.error(error_msg)
return {
"code": 50000,
"message": error_msg,
"status": False,
"data": code
}
logger.info("开始执行生成的代码")
for attempt in range(self.max_retries):
try:
if prev_happend_error:
logger.error(f"代码执行失败,尝试第 {attempt+1} 次修复。错误信息:{error_msg}")
code = self.fix_code(pre_code, error_msg)
import re
pre_code = re.sub(r"^```python\s*|\s*```$", "", code.content, flags=re.MULTILINE)
result = self.execute_code(pre_code)
if result["status"] == "success":
logger.info(f"代码执行成功,结果: {result['data']}")
return result["data"]
else:
error_msg = result.get("error", "未知错误")
prev_happend_error = True
result = self.fix_code(pre_code, error_msg)
if isinstance(result, dict) and result.get('status', False):
code = result['data']
elif isinstance(result, dict):
error_msg = result.get("message", "返回结果中没有错误信息")
raise ValueError(error_msg)
else:
error_msg = "返回结果格式未知"
raise ValueError(error_msg)
except Exception as e:
error_msg = str(e)
error_msg = f"生成修复代码时发生异常: {str(e)}"
prev_happend_error = True
logger.error(f"代码执行失败,超过最大重试次数 {self.max_retries}")
return f"代码执行失败,超过最大重试次数 {self.max_retries}\n最后一次错误信息:\n{error_msg}"
try:
import re
pre_code = re.sub(r"^```python\s*|\s*```$", "", code, flags=re.MULTILINE)
except Exception as e:
error_msg = f"解析生成代码格式时发生异常: {str(e)}"
prev_happend_error = True
try:
result = self.execute_code(pre_code)
if isinstance(result, dict) and result.get('status', False):
logger.info(f"代码执行成功,结果: {result['data']}")
return result
elif isinstance(result, dict):
error_msg = result.get("message", "返回结果中没有错误信息")
else:
error_msg = "返回结果格式未知"
prev_happend_error = True
except Exception as e:
error_msg = f"执行代码时发生异常: {str(e)}"
prev_happend_error = True
error_msg = f"代码执行失败,最后一次错误信息: {error_msg}"
logger.error(error_msg)
return {
"code": 50000,
"message": error_msg,
"status": False,
"data": []
}
+24
View File
@@ -1,4 +1,24 @@
# src/config.py
import yaml
import os
import logging
from datetime import datetime
current_file = os.path.splitext(os.path.basename(__file__))[0]
now_str = datetime.now().strftime("%Y%m%d%H%M%S")
log_filename = f"{current_file}_{now_str}.log"
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[
logging.FileHandler(os.path.join("logs", log_filename), encoding="utf-8"),
logging.StreamHandler()
],
)
logger = logging.getLogger(current_file)
class Config:
def __init__(self, path="config.yaml"):
@@ -35,3 +55,7 @@ class Config:
@property
def langsmith(self):
return self._config.get("langsmith", {})
@property
def max_retries(self):
return self._config.get("max_retries", 3)
+53 -1
View File
@@ -1,10 +1,26 @@
# src/dialog_manager.py
import os
import logging
from datetime import datetime
import logging
from langchain.schema import SystemMessage, HumanMessage
import asyncio
logger = logging.getLogger("BoweiAgent.DialogManager")
current_file = os.path.splitext(os.path.basename(__file__))[0]
now_str = datetime.now().strftime("%Y%m%d%H%M%S")
log_filename = f"{current_file}_{now_str}.log"
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[
logging.FileHandler(os.path.join("logs", log_filename), encoding="utf-8"),
logging.StreamHandler()
],
)
logger = logging.getLogger(current_file)
class QuestionProcessor:
def __init__(self, llm_client, business_structure, prompts):
@@ -112,6 +128,18 @@ class DialogManager:
rewritten_list.append((rewritten, doc.page_content))
return rewritten_list
def understand_user_question(self, user_input: str):
"""
同步调用 understand_user_question_stream 方法
:param user_input: 用户输入的问题
:return: 改写后的问题列表
"""
try:
return asyncio.run(self.understand_user_question_stream(user_input))
except Exception as e:
logger.error(f"执行 understand_user_question 时发生错误: {str(e)}")
return []
async def run_async(self, pre_input: str = None):
logger.info("启动对话管理器,等待用户输入")
print("欢迎使用博微造价工程数据访问系统,输入 exit 退出。")
@@ -174,3 +202,27 @@ class DialogManager:
)
return result
def generated_code(self, selected_rewritten, selected_knowledge):
"""
生成代码
:param selected_rewritten: 选中的重写后的请求
:param selected_knowledge: 选中的知识
:return: 生成的代码
"""
result = self.code_executor.generate_code(
selected_rewritten,
context=selected_knowledge,
bowei_api_docs=self.bowei_api_docs
)
return result
def execute_code(self, code):
"""
执行代码并返回结果
:param code: 代码
:return: 代码执行结果
"""
result = self.code_executor.execute_code(code)
return result
+21
View File
@@ -1,3 +1,24 @@
# src/document_loader.py
import os
import logging
from datetime import datetime
current_file = os.path.splitext(os.path.basename(__file__))[0]
now_str = datetime.now().strftime("%Y%m%d%H%M%S")
log_filename = f"{current_file}_{now_str}.log"
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[
logging.FileHandler(os.path.join("logs", log_filename), encoding="utf-8"),
logging.StreamHandler()
],
)
logger = logging.getLogger(current_file)
def load_file(path: str) -> str:
try:
with open(path, "r", encoding="utf-8") as f:
+19
View File
@@ -1,6 +1,25 @@
# src/embedding_client.py
import os
import logging
from datetime import datetime
from langchain_openai import OpenAIEmbeddings
current_file = os.path.splitext(os.path.basename(__file__))[0]
now_str = datetime.now().strftime("%Y%m%d%H%M%S")
log_filename = f"{current_file}_{now_str}.log"
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[
logging.FileHandler(os.path.join("logs", log_filename), encoding="utf-8"),
logging.StreamHandler()
],
)
logger = logging.getLogger(current_file)
class EmbeddingClient:
def __init__(self, embedding_config: dict):
api_key = embedding_config.get("api_key")
+26 -1
View File
@@ -1,8 +1,27 @@
# src/llm_client.py
import os
import logging
from datetime import datetime
import os
import getpass
from langchain_openai import ChatOpenAI
from langchain_core.rate_limiters import InMemoryRateLimiter
current_file = os.path.splitext(os.path.basename(__file__))[0]
now_str = datetime.now().strftime("%Y%m%d%H%M%S")
log_filename = f"{current_file}_{now_str}.log"
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[
logging.FileHandler(os.path.join("logs", log_filename), encoding="utf-8"),
logging.StreamHandler()
],
)
logger = logging.getLogger(current_file)
class LLMClient:
def __init__(self, openai_config: dict):
@@ -22,8 +41,14 @@ class LLMClient:
model_name = openai_config.get("model_name", "gpt-4o-mini")
# 初始化限速器:每秒最多0.1次请求(即每10秒1次)
rate_limiter = InMemoryRateLimiter(
requests_per_second=13,
check_every_n_seconds=10,
max_bucket_size=10,
)
# 开启流式
self.llm = ChatOpenAI(model_name=model_name, temperature=0, streaming=True)
self.llm = ChatOpenAI(model_name=model_name, temperature=0, streaming=True, rate_limiter=rate_limiter)
def invoke(self, messages):
# 同步调用,返回完整响应
+73
View File
@@ -0,0 +1,73 @@
# src/multi_llm_client.py
import os
import logging
from datetime import datetime
import os
import getpass
import itertools
from langchain_openai import ChatOpenAI
from langchain_core.rate_limiters import InMemoryRateLimiter
current_file = os.path.splitext(os.path.basename(__file__))[0]
now_str = datetime.now().strftime("%Y%m%d%H%M%S")
log_filename = f"{current_file}_{now_str}.log"
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[
logging.FileHandler(os.path.join("logs", log_filename), encoding="utf-8"),
logging.StreamHandler()
],
)
logger = logging.getLogger(current_file)
class MultiAPIKeyChatOpenAI:
def __init__(self, openai_config: dict):
api_keys = openai_config.get("api_keys", [])
if not api_keys:
api_key = openai_config.get("api_key")
if not api_key:
api_key = os.environ.get("OPENAI_API_KEY")
api_keys = [api_key]
api_base = openai_config.get("api_base")
api_type = openai_config.get("api_type", "openai")
model_name = openai_config.get("model_name", "gpt-4o-mini")
# 设置环境变量(可选,根据需要)
if api_base:
os.environ["OPENAI_API_BASE"] = api_base
os.environ["OPENAI_API_TYPE"] = api_type
# 初始化限速器
rate_limiter = InMemoryRateLimiter(
requests_per_second=13,
check_every_n_seconds=10,
max_bucket_size=10,
)
# 创建多个 ChatOpenAI 实例,每个使用不同的 API Key
self.llms = [
ChatOpenAI(
api_key=key,
model_name=model_name,
temperature=0,
streaming=True,
rate_limiter=rate_limiter,
)
for key in api_keys
]
# 轮询器,用于循环调用不同的 llm 实例
self._llm_cycle = itertools.cycle(self.llms)
def invoke(self, messages):
llm = next(self._llm_cycle)
return llm.invoke(messages)
def stream(self, messages):
llm = next(self._llm_cycle)
return llm.astream(messages)
+17 -2
View File
@@ -1,11 +1,26 @@
# src/neo4j_raw_retriever.py
import os
import logging
from datetime import datetime
from typing import List
from langchain.schema import Document
from neo4j import GraphDatabase
import logging
logger = logging.getLogger("BoweiAgent.Neo4jRawRetriever")
current_file = os.path.splitext(os.path.basename(__file__))[0]
now_str = datetime.now().strftime("%Y%m%d%H%M%S")
log_filename = f"{current_file}_{now_str}.log"
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[
logging.FileHandler(os.path.join("logs", log_filename), encoding="utf-8"),
logging.StreamHandler()
],
)
logger = logging.getLogger(current_file)
class Neo4jRawRetriever:
def __init__(self, neo4j_conf: dict):
+20
View File
@@ -1,6 +1,26 @@
# src/neo4j_retriever.py
import os
import logging
from datetime import datetime
from langchain_neo4j import Neo4jVector
from langchain_openai import OpenAIEmbeddings
current_file = os.path.splitext(os.path.basename(__file__))[0]
now_str = datetime.now().strftime("%Y%m%d%H%M%S")
log_filename = f"{current_file}_{now_str}.log"
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[
logging.FileHandler(os.path.join("logs", log_filename), encoding="utf-8"),
logging.StreamHandler()
],
)
logger = logging.getLogger(current_file)
class Neo4jKnowledgeRetriever:
def __init__(self, neo4j_conf: dict, embedding_client):
neo4j_uri = neo4j_conf.get("uri")
+728
View File
@@ -0,0 +1,728 @@
"""
软件知识图谱类定义
根据Ontology_Layer.txt文件中的知识图谱信息创建
"""
from abc import ABC, abstractmethod
import json
class ProjectToolkit(ABC):
"""
项目类(抽象基类)
描述: 代表整个项目结构的顶层容器
"""
def __init__(self):
self.project_division_set = ProjectDivisionItem() # 项目划分集对象
# 项目划分查询方法
@abstractmethod
def get_division_by_name(self, name):
"""
通过名称获取项目划分对象
Args:
name_part (str): 项目划分节点名称
Returns:
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (List[Dict[str, Any]]): 成功时返回的数据列表,失败时为空列表
"""
pass
@abstractmethod
def get_division_item_by_path(self, path):
"""
通过路径获取项目划分对象
Args:
path (str): 项目划分节点的路径,以'/'分隔的多级节点路径
Returns:
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Dict[str, Any]): 成功时返回的项目划分对象数据,失败时为空字典
"""
pass
@abstractmethod
def get_division_node_by_parent_and_name(self, parent_path, partial_name):
"""
通过父节点路径和模糊节点名称获取项目划分对象,包括子节点
Args:
parent_path (str): 父节点的路径,以'/'分隔的多级节点路径
partial_name (str): 目标节点的模糊或不完整名称
Returns:
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (List[Dict[str, Any]]): 成功时返回的数据列表,失败时为空列表
"""
pass
# 工程量查询方法
@abstractmethod
def get_quantities_by_paths(self, paths_str):
"""
获取指定项目路径下的工程量对象
Args:
paths_str (str): 以'/'分隔的多级节点路径
Returns:
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Dict[str, Any]): 成功时返回的节点数据字典,失败时为空字典 {}
"""
pass
@abstractmethod
def get_quantities_node_by_parent_and_code(self, parent_path, quantity_type=None, code=None):
"""
通过父节点路径和编码获取工程量对象(定额、主材或设备),包括子节点
Args:
parent_path (str): 父节点的路径,以'/'分隔的多级节点路径
quantity_type (str): 工程量类型('定额''主材''设备'或None表示所有类型)
code (str): 工程量编码,以'/'分隔的多个编码
Returns:
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Union[List[Dict[str, Any]], Dict]): 成功时返回的数据列表或字典,失败时为空列表 []
"""
pass
@abstractmethod
def get_quantities_node_by_parent_and_name(self, parent_path, partial_name, quantity_type=None):
"""
通过父节点路径、模糊节点名称和类型获取工程量对象(主材或者设备),包括子节点
Args:
parent_path (str): 父节点的路径,以'/'分隔的多级节点路径
partial_name (str): 目标节点的模糊或不完整名称
quantity_type (str): 工程量类型('定额''主材''设备')
Returns:
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Union[List[Dict[str, Any]], Dict]): 成功时返回的数据列表,失败时为空列表 []
"""
pass
# 材机查询方法
@abstractmethod
def get_material_equipment_by_path(self, paths_str):
"""
通过路径获取材机对象(MaterialOrEquipment 类型节点)
Args:
path (str): 项目划分节点的路径,以'/'分隔的多级节点路径
Returns:
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Union[Dict, List]): 成功时返回的节点属性数据字典,失败时为空字典 {}
"""
pass
@abstractmethod
def get_material_equipment_by_parent_and_code(self, parent_path, code):
"""
通过父节点路径和编码获取材机对象
Args:
parent_path (str): 父节点的部分路径,以'/'分隔的多级节点路径
code (str): 目标节点的编码属性值
Returns:
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Union[Dict, List]): 成功时返回的节点属性数据字典,失败时为空字典 {}
"""
pass
# 取费查询方法
@abstractmethod
def get_fee_template_by_path(self, paths_str):
"""
通过路径获取取费表模板节点
Args:
path (str): 项目划分节点的路径,以'/'分隔的多级节点路径
例如:工程数据/取费表模板/余物清理/线路取费表(余物清理)/直接费
Returns:
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Union[Dict, List]): 成功时返回的节点属性数据字典,失败时为空字典 {}
"""
pass
@abstractmethod
def get_fee_template_by_parent_and_name(self, parent_path, partial_name):
"""
通过父节点路径和节点名称获取取费表模板节点
Args:
parent_path (str): 父节点的路径,以'/'分隔的多级节点路径
例如:工程数据/取费表模板/余物清理/线路取费表(余物清理)
node_name (str): 目标节点的名称,例如:直接费
Returns:
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Union[Dict, List]): 成功时返回的节点属性数据字典,失败时为空字典 {}
"""
pass
# 费用表查询方法
@abstractmethod
def get_fee_schedule_on_auxiliary_expense_table(self, table_name, fee_name, fee: str):
"""
在辅助费用表中查找费用
Args:
table_name (str): 费用表名称
fee_name (str): 要查找的费用名称(可能在多级子节点中)
fee_attribute (str): 费用值属性名
Returns:
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Union[Dict, List, Any]): 成功时返回属性值(可能是 str/int/float 等),失败时返回 {}
"""
pass
@abstractmethod
def get_fee_schedule_on_other_expense_table(self, table_name, fee_name, fee):
"""
在其它费用表中查找费用
Args:
table_name (str): 费用表名称
fee_name (str): 要查找的费用名称(可能在多级子节点中)
fee_attribute (str): 费用值属性名
Returns:
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Union[Dict, List, Any]): 成功时返回属性值(可能是 str/int/float 等),失败时返回 {}
"""
pass
@abstractmethod
def get_fee_schedule_on_land_acquisition_fee_table_table(self, table_name, fee_name, fee):
"""
在其中:场地征用费用表中查找费用
Args:
table_name (str): 费用表名称
fee_name (str): 要查找的费用名称(可能在多级子节点中)
fee_attribute (str): 费用值属性名
Returns:
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Union[Dict, List, Any]): 成功时返回属性值(可能是 str/int/float 等),失败时返回 {}
"""
pass
@abstractmethod
def get_fee_schedule_on_installation_price_difference_table(self, table_name, fee_name, fee):
"""
在安装价差费用表中查找费用
Args:
table_name (str): 费用表名称
fee_name (str): 要查找的费用名称(可能在多级子节点中)
fee_attribute (str): 费用值属性名
Returns:
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Union[Dict, List, Any]): 成功时返回属性值(可能是 str/int/float 等),失败时返回 {}
"""
pass
@abstractmethod
def get_fee_schedule_on_Engineering_Cost_table(self, table_name, fee_name, fee):
"""
在工程费用表中查找费用
Args:
table_name (str): 费用表名称
fee_name (str): 要查找的费用名称(可能在多级子节点中)
fee_attribute (str): 费用值属性名
Returns:
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Union[Dict, List, Any]): 成功时返回属性值(可能是 str/int/float 等),失败时返回 {}
"""
pass
# 工程属性查询方法
@abstractmethod
def get_project_property(self, property_name):
"""
通过属性名称获取工程属性
Args:
property_name (str): 属性名称
Returns:
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Union[Dict, List, Any]): 成功时返回属性数据字典,失败时返回空字典 {}
"""
pass
# 项目划分查找取费表
@abstractmethod
def get_fee_table_by_project_division(self, project_division_path, fee_name):
"""
通过项目划分路径和取费名称查找费用
Args:
project_division_path (str): 项目划分节点的路径
fee_name (str): 取费名称
Returns:
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Union[Dict, List, Any]): 成功时返回属性数据字典,失败时返回空字典 {}
"""
pass
# 清单查找取费表
@abstractmethod
def get_fee_table_by_list(self, parent_path, list_code, list_unit, fee_name):
"""
通过清单名称和取费名称查找费用
Args:
parent_path (str): 父级路径
list_code (str): 清单编号
list_unit (str): 清单单位
fee_name (str): 取费名称
Returns:
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Union[Dict, List, Any]): 成功时返回属性数据字典,失败时返回空字典 {}
"""
pass
# 定额节点查找合价
@abstractmethod
def get_fee_table_by_quoto_code(self, parent_path, quantity_type, code, fee_name):
"""
通过父级路径、工程量类型、定额编码和取费名称查找费用
Args:
parent_path (str): 父级路径
quantity_type (str): 工程量类型
code (str): 定额编码
fee_name (str): 取费名称
Returns:
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Union[Dict, List, Any]): 成功时返回属性数据字典,失败时返回空字典 {}
"""
pass
# 工程量节点查找合价
@abstractmethod
def get_fee_table_by_quantities_name(self, parent_path, quantity_type, quantity_name, fee_name):
"""
通过父级路径、工程量类型、工程量名称和取费名称查找费用
Args:
parent_path (str): 父级路径
quantity_type (str): 工程量类型
quantity_name (str): 工程量名称
fee_name (str): 取费名称
Returns:
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Union[Dict, List, Any]): 成功时返回属性数据字典,失败时返回空字典 {}
"""
pass
# 材机节点查找市场价
@abstractmethod
def get_fee_by_material_equipment_code(self, parent_path, material_equipment_code, fee_name):
"""
通过父级路径、材机编码和取费名称查找费用
Args:
parent_path (str): 父级路径
material_equipment_code (str): 材机编码
fee_name (str): 取费名称
Returns:
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Union[Dict, List, Any]): 成功时返回属性数据字典,失败时返回空字典 {}
"""
pass
# 查找清单节点
@abstractmethod
def get_fee_table_by_list_name(self, parent_path, list_code, unit):
"""
通过父级路径、清单编号和单位查找清单
Args:
parent_path (str): 父级路径
list_code (str): 清单编号
unit (str): 单位
Returns:
dict: 返回字典,字段包括:
- code (int): 状态码,固定为 200(成功)或 201(失败)
- message (str): 成功时为 "Ok",失败时包含错误信息和辅助信息
- status (bool): 成功为 True,失败为 False
- data (Union[Dict, List, Any]): 成功时返回属性数据字典,失败时返回空字典 {}
"""
pass
class ProjectDivisionSet:
"""
项目划分项
描述: 代表项目结构中的层级条目,具有自身的属性,并且可以包含ProjectDivisionTree(项目划分树)
"""
def __init__(self):
self.name = None # xsd:string
class ProjectDivisionTree:
"""
项目划分项
描述: 代表项目结构中的层级条目,具有自身的属性,并且可以包含ProjectDivisionItem(项目划分项)
"""
def __init__(self):
self.name = None # xsd:string
class ProjectDivisionItem:
"""
项目划分项
描述: 代表项目结构中的层级条目,具有自身的属性,并且可以包含子项目划分项或详细工作项
JSON对应: ProjectDivisionSet数组中的对象,或ProjectDivisionItem的children数组中type为"项目划分"的对象
"""
def __init__(self):
self.GUID = None # xsd:string
self.id = None # xsd:string
self.name = None # xsd:string
self.代码 = None # xsd:string (可选)
self.费率 = None # xsd:string
self.单位 = None # xsd:string (可选)
self.取费表id = None # xsd:string
self.颜色标记 = None # xsd:string
self.取费表 = None # xsd:string
self.合价含税 = None # xsd:string (可选)
self.type = None # xsd:string
self.专业类型 = None # xsd:string
self.资源库列表 = None # xsd:list (可选)
self.notCheck = None # xsd:string (可选)
class ProjectQuantity:
"""
工程量
描述: 代表项目划分项(ProjectDivisionItem)下的具体工作单元或物料项,是定额、主材、设备的父类型
"""
def __init__(self):
self.id = None # xsd:string
self.类型 = None # xsd:string ("0"为定额,"1"为主材,"5"为设备)
self.name = None # xsd:string
self.编码 = None # xsd:string
self.单位 = None # xsd:string
self.数量 = None # xsd:string
self.资源库名称 = None # xsd:string
self.投标数量 = None # xsd:string (可选)
self.投标单价 = None # xsd:string (可选)
self.特征段 = None # xsd:string (可选)
self.关联父级量 = None # xsd:string (可选)
self.颜色标记 = None # xsd:string (可选)
self.单价不含税 = None
self.cost_set = None # xsd:CostSet
class Ration(ProjectQuantity):
"""
定额
描述: 代表一种标准的工程量条目,通常包含详细的工、料、机消耗标准
"""
def __init__(self):
super().__init__()
self.计算式 = None # xsd:string
self.中标计算式 = None # xsd:string
self.人工费 = None # xsd:string
self.机械费 = None # xsd:string
self.甲供材料费不含税 = None # xsd:string
self.材料费 = None # xsd:string
self.定额系数 = None # xsd:string
self.人工系数 = None # xsd:string
self.材料系数 = None # xsd:string
self.机械系数 = None # xsd:string
self.定额范围 = None # xsd:string
self.定额章节名称 = None # xsd:string
self.费用类型 = None # xsd:string
self.甲供材料费含税 = None # xsd:string
self.投标合价 = None # xsd:string
self.其中甲供材料费 = None # xsd:string
self.合价不含税 = None # xsd:string
self.基价 = None # xsd:string
self.所属定额库 = None # xsd:string
def __str__(self):
"""返回定额的字符串表示"""
attrs = {k: v for k, v in self.__dict__.items() if not k.startswith("_")}
return json.dumps(attrs, ensure_ascii=False, indent=2)
class Material(ProjectQuantity):
"""
主材
描述: 代表工程中使用的主要材料
"""
def __init__(self):
super().__init__()
self.规格型号 = None # xsd:string
self.损耗率 = None # xsd:string
self.供货方 = None # xsd:string
self.集中配送 = None # xsd:string (可选)
self.单重 = None # xsd:string (可选)
self.市场价不含税 = None # xsd:string
self.市场价含税 = None # xsd:string
self.单价含税 = None # xsd:string
self.结算市场价不含税 = None # xsd:string (可选)
self.结算市场价含税 = None # xsd:string (可选)
self.基准价不含税 = None # xsd:string (可选)
self.基准价含税 = None # xsd:string (可选)
self.费用类型 = None # xsd:string
self.增值税率 = None # xsd:string (可选)
self.合价含税 = None # xsd:string
self.合价不含税 = None # xsd:string
self.线重 = None # xsd:string (可选)
self.制造长度 = None # xsd:string (可选)
self.截面积 = None # xsd:string (可选)
def __str__(self):
"""返回材料的字符串表示"""
attrs = {k: v for k, v in self.__dict__.items() if not k.startswith("_")}
return json.dumps(attrs, ensure_ascii=False, indent=2)
class Equipment(ProjectQuantity):
"""
设备
描述: 代表工程中安装或使用的设备
"""
def __init__(self):
super().__init__()
self.规格型号 = None # xsd:string
self.供货方 = None # xsd:string
self.运杂费率 = None # xsd:string (可选)
self.单价含税 = None # xsd:string
self.设备类型 = None # xsd:string (可选)
self.增值税率 = None # xsd:string (可选)
self.合价含税 = None # xsd:string
self.合价不含税 = None # xsd:string
class MaterialOrEquipment:
"""
材机
描述: 代表DetailedWorkItem中所列出的具体材料、人工或机械设备及其详细信息
"""
def __init__(self):
self.id = None # xsd:string
self.编码 = None # xsd:string
self.名称 = None # xsd:string
self.单位 = None # xsd:string
self.类型 = None # xsd:string
self.供货方 = None # xsd:string
self.预算价不含税 = None # xsd:string
self.市场价不含税 = None # xsd:string
self.预算价含税 = None # xsd:string
self.市场价含税 = None # xsd:string
self.结算预算价不含税 = None # xsd:string
self.结算市场价不含税 = None # xsd:string
self.结算预算价含税 = None # xsd:string
self.结算市场价含税 = None # xsd:string
self.暂估价 = None # xsd:string
self.拆分 = None # xsd:string
self.全口径市场价不含税 = None # xsd:string
self.全口径市场价含税 = None # xsd:string
self.商品砼 = None # xsd:string
self.数量 = None # xsd:string
self.是否未计价 = None # xsd:string
class FeeTableTemplateItem:
"""
取费表模板项
描述:
"""
def __init__(self):
self.name = None # xsd:string
self.OutlayID = None # xsd:string
self.type = None # xsd:string
self.profession = None # xsd:string
class FeeCollection:
"""
取费
描述:
"""
def __init__(self):
self.name = None # xsd:string
self.serialNumber = None # xsd:string
self.code = None # xsd:string
self.base = None # xsd:string
self.rate = None # xsd:string
class FeeScheduleItem:
"""
费用表项
描述:
"""
def __init__(self):
self.name = None # xsd:string
class Fee:
"""
取费
描述:
"""
def __init__(self):
self.name = None # xsd:string
self.serialNumber = None # xsd:string
self.code = None # xsd:string
self.rate = None # xsd:string
self.amount = None # xsd:string
self.输出 = None # xsd:string (可选)
self.取费基数 = None # xsd:string (可选)
self.编制依据 = None # xsd:string (可选)
self.编码 = None # xsd:string (可选)
self.备注 = None # xsd:string (可选)
self.表一显示 = None # xsd:string (可选)
self.报表输出 = None # xsd:string (可选)
self.建安合计费 = None # xsd:string (可选)
self.合计费 = None # xsd:string (可选)
self.占总计 = None # xsd:string (可选)
self.建筑费 = None # xsd:string (可选)
self.安装费 = None # xsd:string (可选)
self.设备费 = None # xsd:string (可选)
self.其他费 = None # xsd:string (可选)
self.施工费 = None # xsd:string (可选)
self.单位投资 = None # xsd:string (可选)
class ProjectBuilder:
# 存储注册的工具类
_registry = None
_config = {}
"""项目工具工厂类"""
def __init__(self):
pass
@classmethod
def register(cls, toolkit_class: type, config: dict):
"""
注册工具类到工厂
参数:
toolkit_class: 继承自ProjectToolkit的具体工具类
"""
if not issubclass(toolkit_class, ProjectToolkit):
raise TypeError(f"{toolkit_class.__name__} 必须继承自 ProjectToolkit")
cls._config = config
cls._registry = toolkit_class
@classmethod
def build(cls) -> ProjectToolkit:
"""
创建工具实例
参数:
返回:
实例化的工具对象
"""
if cls._registry is None:
raise KeyError(f"未注册的类,请先注册类")
return cls._registry(cls._config)
File diff suppressed because it is too large Load Diff
+197 -5
View File
@@ -1,5 +1,8 @@
# src/prompt_manager.py
import os
import logging
from datetime import datetime
from dataclasses import dataclass
from langchain.prompts import ChatPromptTemplate
from langchain.schema import SystemMessage, HumanMessage
@@ -60,14 +63,23 @@ class PromptManager:
# 输出格式(必须严格遵循)
def project_get_calculate_function():
project = ProjectBuilder.build()
status, data, error, helper_info = project.[SELECTED_METHOD]([PARAMETERS])
return status, data, error, helper_info
result_dict = project.[SELECTED_METHOD]([PARAMETERS])
status = result_dict.get('status', False)
message = result_dict.get('message', '')
code = result_dict.get('data', '')
data = result_dict.get('data', [])
if status:
return result_dict
else:
return result_dict
# 执行规则
- 参数必须从用户问题或上下文信息中提取
- 输出代码中必须以def project_get_calculate_function() -> Tuple[bool, Any, Optional[str], Dict[str, Any]]函数作为入口函数
- 输出代码中必须以def project_get_calculate_function() -> dict函数作为入口函数
- 必须确保生成的代码可以直接执行,代码要注意进行各类错误检查,出错采用抛出异常方式,说明详细信息
- 禁止添加任何注释或解释
- ProjectToolkit 类中涉及项目划分的函数已考虑在其及其子孙项目划分下查找,所以无需生成递归子项目划分的代码
- 如果文本中包含范围编码格式则需要进行编码展开,如'YX2-1~7'展开为‘YX2-1/YX2-2/YX2-3/YX2-4/YX2-5/YX2-6/YX2-7
"""
)
@@ -89,6 +101,7 @@ def project_get_calculate_function():
4. 修复后的代码应该完整,可以直接执行,并且能够返回查询结果
注意:
- 如果文本中包含范围编码格式则需要进行编码展开,如'YX2-1~7'展开为‘YX2-1/YX2-2/YX2-3/YX2-4/YX2-5/YX2-6/YX2-7
- 必须只输出最终的Python代码,不要添加任何解释、注释、推理过程或自然语言描述。
- 不要以“以下是修正后的代码”、“修改如下”等语句开头。
- 不要输出任何其他无关的内容。
@@ -131,8 +144,187 @@ def project_get_calculate_function():
{user_input}
改写规则:
识别目标层级: 从用户输入中解析层级路径,例如字符串“安装/架空输电线路本体工程”映射为:根节点(name: '安装') -[:CHILD_OF]-> 子节点(name: '架空输电线路本体工程')。知识图谱节点标签包括ProjectDivisionItem、ProjectQuantity、Fee、FeeCollection等。
识别目标对象:仅从业务结构中识别用户问题中需要获取的核心对象类型(如 ProjectDivisionItem、ProjectQuantity、Fee、FeeCollection 等)。对象类型必须精确映射到结构中的节点标签(例如,使用 ProjectDivisionItem 而非“项目划分项”)。
识别目标层级: 从用户输入中解析层级路径,例如字符串“安装/架空输电线路本体工程”映射为:根节点(name: '安装') -[:CHILD_OF]-> 子节点(name: '架空输电线路本体工程')。
识别目标对象:仅从业务结构中识别如下类型:ProjectDivisionItem、ProjectAttributeSet、FeeSchedule、FeeItem的核心对象。对象类型必须精确映射到结构中的节点标签(例如,使用 ProjectDivisionItem 而非“项目划分项”)。
提取查询条件:从用户输入中解析关键条件(如名称、量、类型、值、层级等),条件应基于对象属性(如 name、quantity、type),注意区分查找子串和相等的区别。如果条件涉及层级路径(如路径中包含特定部分),需提取路径部分作为条件(例如,用户提到“架空输电线路”时,解析为路径匹配)。
如果用户指定层级(如“叶节点”),需在条件中体现(例如,添加 WHERE item.isLeaf = true)。
忽略任何计算、转换或后处理要求(如“乘以1000”),只关注获取原始数据对象或属性。
构建Cypher查询:生成一个Cypher查询语句,格式为:
MATCH 子句:匹配目标对象节点,并应用条件。如果涉及层级路径,使用路径匹配(如 MATCH path = (item1:ProjectDivisionItem)-[:CHILD_OF*]->(item2:ProjectDivisionItem) WHERE item1.name = '安装' AND item2.name = '架空输电线路本体工程')。变量仅用于目标节点和必要路径节点。
WHERE 子句:包含提取的条件(使用变量或具体值)。
RETURN 子句:必须返回对象(如 RETURN item),不能包含对象属性、函数(如乘法、SUM)。
LIMIT 子句: 最多返回5条。
使用业务术语在节点标签和属性中(例如,ProjectDivisionItem 而不是“项目”)。
查询应简洁,只获取数据,不执行计算。
输出格式:直接输出最终Cypher查询语句,不添加解释或额外文本。
**示例**:
用户问题:查找一下名称中包含工程的项目划分
Cypher查询语句:MATCH (item:ProjectDivisionItem)\nWHERE item.name CONTAINS '工程'\nRETURN item\nLIMIT 5
""")
return CodeExecutorPrompts(
understand_prompt=understand_prompt,
code_gen_prompt=code_gen_prompt,
code_fix_prompt=code_fix_prompt,
rewrite_prompt_template=rewrite_prompt_template,
cypher_conversion_prompt=cypher_conversion_prompt,
)
current_file = os.path.splitext(os.path.basename(__file__))[0]
now_str = datetime.now().strftime("%Y%m%d%H%M%S")
log_filename = f"{current_file}_{now_str}.log"
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[
logging.FileHandler(os.path.join("logs", log_filename), encoding="utf-8"),
logging.StreamHandler()
],
)
logger = logging.getLogger(current_file)
@dataclass
class CodeExecutorPrompts:
understand_prompt: ChatPromptTemplate
code_gen_prompt: ChatPromptTemplate
code_fix_prompt: ChatPromptTemplate
rewrite_prompt_template: ChatPromptTemplate
cypher_conversion_prompt: ChatPromptTemplate # 新增Cypher转换提示模板
class PromptManager:
def __init__(self):
self.prompts = self._init_prompts()
def _init_prompts(self) -> CodeExecutorPrompts:
understand_prompt = ChatPromptTemplate.from_template(
"""
你是一名电力造价业务专家,请基于以下示意工程文件业务结构,将用户自然语言问题改写成专业查询语句:
**示意工程文件业务结构**
{business_structure}
**改写规则**
1. **定位目标对象**:仅从示意工程文件业务结构中识别核心对象(如 `ProjectDivisionTree`→项目划分树、`FeeScheduleItem`→费用表)。
2. **提取条件**:从用户输入中解析关键条件(如名称、量、类型),用【】标注变量。
3. **构建专业语句**:格式为:`在[目标对象]中查找【条件】的项。`
- 使用业务术语(如“项目划分项”而非“项目”)。
- 条件需明确属性(如【名称】、【量】、【类型】)。
4. **精确映射结构**:若用户查询层级(如“叶节点”),需在条件中体现。
**用户输入**{user_input}
**改写输出**:(仅输出改写后的语句)
"""
)
code_gen_prompt = ChatPromptTemplate.from_template(
"""
你是一个专业的Python工程师。我会给你一个用户问题,你需要将其转换为对应的Python代码
用户问题:
{user_request}
上下文信息:
{context}
工程数据访问库:
{bowei_api_docs}
# 工作流程
1. 从用户问题中提取关键信息(节点路径、节点类型、节点名称等)
2. 根据"用户问题""上下文信息"选择最匹配的"工程数据访问库"中的函数和对象属性
3. 生成可直接执行的完全满足用户输入问题要求功能效果的Python函数代码
# 输出格式(必须严格遵循)
def project_get_calculate_function():
project = ProjectBuilder.build()
result_dict = project.[SELECTED_METHOD]([PARAMETERS])
status = result_dict.get('status', False)
message = result_dict.get('message', '')
code = result_dict.get('data', '')
data = result_dict.get('data', [])
if status:
return result_dict
else:
return result_dict
# 执行规则
- 参数必须从用户问题或上下文信息中提取
- 输出代码中必须以def project_get_calculate_function() -> dict函数作为入口函数
- 必须确保生成的代码可以直接执行,代码要注意进行各类错误检查,出错采用抛出异常方式,说明详细信息
- 禁止添加任何注释或解释
- ProjectToolkit 类中涉及项目划分的函数已考虑在其及其子孙项目划分下查找,所以无需生成递归子项目划分的代码
- 如果文本中包含范围编码格式则需要进行编码展开,如'YX2-1~7'展开为‘YX2-1/YX2-2/YX2-3/YX2-4/YX2-5/YX2-6/YX2-7
"""
)
code_fix_prompt = ChatPromptTemplate.from_template(
"""
你是一个专业的Python工程师。我会给你一段错误python代码和错误信息,你需要帮我修复这段出错的代码
已执行代码:
{code}
代码执行报错信息:
{error}
你的任务是:
1. 根据"已执行代码""代码执行报错信息"来对“已执行代码”和函数调用参数进行修改,修复执行错误
2. 如果错误信息中是代码的逻辑出现错误,那么就需要对代码本身整体结构进行修改
3. 如果是代码中参数出现问题了,那么就需要结合错误信息中的帮助信息(helper_info)来对代码总的参数进行修改
4. 修复后的代码应该完整,可以直接执行,并且能够返回查询结果
注意:
- 如果文本中包含范围编码格式则需要进行编码展开,如'YX2-1~7'展开为‘YX2-1/YX2-2/YX2-3/YX2-4/YX2-5/YX2-6/YX2-7
- 必须只输出最终的Python代码,不要添加任何解释、注释、推理过程或自然语言描述。
- 不要以“以下是修正后的代码”、“修改如下”等语句开头。
- 不要输出任何其他无关的内容。
- 输出格式必须完全符合指定的函数模板。
- 如果无法根据已有信息进行修改,请原样返回原始代码。
- 禁止在代码前加上```python字样
- 禁止在代码后加上```字样
请输出你修补后的代码:
""")
rewrite_prompt_template = ChatPromptTemplate.from_template(
"""
您是一个AI查询改写助手。基于给定的原始查询和上下文知识,生成一个精确的改写查询。步骤:
1. 从上下文知识的`labels`提取对象类型,翻译为中文。
2. 从`properties`选择对象标识:优先用`path`值,若无则用`name`值。
3. 智能映射原始查询的属性名称:
- 如果属性名称是上下文属性的缩写、省略或同义词,映射到实际属性名称(如“人工费”可能映射到“费率”或“合价含税”)。
- 如果无法映射,保留原始名称。
4. 保留原始查询的额外操作(如计算指令)。
5. 输出格式:“获取[对象标识][对象类型]的[属性]属性,[额外操作]”。
示例参考:
- 输入:原始问题="查找名称中包含“工程”的项目划分项,并返回其人工费乘以1000的值。", 上下文知识=...
- 输出="获取[安装/架空输电线路本体工程/基础工程/基础工程材料工地运输]项目划分项的人工费,并乘以1000的值"
现在,处理以下输入:
- 原始问题:{user_input}
- 上下文知识:{context}
""")
cypher_conversion_prompt = ChatPromptTemplate.from_template(
"""
你是一名电力造价业务专家,负责将用户自然语言问题中需要访问的对象识别出来,并生成针对该对象的NEO4J知识图谱的Cypher查询语句,获取该对象的全部信息。知识图谱基于从文件“获取[安装/架空输电线路本体工程]项目划分项的单位.”中读取的层级关联结构构建。该文件包含用反斜杠分割的多个字符串(如“安装/架空输电线路本体工程”),每个字符串表示一个完整的层级路径,路径部分用斜杠分隔,对应于知识图谱中ProjectDivisionItem节点的层级关系。路径映射规则:每个路径部分(如“安装”)是一个ProjectDivisionItem节点,父子关系通过关系类型`:CHILD_OF`连接,形成从根节点到叶节点的层级结构。
示例业务结构:
{business_structure}
用户问题:
{user_input}
改写规则:
识别目标层级: 从用户输入中解析层级路径,例如字符串“安装/架空输电线路本体工程”映射为:根节点(name: '安装') -[:CHILD_OF]-> 子节点(name: '架空输电线路本体工程')。
识别目标对象:仅从业务结构中识别如下类型:ProjectDivisionItem、ProjectAttributeSet、FeeSchedule、FeeItem的核心对象。对象类型必须精确映射到结构中的节点标签(例如,使用 ProjectDivisionItem 而非“项目划分项”)。
提取查询条件:从用户输入中解析关键条件(如名称、量、类型、值、层级等),条件应基于对象属性(如 name、quantity、type),注意区分查找子串和相等的区别。如果条件涉及层级路径(如路径中包含特定部分),需提取路径部分作为条件(例如,用户提到“架空输电线路”时,解析为路径匹配)。
如果用户指定层级(如“叶节点”),需在条件中体现(例如,添加 WHERE item.isLeaf = true)。
忽略任何计算、转换或后处理要求(如“乘以1000”),只关注获取原始数据对象或属性。
+87
View File
@@ -0,0 +1,87 @@
# src/user_interaction.py
import os
import logging
from datetime import datetime
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.prompts import PromptTemplate
from langchain.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field
from typing import List, Dict, Any
import asyncio
current_file = os.path.splitext(os.path.basename(__file__))[0]
now_str = datetime.now().strftime("%Y%m%d%H%M%S")
log_filename = f"{current_file}_{now_str}.log"
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[
logging.FileHandler(os.path.join("logs", log_filename), encoding="utf-8"),
logging.StreamHandler()
],
)
logger = logging.getLogger(current_file)
class BusinessObject(BaseModel):
name: str = Field(description="对象的名称")
constraints: Dict[str, Any] = Field(description="对象约束条件")
class UserInteraction:
def __init__(self, llm: ChatOpenAI, business_structure: str):
self.llm = llm
self.business_structure = business_structure
self.prompt_template = ChatPromptTemplate.from_template("""
请基于提供的"示例业务结构",分析用户输入问题。严格识别用户输入中提到的所有业务对象(对象必须来源于业务结构,不能是属性)。输出一个JSON数组,每个元素是一个对象,包含键"name""constraints"
- "name": 对象的名称,优先使用用户输入中指定的属性值(如项目划分名称、费用项名称等);如果未指定具体名称,则使用对象类型(如"ProjectDivisionItem")。
- "constraints": 一个对象,包含:
- "type": 业务结构中的对象类型(如ProjectDivisionItem、ProjectQuantity)。
- 其他属性:用户输入中明确指定的属性条件(如"path""name""类型""编码"等),使用业务结构中的属性名(英文或中文,需一致)。
注意:
- 对象只能来源于业务结构(如EngineeringData的子对象),勿将属性(如"数量""编码")识别为对象。
- 勿执行查询、过滤或汇总操作;只识别对象及其约束。
- 对于集合性描述(如"所有【定额】"),输出一个对象代表该集合,在"constraints"中指定条件。
- 输出仅基于用户输入,不推断额外信息。
示例业务结构(供参考):
{business_structure}
示例输入:从项目划分【架空输电线路本体工程/辅助工程】的费用集中查找名称为【'合计'】费用
示例输出:
[
{{
"name": "辅助工程",
"constraints": {{
"type": "ProjectDivisionItem",
"path": "架空输电线路本体工程/辅助工程",
"name": "辅助工程"
}}
}},
{{
"name": "合计",
"constraints": {{
"parent": "辅助工程",
"type": "CostItem",
"name": "合计"
}}
}}
]
用户输入:{query}
""")
self.parser = JsonOutputParser(pydantic_object=List[BusinessObject])
def understand(self, user_input: str) -> List[BusinessObject]:
prompt_text = self.prompt_template.format_prompt(query=user_input, business_structure = self.business_structure)
messages = prompt_text.to_messages()
response = self.llm.invoke(messages)
return self.parser.parse(response.text())
async def aunderstand(self, user_input: str) -> List[BusinessObject]:
prompt_text = self.prompt_template.format_prompt(query=user_input)
messages = prompt_text.to_messages()
response = await self.llm.ainvoke(messages)
return self.parser.parse(response.text())
+46
View File
@@ -0,0 +1,46 @@
{"name": "杆塔总基数", "query": "从【架空输电线路本体工程/基础工程】及其子孙项目划分中查找编码中包含【YX2-1~7】的所有【定额】的【数量】之和", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n path = '架空输电线路本体工程/基础工程'\n quantity_type = '定额'\n code = 'YX2-1/YX2-2/YX2-3/YX2-4/YX2-5/YX2-6/YX2-7'\n result_dict = project.get_quantities_node_by_parent_and_code(path, quantity_type, code)\n status = result_dict.get('status', False)\n if not status:\n return result_dict\n data = result_dict.get('data', [])\n total_quantity = 0.0\n for item in data:\n if isinstance(item, dict) and '数量' in item:\n try:\n total_quantity += float(item['数量'])\n except (ValueError, TypeError):\n continue\n return {'code': 200, 'message': 'Ok', 'status': True, 'data': total_quantity}"}
{"name": "角钢塔_塔材量", "query": "从【架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立】及其子孙项目划分中查找名称中包含【角钢】的所有【主材】的【数量】之和", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n result_dict = project.get_quantities_node_by_parent_and_name(\n parent_path=\"架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立\",\n partial_name=\"角钢\",\n quantity_type=\"主材\"\n )\n status = result_dict.get('status', False)\n if not status:\n return result_dict\n \n data = result_dict.get('data', [])\n total_quantity = 0.0\n \n for item in data:\n if isinstance(item, dict) and item.get('类型') == '主材' and '角钢' in item.get('name', ''):\n try:\n quantity = float(item.get('数量', 0))\n total_quantity += quantity\n except (ValueError, TypeError):\n continue\n \n return {\n 'code': 200,\n 'message': 'Ok',\n 'status': True,\n 'data': total_quantity\n }"}
{"name": "角钢塔_其中:高强钢塔材量", "query": "从【架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立】及其子孙项目划分中查找名称中包含【角钢、高强】的所有【主材】的【数量】之和", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n path = \"架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立\"\n result = project.get_quantities_node_by_parent_and_name(path, \"角钢、高强\", \"主材\")\n if not result.get('status', False):\n return result\n \n total_quantity = 0.0\n for item in result.get('data', []):\n if isinstance(item, dict) and item.get('类型') == '主材' and '数量' in item:\n try:\n total_quantity += float(item['数量'])\n except (ValueError, TypeError):\n continue\n \n return {\n 'code': 200,\n 'message': 'Ok',\n 'status': True,\n 'data': total_quantity\n }"}
{"name": "角钢塔_塔材装材费_元", "query": "从【架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立】及其子孙项目划分中查找名称中包含【角钢】的所有【主材】的【单价】之和", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n \n parent_path = \"架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立\"\n quantity_type = \"主材\"\n partial_name = \"角钢\"\n \n result_dict = project.get_quantities_node_by_parent_and_name(parent_path, partial_name, quantity_type)\n status = result_dict.get('status', False)\n data = result_dict.get('data', [])\n \n if not status:\n return {'code': 201, 'message': '查询失败: ' + result_dict.get('message', ''), 'status': False, 'data': []}\n \n total_price = 0.0\n for item in data:\n if isinstance(item, Material) and '单价含税' in item.__dict__:\n try:\n price = float(item.单价含税)\n total_price += price\n except (ValueError, TypeError):\n continue\n \n return {'code': 200, 'message': 'Ok', 'status': True, 'data': total_price}"}
{"name": "角钢塔_其中:高强钢塔材费用_元", "query": "从【架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立】及其子孙项目划分中查找名称中包含【角钢、高强】的所有【主材】的【单价】之和", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n \n parent_path = \"架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立\"\n quantity_type = \"主材\"\n partial_name = \"角钢、高强\"\n \n result_dict = project.get_quantities_node_by_parent_and_name(parent_path, partial_name, quantity_type)\n status = result_dict.get('status', False)\n data = result_dict.get('data', [])\n \n if not status:\n return {'code': 201, 'message': '查询失败: ' + result_dict.get('message', ''), 'status': False, 'data': []}\n \n total_price = 0.0\n for item in data:\n if isinstance(item, dict) and '单价含税' in item:\n try:\n price = float(item['单价含税'])\n total_price += price\n except (ValueError, TypeError):\n continue\n \n return {'code': 200, 'message': 'Ok', 'status': True, 'data': total_price}"}
{"name": "钢管塔_塔材量", "query": "从【架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立】及其子孙项目划分中查找名称中包含【钢管塔】的所有【主材】的【数量】之和", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n result_dict = project.get_quantities_node_by_parent_and_name(\n parent_path=\"架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立\",\n partial_name=\"钢管塔\",\n quantity_type=\"主材\"\n )\n status = result_dict.get('status', False)\n message = result_dict.get('message', '')\n data = result_dict.get('data', [])\n \n if not status:\n return {'code': 201, 'message': message, 'status': False, 'data': 0}\n \n total_quantity = 0\n for item in data:\n if isinstance(item, dict) and item.get('类型') == '主材' and '钢管塔' in item.get('name', ''):\n try:\n quantity = float(item.get('数量', 0))\n total_quantity += quantity\n except (ValueError, TypeError):\n continue\n \n return {'code': 200, 'message': 'Ok', 'status': True, 'data': total_quantity}"}
{"name": "钢管塔_钢管价格_元", "query": "从【架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立】及其子孙项目划分中查找名称中包含【钢管塔】的所有【主材】的【单价】之和", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n parent_path = \"架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立\"\n result_dict = project.get_quantities_node_by_parent_and_name(parent_path, \"钢管塔\", \"主材\")\n status = result_dict.get('status', False)\n data = result_dict.get('data', [])\n if not status:\n return result_dict\n \n total_price = 0.0\n for item in data:\n if isinstance(item, Material) and \"钢管塔\" in item.name:\n try:\n price = float(item.单价含税) if item.单价含税 else 0.0\n total_price += price\n except (ValueError, AttributeError):\n continue\n \n return {\n 'code': 200,\n 'message': 'Ok',\n 'status': True,\n 'data': total_price\n }"}
{"name": "钢管杆_塔材量", "query": "从【架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立】及其子孙项目划分中查找名称中包含【钢管杆】的所有【主材】的【数量】之和", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n path = \"架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立\"\n result_dict = project.get_quantities_node_by_parent_and_name(path, \"钢管杆\", \"主材\")\n status = result_dict.get('status', False)\n data = result_dict.get('data', [])\n if not status:\n return result_dict\n \n total_quantity = 0.0\n for item in data:\n if isinstance(item, Material) and \"钢管杆\" in item.name:\n try:\n quantity = float(item.数量) if item.数量 else 0.0\n total_quantity += quantity\n except (ValueError, TypeError):\n continue\n \n return {\n 'code': 200,\n 'message': 'Ok',\n 'status': True,\n 'data': total_quantity\n }"}
{"name": "钢管杆_钢管价格_元", "query": "从【架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立】及其子孙项目划分中查找名称中包含【钢管杆】的所有【主材】的【单价】之和", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n result = project.get_division_node_by_parent_and_name(\n parent_path=\"架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立\",\n partial_name=\"钢管杆\"\n )\n if not result.get('status', False):\n return result\n \n total_price = 0.0\n for division in result.get('data', []):\n materials_result = project.get_quantities_node_by_parent_and_name(\n parent_path=division['constraints']['path'],\n partial_name=\"主材\",\n quantity_type=\"主材\"\n )\n if not materials_result.get('status', False):\n continue\n \n for material in materials_result.get('data', []):\n if \"钢管杆\" in material.get('name', ''):\n price = float(material.get('单价含税', 0)) if material.get('单价含税') else 0.0\n total_price += price\n \n return {\n 'code': 200,\n 'message': 'Ok',\n 'status': True,\n 'data': total_price\n }"}
{"name": "导线及线材_分裂数", "query": "从【架空输电线路本体工程/架线工程】及其子孙项目划分中查找编码中包含【['YX5-67', 'YX5-68', 'YX5-69', 'YX5-78', 'YX5-79', 'YX5-59', 'YX5-60', 'YX5-61', 'YX5-62', 'YX5-63', 'YX5-64', 'YX5-65', 'YX5-66', 'YX5-74', 'YX5-75', 'YX5-76', 'YX5-77', 'YX5-52', 'YX5-53', 'YX5-54', 'YX5-55', 'YX5-56', 'YX5-57', 'YX5-58', 'YX5-70', 'YX5-71', 'YX5-72', 'YX5-73', 'YX5-14', 'YX5-15', 'YX5-16', 'YX5-17', 'YX5-43', 'YX5-44', 'YX5-45', 'YX5-46', 'YX5-47', 'YX5-48', 'YX5-49', 'YX5-50', 'YX5-51', 'YX5-10', 'YX5-11', 'YX5-12', 'YX5-13', 'YX5-38', 'YX5-39', 'YX5-40', 'YX5-41', 'YX5-42', 'YX5-8', 'YX5-9']】的所有【定额】的【参数】之和", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n target_codes = ['YX5-67', 'YX5-68', 'YX5-69', 'YX5-78', 'YX5-79', 'YX5-59', 'YX5-60', 'YX5-61', 'YX5-62', 'YX5-63', 'YX5-64', 'YX5-65', 'YX5-66', 'YX5-74', 'YX5-75', 'YX5-76', 'YX5-77', 'YX5-52', 'YX5-53', 'YX5-54', 'YX5-55', 'YX5-56', 'YX5-57', 'YX5-58', 'YX5-70', 'YX5-71', 'YX5-72', 'YX5-73', 'YX5-14', 'YX5-15', 'YX5-16', 'YX5-17', 'YX5-43', 'YX5-44', 'YX5-45', 'YX5-46', 'YX5-47', 'YX5-48', 'YX5-49', 'YX5-50', 'YX5-51', 'YX5-10', 'YX5-11', 'YX5-12', 'YX5-13', 'YX5-38', 'YX5-39', 'YX5-40', 'YX5-41', 'YX5-42', 'YX5-8', 'YX5-9']\n parent_path = '架空输电线路本体工程/架线工程'\n quantity_type = '定额'\n total_parameter = 0.0\n \n for code in target_codes:\n result_dict = project.get_quantities_node_by_parent_and_code(parent_path, quantity_type, code)\n status = result_dict.get('status', False)\n if not status:\n continue\n data = result_dict.get('data', [])\n if isinstance(data, list):\n for item in data:\n if isinstance(item, dict) and '参数' in item:\n try:\n total_parameter += float(item['参数'])\n except (ValueError, TypeError):\n pass\n elif isinstance(data, dict) and '参数' in data:\n try:\n total_parameter += float(data['参数'])\n except (ValueError, TypeError):\n pass\n \n return {\n 'code': 200,\n 'message': 'Ok',\n 'status': True,\n 'data': total_parameter\n }"}
{"name": "导线及线材_其中:节能导线量", "query": "从【架空输电线路本体工程/架线工程/导地线架设@@架空输电线路本体工程/架线工程/导地线跨越架设】及其子孙项目划分中查找名称中包含【高导电率】的所有【主材】的【数量】之和", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n total_quantity = 0.0\n \n paths = [\n \"架空输电线路本体工程/架线工程/导地线架设\",\n \"架空输电线路本体工程/架线工程/导地线跨越架设\"\n ]\n \n for path in paths:\n result = project.get_quantities_node_by_parent_and_name(\n parent_path=path,\n partial_name=\"高导电率\",\n quantity_type=\"主材\"\n )\n \n if not result.get('status', False):\n continue\n \n materials = result.get('data', [])\n if not isinstance(materials, list):\n materials = [materials]\n \n for material in materials:\n if not isinstance(material, dict):\n continue\n if '数量' in material:\n try:\n total_quantity += float(material['数量'])\n except (ValueError, TypeError):\n pass\n \n return {\n 'code': 200 if total_quantity > 0 else 201,\n 'message': 'Ok' if total_quantity > 0 else 'No matching materials found',\n 'status': total_quantity > 0,\n 'data': total_quantity\n }"}
{"name": "导线及线材_导线装材费_元", "query": "从【架空输电线路本体工程/架线工程/导地线架设@@架空输电线路本体工程/架线工程/导地线跨越架设】及其子孙项目划分中查找名称中包含【线】的所有【主材】的【单价】之和", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n total_price = 0.0\n \n paths = [\n \"架空输电线路本体工程/架线工程/导地线架设\",\n \"架空输电线路本体工程/架线工程/导地线跨越架设\"\n ]\n \n for path in paths:\n result = project.get_quantities_node_by_parent_and_name(\n parent_path=path,\n partial_name=\"线\",\n quantity_type=\"主材\"\n )\n \n if not result.get('status', False):\n continue\n \n materials = result.get('data', [])\n for material in materials:\n if isinstance(material, dict):\n price_str = material.get('单价含税', '0')\n try:\n price = float(price_str) if price_str else 0.0\n total_price += price\n except ValueError:\n continue\n \n return {\n 'code': 200,\n 'message': 'Ok',\n 'status': True,\n 'data': total_price\n }"}
{"name": "导线及线材_其中:节能导线费用_元", "query": "从【架空输电线路本体工程/架线工程/导地线架设@@架空输电线路本体工程/架线工程/导地线跨越架设】及其子孙项目划分中查找名称中包含【高导电率】的所有【主材】的【单价】之和", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n total_price = 0.0\n \n paths = [\n \"架空输电线路本体工程/架线工程/导地线架设\",\n \"架空输电线路本体工程/架线工程/导地线跨越架设\"\n ]\n \n for path in paths:\n result = project.get_quantities_node_by_parent_and_name(\n parent_path=path,\n partial_name=\"高导电率\",\n quantity_type=\"主材\"\n )\n \n if not result.get('status', False):\n continue\n \n materials = result.get('data', [])\n for material in materials:\n if not isinstance(material, dict):\n continue\n \n price_str = material.get('单价含税', '0')\n try:\n price = float(price_str)\n total_price += price\n except ValueError:\n continue\n \n return {\n 'code': 200,\n 'message': 'Ok',\n 'status': True,\n 'data': total_price\n }"}
{"name": "导线及线材_导线类型", "query": "从【架空输电线路本体工程/架线工程/导地线架设@@架空输电线路本体工程/架线工程/导地线跨越架设@@架空输电线路本体工程/架线工程/其他架线工程】及其子孙项目划分中查找名称中包含【['钢芯铝绞线', '铝包钢芯铝绞线', '中强度铝合金绞线', '铝合金芯铝绞线', '铝合金芯高导电率铝绞线', '钢芯高导电率铝绞线', '特高强度钢芯铝合金绞线', '扩径导线', '耐热导线', '碳纤维导线']】的所有【主材】的【参数】之和", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n parent_paths = [\n \"架空输电线路本体工程/架线工程/导地线架设\",\n \"架空输电线路本体工程/架线工程/导地线跨越架设\",\n \"架空输电线路本体工程/架线工程/其他架线工程\"\n ]\n material_names = [\n '钢芯铝绞线', '铝包钢芯铝绞线', '中强度铝合金绞线', '铝合金芯铝绞线', \n '铝合金芯高导电率铝绞线', '钢芯高导电率铝绞线', '特高强度钢芯铝合金绞线', \n '扩径导线', '耐热导线', '碳纤维导线'\n ]\n total_params = 0.0\n\n for parent_path in parent_paths:\n for material_name in material_names:\n result_dict = project.get_quantities_node_by_parent_and_name(\n parent_path=parent_path,\n partial_name=material_name,\n quantity_type='主材'\n )\n if not result_dict.get('status', False):\n continue\n materials = result_dict.get('data', [])\n for material in materials:\n if isinstance(material, dict) and '参数' in material:\n try:\n param_value = float(material['参数'])\n total_params += param_value\n except (ValueError, TypeError):\n continue\n\n return {\n 'code': 200,\n 'message': 'Ok',\n 'status': True,\n 'data': total_params\n }"}
{"name": "基础钢材量", "query": "从【架空输电线路本体工程/基础工程/基础砌筑】及其子孙项目划分中查找名称中包含【圆钢】的所有【主材】的【数量】之和", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n result_dict = project.get_quantities_node_by_parent_and_name(\n parent_path=\"架空输电线路本体工程/基础工程/基础砌筑\",\n partial_name=\"圆钢\",\n quantity_type=\"主材\"\n )\n status = result_dict.get('status', False)\n if not status:\n return result_dict\n \n data = result_dict.get('data', [])\n total_quantity = 0.0\n \n for item in data:\n if isinstance(item, dict) and item.get(\"类型\") == \"主材\" and \"圆钢\" in item.get(\"name\", \"\"):\n try:\n quantity = float(item.get(\"数量\", 0))\n total_quantity += quantity\n except (ValueError, TypeError):\n continue\n \n return {\n 'code': 200,\n 'message': 'Ok',\n 'status': True,\n 'data': total_quantity\n }"}
{"name": "基础钢材价格", "query": "从【架空输电线路本体工程/基础工程/基础砌筑】及其子孙项目划分中查找名称中包含【圆钢】的所有【主材】的【单价】之和", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n base_path = \"架空输电线路本体工程/基础工程/基础砌筑\"\n total_price = 0.0\n \n def process_division(path):\n nonlocal total_price\n quantities_result = project.get_quantities_node_by_parent_and_name(path, \"圆钢\", \"主材\")\n if quantities_result.get('status'):\n for material_data in quantities_result.get('data', []):\n material = Material()\n material.__dict__.update(material_data)\n if \"单价含税\" in material.__dict__ and material.单价含税:\n try:\n total_price += float(material.单价含税)\n except (ValueError, TypeError):\n pass\n \n children_result = project.get_division_node_by_parent_and_name(path, \"\")\n if children_result.get('status'):\n for child_data in children_result.get('data', []):\n if 'path' in child_data:\n process_division(child_data['path'])\n \n process_division(base_path)\n \n return {\n 'code': 200,\n 'message': 'Ok',\n 'status': True,\n 'data': total_price\n }"}
{"name": "本体费用合计_元", "query": "从【工程费用】中获取【架空输电线路本体工程.合计费】的属性", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n result_dict = project.get_fee_schedule_on_Engineering_Cost_table(\"工程费用\", \"架空输电线路本体工程.合计费\", \"合计费\")\n status = result_dict.get('status', False)\n message = result_dict.get('message', '')\n code = result_dict.get('code', '')\n data = result_dict.get('data', {})\n if status:\n return result_dict\n else:\n return result_dict"}
{"name": "本体工程人工费_本体_元", "query": "从【架空输电线路本体工程】项目划分中获取名称属于【本体工程人工费_本体_元】的费用", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n result_dict = project.get_division_node_by_parent_and_name('架空输电线路本体工程', '本体工程人工费_本体_元')\n status = result_dict.get('status', False)\n message = result_dict.get('message', '')\n code = result_dict.get('code', '')\n data = result_dict.get('data', [])\n if status:\n return result_dict\n else:\n return result_dict"}
{"name": "本体工程人工费_调试_元", "query": "从【架空输电线路本体工程】项目划分中获取名称属于【本体工程人工费_调试_元】的费用", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n result_dict = project.get_division_node_by_parent_and_name('架空输电线路本体工程', '本体工程人工费_调试_元')\n status = result_dict.get('status', False)\n message = result_dict.get('message', '')\n code = result_dict.get('code', '')\n data = result_dict.get('data', [])\n if status:\n return result_dict\n else:\n return result_dict"}
{"name": "本体工程机械费_本体_元", "query": "从【架空输电线路本体工程】项目划分中获取名称属于【本体工程机械费_本体_元】的费用", "code": "根据用户问题和上下文信息,我将生成对应的Python代码:\n\n```python\ndef project_get_calculate_function():\n project = ProjectBuilder.build()\n result_dict = project.get_fee_table_by_project_division(\n project_division_path=\"架空输电线路本体工程\",\n fee_name=\"本体工程机械费_本体_元\"\n )\n status = result_dict.get('status', False)\n message = result_dict.get('message', '')\n code = result_dict.get('data', '')\n data = result_dict.get('data', [])\n if status:\n return result_dict\n else:\n return result_dict\n```"}
{"name": "本体工程机械费_调试_元", "query": "从【架空输电线路本体工程】项目划分中获取名称属于【本体工程机械费_调试_元】的费用", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n result_dict = project.get_division_node_by_parent_and_name('架空输电线路本体工程', '本体工程机械费_调试_元')\n status = result_dict.get('status', False)\n message = result_dict.get('message', '')\n code = result_dict.get('code', '')\n data = result_dict.get('data', [])\n if status:\n return result_dict\n else:\n return result_dict"}
{"name": "基础工程费用_元", "query": "从【架空输电线路本体工程/基础工程】项目划分中获取名称属于【基础工程费用_元】的费用", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n result_dict = project.get_fee_table_by_project_division(\"架空输电线路本体工程/基础工程\", \"基础工程费用_元\")\n status = result_dict.get('status', False)\n message = result_dict.get('message', '')\n code = result_dict.get('code', '')\n data = result_dict.get('data', {})\n if status:\n return result_dict\n else:\n return result_dict"}
{"name": "杆塔工程费用_元", "query": "从【架空输电线路本体工程/杆塔工程】项目划分中获取名称属于【杆塔工程费用_元】的费用", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n result_dict = project.get_fee_table_by_project_division(\"架空输电线路本体工程/杆塔工程\", \"杆塔工程费用_元\")\n status = result_dict.get('status', False)\n message = result_dict.get('message', '')\n code = result_dict.get('code', '')\n data = result_dict.get('data', {})\n if status:\n return result_dict\n else:\n return result_dict"}
{"name": "接地工程费用_元", "query": "从【架空输电线路本体工程/接地工程】项目划分中获取名称属于【接地工程费用_元】的费用", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n result_dict = project.get_fee_table_by_project_division(\"架空输电线路本体工程/接地工程\", \"接地工程费用_元\")\n status = result_dict.get('status', False)\n message = result_dict.get('message', '')\n code = result_dict.get('code', '')\n data = result_dict.get('data', {})\n if status:\n return result_dict\n else:\n return result_dict"}
{"name": "架线工程费用_元", "query": "从【架空输电线路本体工程/架线工程】项目划分中获取名称属于【架线工程费用_元】的费用", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n result_dict = project.get_fee_table_by_project_division(\"架空输电线路本体工程/架线工程\", \"架线工程费用_元\")\n status = result_dict.get('status', False)\n message = result_dict.get('message', '')\n code = result_dict.get('code', '')\n data = result_dict.get('data', {})\n if status:\n return result_dict\n else:\n return result_dict"}
{"name": "附件工程费用_元", "query": "从【架空输电线路本体工程/附件安装工程】项目划分中获取名称属于【附件工程费用_元】的费用", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n result_dict = project.get_fee_table_by_project_division(\"架空输电线路本体工程/附件安装工程\", \"附件工程费用_元\")\n status = result_dict.get('status', False)\n message = result_dict.get('message', '')\n code = result_dict.get('code', '')\n data = result_dict.get('data', {})\n if status:\n return result_dict\n else:\n return result_dict"}
{"name": "辅助工程费用_元", "query": "从【架空输电线路本体工程/辅助工程】项目划分中获取名称属于【辅助工程费用_元】的费用", "code": "根据用户问题和上下文信息,我们需要从\"架空输电线路本体工程/辅助工程\"项目划分中获取名称为\"辅助工程费用_元\"的费用。根据工程数据访问库,最匹配的方法是`get_fee_table_by_project_division`。\n\ndef project_get_calculate_function():\n project = ProjectBuilder.build()\n result_dict = project.get_fee_table_by_project_division(\n project_division_path=\"架空输电线路本体工程/辅助工程\",\n fee_name=\"辅助工程费用_元\"\n )\n status = result_dict.get('status', False)\n if not status:\n raise Exception(f\"获取费用失败: {result_dict.get('message', '未知错误')}\")\n return result_dict"}
{"name": "辅助工程费用_调试_元", "query": "从【架空输电线路本体工程/辅助工程】项目划分中获取名称属于【辅助工程费用_调试_元】的费用", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n result_dict = project.get_fee_table_by_project_division(\"架空输电线路本体工程/辅助工程\", \"辅助工程费用_调试_元\")\n status = result_dict.get('status', False)\n message = result_dict.get('message', '')\n code = result_dict.get('code', '')\n data = result_dict.get('data', {})\n if status:\n return result_dict\n else:\n return result_dict"}
{"name": "辅助设施工程_元", "query": "从【工程费用】中获取【辅助设施工程.合计费】的属性", "code": "根据用户问题和上下文信息,我们需要从工程费用表中获取\"辅助设施工程.合计费\"的属性。根据工程数据访问库,最匹配的方法是`get_fee_schedule_on_Engineering_Cost_table`。\n\n```python\ndef project_get_calculate_function():\n project = ProjectBuilder.build()\n result_dict = project.get_fee_schedule_on_Engineering_Cost_table(\"工程费用\", \"辅助设施工程.合计费\", \"合计费\")\n status = result_dict.get('status', False)\n message = result_dict.get('message', '')\n code = result_dict.get('code', '')\n data = result_dict.get('data', {})\n if status:\n return result_dict\n else:\n return result_dict\n```"}
{"name": "其他费用合计_元", "query": "从【工程费用】中获取【其他费用.合计费】的属性", "code": "根据用户问题和上下文信息,我们需要从\"工程费用\"中获取\"其他费用.合计费\"的属性。根据上下文信息,\"工程费用\"的类型是\"FeeScheduleItem\",而\"其他费用.合计费\"的类型是\"Fee\"。\n\n最匹配的方法是`get_fee_schedule_on_Engineering_Cost_table`,因为它专门用于在工程费用表中查找费用。\n\n```python\ndef project_get_calculate_function():\n project = ProjectBuilder.build()\n result_dict = project.get_fee_schedule_on_Engineering_Cost_table(\n table_name=\"工程费用表\",\n fee_name=\"合计费\",\n fee=\"其他费用\"\n )\n status = result_dict.get('status', False)\n message = result_dict.get('message', '')\n code = result_dict.get('data', '')\n data = result_dict.get('data', [])\n if status:\n return result_dict\n else:\n return result_dict\n```"}
{"name": "建场费合计_元", "query": "从【其他费用】中获取【建设场地征用及清理费】的属性", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n result_dict = project.get_fee_schedule_on_other_expense_table('其他费用表', '建设场地征用及清理费', 'amount')\n status = result_dict.get('status', False)\n message = result_dict.get('message', '')\n code = result_dict.get('code', '')\n data = result_dict.get('data', {})\n if status:\n return result_dict\n else:\n return result_dict"}
{"name": "项目建设管理费合计_元", "query": "从【其他费用】中获取【项目建设管理费】的属性", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n result_dict = project.get_fee_schedule_on_other_expense_table('其他费用表', '项目建设管理费', 'amount')\n status = result_dict.get('status', False)\n message = result_dict.get('message', '')\n code = result_dict.get('code', '')\n data = result_dict.get('data', {})\n if status:\n return result_dict\n else:\n return result_dict"}
{"name": "其中:工程监理费_元", "query": "从【其他费用】中获取【工程监理费】的属性", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n result_dict = project.get_fee_schedule_on_other_expense_table('其他费用表', '工程监理费', 'rate')\n status = result_dict.get('status', False)\n message = result_dict.get('message', '')\n code = result_dict.get('code', '')\n data = result_dict.get('data', {})\n if status:\n return result_dict\n else:\n return result_dict"}
{"name": "项目建设技术服务费合计_元", "query": "从【其他费用】中获取【项目建设技术服务费】的属性", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n result_dict = project.get_fee_schedule_on_other_expense_table('其他费用表', '项目建设技术服务费', 'amount')\n status = result_dict.get('status', False)\n message = result_dict.get('message', '')\n code = result_dict.get('code', '')\n data = result_dict.get('data', {})\n if status:\n return result_dict\n else:\n return result_dict"}
{"name": "其中:项目前期工作费_元", "query": "从【其他费用】中获取【项目前期工作费】的属性", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n result_dict = project.get_fee_schedule_on_other_expense_table('其他费用表', '项目前期工作费', 'amount')\n status = result_dict.get('status', False)\n message = result_dict.get('message', '')\n code = result_dict.get('code', '')\n data = result_dict.get('data', {})\n if status:\n return result_dict\n else:\n return result_dict"}
{"name": "其中:勘察费_元", "query": "从【其他费用】中获取【勘察费】的属性", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n result_dict = project.get_fee_schedule_on_other_expense_table('其他费用表', '勘察费', 'rate')\n status = result_dict.get('status', False)\n message = result_dict.get('message', '')\n code = result_dict.get('code', '')\n data = result_dict.get('data', {})\n if status:\n return result_dict\n else:\n return result_dict"}
{"name": "其中:设计费_元", "query": "从【其他费用】中获取【设计费】的属性", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n result_dict = project.get_fee_schedule_on_other_expense_table('其他费用表', '设计费', 'rate')\n status = result_dict.get('status', False)\n message = result_dict.get('message', '')\n code = result_dict.get('code', '')\n data = result_dict.get('data', {})\n if status:\n return result_dict\n else:\n return result_dict"}
{"name": "其中:工程建设检测费_元", "query": "从【其他费用】中获取【工程建设检测费】的属性", "code": "根据用户问题和上下文信息,我们需要从\"其他费用\"表中获取\"工程建设检测费\"的属性。根据上下文信息,\"其他费用\"是一个FeeScheduleItem类型,而\"工程建设检测费\"是一个Fee类型。\n\n最匹配的方法是`get_fee_schedule_on_other_expense_table`,因为它专门用于在其他费用表中查找费用。\n\n```python\ndef project_get_calculate_function():\n project = ProjectBuilder.build()\n result_dict = project.get_fee_schedule_on_other_expense_table(\n table_name=\"其他费用\",\n fee_name=\"工程建设检测费\",\n fee=\"*\"\n )\n status = result_dict.get('status', False)\n message = result_dict.get('message', '')\n code = result_dict.get('code', '')\n data = result_dict.get('data', {})\n if status:\n return result_dict\n else:\n return result_dict\n```"}
{"name": "生产准备费_元", "query": "从【其他费用】中获取【生产准备费】的属性", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n result_dict = project.get_fee_schedule_on_other_expense_table(table_name='其他费用表', fee_name='生产准备费', fee='amount')\n status = result_dict.get('status', False)\n message = result_dict.get('message', '')\n code = result_dict.get('code', '')\n data = result_dict.get('data', {})\n if status:\n return result_dict\n else:\n return result_dict"}
{"name": "其中:安全文明施工费_线路_元", "query": "从【架空输电线路本体工程】项目划分中获取名称属于【其中:安全文明施工费_线路_元】的费用", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n result_dict = project.get_division_node_by_parent_and_name('架空输电线路本体工程', '其中:安全文明施工费_线路_元')\n status = result_dict.get('status', False)\n message = result_dict.get('message', '')\n code = result_dict.get('code', '')\n data = result_dict.get('data', [])\n if status:\n return result_dict\n else:\n return result_dict"}
{"name": "其中:安全文明施工费_调试_元", "query": "从【架空输电线路本体工程】项目划分中获取名称属于【其中:安全文明施工费_调试_元】的费用", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n result_dict = project.get_division_node_by_parent_and_name('架空输电线路本体工程', '其中:安全文明施工费_调试_元')\n status = result_dict.get('status', False)\n message = result_dict.get('message', '')\n code = result_dict.get('code', '')\n data = result_dict.get('data', [])\n if status:\n return result_dict\n else:\n return result_dict"}
{"name": "基本预备费_元", "query": "从【工程费用】中获取【基本预备费.合计费】的属性", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n result_dict = project.get_fee_schedule_on_Engineering_Cost_table(\"工程费用\", \"基本预备费.合计费\", \"合计费\")\n status = result_dict.get('status', False)\n message = result_dict.get('message', '')\n code = result_dict.get('code', '')\n data = result_dict.get('data', {})\n if status:\n return result_dict\n else:\n return result_dict"}
{"name": "静态投资_元", "query": "从【工程费用】中获取【工程静态投资(一~七项合计).合计费】的属性", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n result_dict = project.get_fee_schedule_on_Engineering_Cost_table(\"工程费用表\", \"工程静态投资(一~七项合计).合计费\", \"合计费\")\n status = result_dict.get('status', False)\n message = result_dict.get('message', '')\n code = result_dict.get('code', '')\n data = result_dict.get('data', {})\n if status:\n return result_dict\n else:\n return result_dict"}
{"name": "建设期利息_元", "query": "从【工程费用】中获取【建设期贷款利息.合计费】的属性", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n result_dict = project.get_fee_schedule_on_Engineering_Cost_table(\"工程费用\", \"建设期贷款利息.合计费\", \"合计费\")\n status = result_dict.get('status', False)\n message = result_dict.get('message', '')\n code = result_dict.get('code', '')\n data = result_dict.get('data', {})\n if status:\n return result_dict\n else:\n return result_dict"}
{"name": "动态投资_元", "query": "从【工程费用】中获取【工程动态投资(一~八项合计).合计费】的属性", "code": "根据用户问题和上下文信息,我们需要从\"工程费用\"表中获取\"工程动态投资(一~八项合计).合计费\"的属性。根据工程数据访问库,最匹配的方法是`get_fee_schedule_on_Engineering_Cost_table`。\n\n```python\ndef project_get_calculate_function():\n project = ProjectBuilder.build()\n result_dict = project.get_fee_schedule_on_Engineering_Cost_table(\n table_name=\"工程费用\",\n fee_name=\"工程动态投资(一~八项合计)\",\n fee=\"合计费\"\n )\n status = result_dict.get('status', False)\n message = result_dict.get('message', '')\n code = result_dict.get('code', '')\n data = result_dict.get('data', {})\n if status:\n return result_dict\n else:\n return result_dict\n```"}
{"name": "增值税抵扣税额_元", "query": "从【工程费用】中获取【其中:可抵扣增值税额.合计费】的属性", "code": "def project_get_calculate_function():\n project = ProjectBuilder.build()\n result_dict = project.get_fee_schedule_on_Engineering_Cost_table('工程费用表', '其中:可抵扣增值税额.合计费', '合计费')\n status = result_dict.get('status', False)\n message = result_dict.get('message', '')\n code = result_dict.get('code', '')\n data = result_dict.get('data', {})\n if status:\n return result_dict\n else:\n return result_dict"}
View File
+42 -22
View File
@@ -2,9 +2,25 @@ import os
import sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import logging
def setup_logger(logger_name):
"""
设置指定名称的logger,将其级别设置为WARNING并禁用传播
:param logger_name: logger的名称
"""
logger = logging.getLogger(logger_name)
logger.setLevel(logging.WARNING) # 设置httpcore及其子模块的级别
logger.propagate = False # 可选:禁用传播(防止被根logger处理)
return logger
logger_names = ["httpx", "openai", "langsmith.client", "neo4j", "urllib3", "httpcore"]
for name in logger_names:
setup_logger(name)
import json
from src.dialog_manager import DialogManager
from src.llm_client import LLMClient
from src.multi_llm_client import MultiAPIKeyChatOpenAI
from src.code_executor import CodeExecutor
from src.neo4j_raw_retriever import Neo4jRawRetriever
from src.prompt_manager import PromptManager
@@ -13,18 +29,25 @@ from src.config import Config
from src.document_loader import load_file
from src.embedding_client import EmbeddingClient
from project import ProjectBuilder, ProjectToolkit
from project_implementation import ProjectToolkitNeo4j
from src.project import ProjectBuilder, ProjectToolkit
from src.project_implementation import ProjectToolkitNeo4j
success_count = 0
fail_count = 0
questions = []
error_list = []
def main():
global fail_count, success_count, questions, error_list
config = Config()
business_structure = load_file(config.business_object_structure_path)
bowei_api_docs = load_file(config.bowei_api_docs_path)
llm_client = LLMClient(config.openai)
llm_client = MultiAPIKeyChatOpenAI(config.openai)
llm_client_coder = LLMClient(config.openai_coder)
llm_client_coder = MultiAPIKeyChatOpenAI(config.openai_coder)
prompt_manager = PromptManager()
@@ -38,7 +61,7 @@ def main():
ProjectBuilder.register(ProjectToolkitNeo4j, knowledge_retriever.driver)
code_executor = CodeExecutor(prompt_manager.prompts, llm_client_coder)
code_executor = CodeExecutor(prompt_manager.prompts, llm_client_coder, config.max_retries)
dialog_manager = DialogManager(
llm_client,
@@ -55,37 +78,34 @@ def main():
# 提取指标映射关系并批量执行
for item in zhibiao_data:
datasource = item['数据来源']
if datasource == '报表指标' or datasource == '指标库':
continue
query = item['指标描述']['指标映射']
rewritten_results = dialog_manager.understand_user_question_stream(query)
#rewritten_results = dialog_manager.understand_user_question(query)
rewritten_results = []
if rewritten_results is None or rewritten_results == []:
print('问题: {} 没有找到符合要求的数据'.format(query))
fail_count += 1
error_list.append(f"问题 {query} 调用 understand_user_question 返回空结果")
continue
selected_rewritten, selected_knowledge = rewritten_results[0]
questions = []
success_count = 0
fail_count = 0
error_list = []
# 检查 understand_user_question_stream 方法调用结果,假设存在该方法调用
# 示例调用,实际使用时请替换为真实调用
# stream_result = dialog_manager.understand_user_question_stream(query)
# if stream_result is None or stream_result == []:
# questions.append(query)
# fail_count += 1
# error_list.append(f"问题 {query} 调用 understand_user_question_stream 返回空结果")
result = dialog_manager.execute_generated_code(selected_rewritten, selected_knowledge)
if result is None or result == []:
questions.append(selected_rewritten)
fail_count += 1
error_list.append(f"问题 {selected_rewritten} 调用 execute_generated_code 返回空结果")
error_list.append(f"问题 {query} {selected_rewritten} 调用 execute_generated_code 返回空结果")
else:
success_count += 1
print(result)
#print(result)
if __name__ == "__main__":
main()
total = success_count + fail_count
if total > 0:
success_rate = success_count / total
@@ -98,5 +118,5 @@ if __name__ == "__main__":
print("错误列表:")
for error in error_list:
print(error)
main()
+171
View File
@@ -0,0 +1,171 @@
import os
import sys
import logging
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from datetime import datetime
# 获取当前时间,格式化为字符串
now_str = datetime.now().strftime("%Y%m%d%H%M%S")
log_filename = f"test_code1{now_str}.log"
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[
logging.FileHandler(os.path.join("logs", log_filename), encoding="utf-8"),
logging.StreamHandler()
],
)
logger = logging.getLogger("test_code1")
import logging
def setup_logger(logger_name):
"""
设置指定名称的logger,将其级别设置为WARNING并禁用传播
:param logger_name: logger的名称
"""
logger = logging.getLogger(logger_name)
logger.setLevel(logging.WARNING) # 设置httpcore及其子模块的级别
logger.propagate = False # 可选:禁用传播(防止被根logger处理)
return logger
logger_names = ["httpx", "openai", "langsmith.client", "neo4j", "urllib3", "httpcore"]
for name in logger_names:
setup_logger(name)
import json
import os
from src.dialog_manager import DialogManager
from src.multi_llm_client import MultiAPIKeyChatOpenAI
from src.code_executor import CodeExecutor
from src.neo4j_raw_retriever import Neo4jRawRetriever
from src.prompt_manager import PromptManager
import yaml
from src.config import Config
from src.document_loader import load_file
from src.embedding_client import EmbeddingClient
from src.project import ProjectBuilder, ProjectToolkit
from src.project_implementation import ProjectToolkitNeo4j
success_count = 0
fail_count = 0
questions = []
error_list = []
success_results = []
fail_results = []
def main():
global fail_count, success_count, questions, error_list
config = Config()
business_structure = load_file(config.business_object_structure_path)
bowei_api_docs = load_file(config.bowei_api_docs_path)
llm_client = MultiAPIKeyChatOpenAI(config.openai)
llm_client_coder = MultiAPIKeyChatOpenAI(config.openai_coder)
prompt_manager = PromptManager()
neo4j_conf = config.neo4j_conf
embedding_conf = config.embedding
embedding_client = EmbeddingClient(embedding_conf)
# 创建Neo4j检索器
knowledge_retriever = Neo4jRawRetriever(neo4j_conf)
ProjectBuilder.register(ProjectToolkitNeo4j, knowledge_retriever.driver)
code_executor = CodeExecutor(prompt_manager.prompts, llm_client_coder, config.max_retries)
dialog_manager = DialogManager(
llm_client,
business_structure,
bowei_api_docs,
code_executor,
knowledge_retriever,
prompt_manager,
)
# 加载 zhibiao.jsonl
zhibiao_data = []
with open('./tests/zhibiao.jsonl', 'r', encoding='utf-8') as f:
for line in f:
zhibiao_data.append(json.loads(line))
# 提取指标映射关系并批量执行
for item in zhibiao_data:
query = item['query']
selected_knowledge = item['result']
result = dialog_manager.generated_code(query, selected_knowledge)
if isinstance(result, dict) and result.get('status', False):
code = result['data']
result = dialog_manager.execute_code(code)
if isinstance(result, dict) and result.get('status', False):
success_count += 1
success_results.append({
"query": query,
"result": result.get('data', []),
"status": True
})
else:
questions.append(query)
fail_count += 1
error_msg = result.get('message', '调用 execute_code 返回空结果') if isinstance(result, dict) else f"问题 {query} {selected_knowledge} 调用 execute_generated_code 返回空结果"
error_list.append(error_msg)
fail_results.append({
"query": query,
"result": error_msg,
"status": False
})
else:
questions.append(query)
fail_count += 1
error_msg = result.get('message', '调用 generated_code 返回空结果') if isinstance(result, dict) else f"问题 {query} {selected_knowledge} 调用 execute_generated_code 返回空结果"
error_list.append(error_msg)
fail_results.append({
"query": query,
"result": error_msg,
"status": False
})
#print(result)
if __name__ == "__main__":
main()
total = success_count + fail_count
if total > 0:
success_rate = success_count / total
fail_rate = fail_count / total
else:
success_rate = fail_rate = 0
print(f"问题总数: {total}")
print(f"成功比例: {success_rate * 100:.2f}%")
print(f"失败比例: {fail_rate * 100:.2f}%")
print("错误列表:")
for error in error_list:
print(error)
# 保存成功结果到 jsonl 文件
success_filename = f'./tests/success_{now_str}.jsonl'
with open(success_filename, 'w', encoding='utf-8') as f:
for item in success_results:
f.write(json.dumps(item, ensure_ascii=False) + '\n')
# 保存失败结果到 jsonl 文件
fail_filename = f'./tests/fail_{now_str}.jsonl'
with open(fail_filename, 'w', encoding='utf-8') as f:
for item in fail_results:
f.write(json.dumps(item, ensure_ascii=False) + '\n')
+162
View File
@@ -0,0 +1,162 @@
import os
import sys
import logging
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from datetime import datetime
# 获取当前时间,格式化为字符串
now_str = datetime.now().strftime("%Y%m%d%H%M%S")
log_filename = f"test_code1{now_str}.log"
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[
logging.FileHandler(os.path.join("logs", log_filename), encoding="utf-8"),
logging.StreamHandler()
],
)
logger = logging.getLogger("test_code1")
import logging
def setup_logger(logger_name):
"""
设置指定名称的logger,将其级别设置为WARNING并禁用传播
:param logger_name: logger的名称
"""
logger = logging.getLogger(logger_name)
logger.setLevel(logging.WARNING) # 设置httpcore及其子模块的级别
logger.propagate = False # 可选:禁用传播(防止被根logger处理)
return logger
logger_names = ["httpx", "openai", "langsmith.client", "neo4j", "urllib3", "httpcore"]
for name in logger_names:
setup_logger(name)
import json
import os
from src.dialog_manager import DialogManager
from src.multi_llm_client import MultiAPIKeyChatOpenAI
from src.code_executor import CodeExecutor
from src.neo4j_raw_retriever import Neo4jRawRetriever
from src.prompt_manager import PromptManager
import yaml
from src.config import Config
from src.document_loader import load_file
from src.embedding_client import EmbeddingClient
from src.project import ProjectBuilder, ProjectToolkit
from src.project_implementation import ProjectToolkitNeo4j
success_count = 0
fail_count = 0
questions = []
error_list = []
success_results = []
fail_results = []
def main():
global fail_count, success_count, questions, error_list
config = Config()
business_structure = load_file(config.business_object_structure_path)
bowei_api_docs = load_file(config.bowei_api_docs_path)
llm_client = MultiAPIKeyChatOpenAI(config.openai)
llm_client_coder = MultiAPIKeyChatOpenAI(config.openai_coder)
prompt_manager = PromptManager()
neo4j_conf = config.neo4j_conf
embedding_conf = config.embedding
embedding_client = EmbeddingClient(embedding_conf)
# 创建Neo4j检索器
knowledge_retriever = Neo4jRawRetriever(neo4j_conf)
ProjectBuilder.register(ProjectToolkitNeo4j, knowledge_retriever.driver)
code_executor = CodeExecutor(prompt_manager.prompts, llm_client_coder, config.max_retries)
dialog_manager = DialogManager(
llm_client,
business_structure,
bowei_api_docs,
code_executor,
knowledge_retriever,
prompt_manager,
)
# 加载 zhibiao.jsonl
zhibiao_data = []
with open('./tests/zhibiao.jsonl', 'r', encoding='utf-8') as f:
for line in f:
zhibiao_data.append(json.loads(line))
# 提取指标映射关系并批量执行
for item in zhibiao_data:
query = item['query']
name = item['name']
selected_knowledge = item['result']
logger.info(f"指标名称 {name} 问题: {query} 开始生成代码")
result = dialog_manager.generated_code(query, selected_knowledge)
if isinstance(result, dict) and result.get('status', False):
code = result['data']
success_count += 1
success_results.append({
"name": name,
"query": query,
"code": code,
})
else:
questions.append(query)
fail_count += 1
error_msg = result.get('message', '调用 generated_code 返回空结果') if isinstance(result, dict) else f"问题 {query} {selected_knowledge} 调用 execute_generated_code 返回空结果"
error_list.append(error_msg)
fail_results.append({
"name": name,
"query": query,
"messages": error_msg,
"selected_knowledge": selected_knowledge,
})
#print(result)
if __name__ == "__main__":
main()
total = success_count + fail_count
if total > 0:
success_rate = success_count / total
fail_rate = fail_count / total
else:
success_rate = fail_rate = 0
print(f"问题总数: {total}")
print(f"成功比例: {success_rate * 100:.2f}%")
print(f"失败比例: {fail_rate * 100:.2f}%")
print("错误列表:")
for error in error_list:
print(error)
# 保存成功结果到 jsonl 文件
success_filename = f'./tests/code_{now_str}.jsonl'
with open(success_filename, 'w', encoding='utf-8') as f:
for item in success_results:
f.write(json.dumps(item, ensure_ascii=False) + '\n')
# 保存失败结果到 jsonl 文件
fail_filename = f'./tests/fail_{now_str}.jsonl'
with open(fail_filename, 'w', encoding='utf-8') as f:
for item in fail_results:
f.write(json.dumps(item, ensure_ascii=False) + '\n')
+6 -6
View File
@@ -8,7 +8,7 @@ import logging
logging.basicConfig(
level=logging.DEBUG, # 生产环境可改为 INFO 或 WARNING
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[logging.FileHandler("test.log", encoding="utf-8"), logging.StreamHandler()],
handlers=[logging.FileHandler("logs/test.log", encoding="utf-8"), logging.StreamHandler()],
)
logger = logging.getLogger("test")
@@ -34,7 +34,7 @@ for name in logger_names:
import asyncio
import json
from src.dialog_manager import DialogManager
from src.llm_client import LLMClient
from src.multi_llm_client import MultiAPIKeyChatOpenAI
from src.code_executor import CodeExecutor
from src.neo4j_raw_retriever import Neo4jRawRetriever
from src.prompt_manager import PromptManager
@@ -43,8 +43,8 @@ from src.config import Config
from src.document_loader import load_file
from src.embedding_client import EmbeddingClient
from project import ProjectBuilder, ProjectToolkit
from project_implementation import ProjectToolkitNeo4j
from src.project import ProjectBuilder, ProjectToolkit
from src.project_implementation import ProjectToolkitNeo4j
def main():
@@ -53,9 +53,9 @@ def main():
business_structure = load_file(config.business_object_structure_path)
bowei_api_docs = load_file(config.bowei_api_docs_path)
llm_client = LLMClient(config.openai)
llm_client = MultiAPIKeyChatOpenAI(config.openai)
llm_client_coder = LLMClient(config.openai_coder)
llm_client_coder = MultiAPIKeyChatOpenAI(config.openai_coder)
prompt_manager = PromptManager()
+48 -15
View File
@@ -5,15 +5,15 @@ import logging
import traceback
import sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from project import ProjectBuilder, ProjectToolkit
from project_implementation import ProjectToolkitNeo4j
from src.project import ProjectBuilder, ProjectToolkit
from src.project_implementation import ProjectToolkitNeo4j
from neo4j import GraphDatabase
from src.config import Config
logging.basicConfig(
level=logging.DEBUG, # 生产环境可改为 INFO 或 WARNING
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[logging.FileHandler("test_runcode.log", encoding="utf-8"), logging.StreamHandler()],
handlers=[logging.FileHandler("logs/test_runcode.log", encoding="utf-8"), logging.StreamHandler()],
)
logger = logging.getLogger("test_runcode")
@@ -24,13 +24,27 @@ def main():
neo4j_conf = config.neo4j_conf
code_str = '''
def project_get_calculate_function():
project = ProjectBuilder.build()
status, data, error, helper_info = project.get_division_item_by_path("安装/架空输电线路本体工程")
if status == 'success':
return status, data.get('单位', ''), error, helper_info
return status, None, error, helper_info
'''
def project_get_calculate_function():
project = ProjectBuilder.build()
parent_path = "架空输电线路本体工程/基础工程"
quantity_type = "定额"
code = "YX2-1/YX2-2/YX2-3/YX2-4/YX2-5/YX2-6/YX2-7"
status, data, error, helper_info = project.get_quantities_node_by_parent_and_code(parent_path, quantity_type, code)
if status != "success":
return {
"helper_info": helper_info,
"error": error,
"status": status,
"data": None
}
total_quantity = sum(float(item.get("数量", 0)) for item in data if item.get("数量"))
return {
"helper_info": helper_info,
"error": error,
"status": status,
"data": total_quantity
}
'''
neo4j_driver = GraphDatabase.driver(neo4j_conf.get("uri"), auth=(neo4j_conf.get("username"), neo4j_conf.get("password")))
@@ -53,23 +67,42 @@ def main():
if "project_get_calculate_function" not in namespace:
raise ValueError("代码中未定义project_get_calculate_function函数")
result_tuple = namespace["project_get_calculate_function"]()
result_dict = namespace["project_get_calculate_function"]()
sys.stdout = old_stdout
output = redirected_output.getvalue().strip()
if not isinstance(result_tuple, tuple) or len(result_tuple) != 4:
raise ValueError("函数应返回包含4个元素的元组(status, data, error, helper_info)")
if not isinstance(result_dict, dict) or len(result_dict) != 4:
raise ValueError("函数应返回包含4个元素的字典(status, data, error, helper_info)")
status, data, error, helper_info = result_tuple
logger.debug(f"执行结果: {result_dict}")
logger.info(f"执行结果: status={status}, data={data}, error={error}")
if result_dict["status"] == "success":
return {
"code": 20000,
"message": result_dict["error"] if result_dict["error"] else '',
"status": True,
"data": result_dict["data"] if result_dict["data"] else []
}
else:
return {
"code": 50000,
"message": result_dict["error"] if result_dict["error"] else '',
"status": False,
"data": result_dict["data"] if result_dict["data"] else []
}
except Exception as e:
# 确保恢复stdout
sys.stdout = old_stdout
logger.error(f"执行代码时出错: {e}")
logger.error(traceback.format_exc())
return {
"code": 50000,
"message": str(e),
"status": False,
"data": []
}
if __name__ == "__main__":
+184
View File
@@ -0,0 +1,184 @@
# tests/test_userinteraction.py
import os
import sys
import json
import logging
import os
from datetime import datetime
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
current_file = os.path.splitext(os.path.basename(__file__))[0]
now_str = datetime.now().strftime("%Y%m%d%H%M%S")
log_filename = f"{current_file}_{now_str}.log"
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[
logging.FileHandler(os.path.join("logs", log_filename), encoding="utf-8"),
logging.StreamHandler()
],
)
logger = logging.getLogger(current_file)
def setup_logger(logger_name):
"""
设置指定名称的logger,将其级别设置为WARNING并禁用传播
:param logger_name: logger的名称
"""
logger = logging.getLogger(logger_name)
logger.setLevel(logging.WARNING) # 设置httpcore及其子模块的级别
logger.propagate = False # 可选:禁用传播(防止被根logger处理)
return logger
logger_names = ["httpx", "openai", "langsmith.client", "neo4j", "urllib3", "httpcore"]
for name in logger_names:
setup_logger(name)
from src.config import Config
from src.document_loader import load_file
from src.multi_llm_client import MultiAPIKeyChatOpenAI
from src.user_interaction import UserInteraction
import json
from src.dialog_manager import DialogManager
from src.multi_llm_client import MultiAPIKeyChatOpenAI
from src.code_executor import CodeExecutor
from src.neo4j_raw_retriever import Neo4jRawRetriever
from src.prompt_manager import PromptManager
import yaml
from src.config import Config
from src.document_loader import load_file
from src.embedding_client import EmbeddingClient
from src.project import ProjectBuilder, ProjectToolkit
from src.project_implementation import ProjectToolkitNeo4j
success_count = 0
fail_count = 0
questions = []
error_list = []
success_list = []
def main():
global success_count, fail_count, questions, error_list, success_list
config = Config()
business_structure = load_file(config.business_object_structure_path)
bowei_api_docs = load_file(config.bowei_api_docs_path)
llm_client = MultiAPIKeyChatOpenAI(config.openai)
user_interaction = UserInteraction(llm_client.llm, business_structure)
llm_client_coder = MultiAPIKeyChatOpenAI(config.openai_coder)
prompt_manager = PromptManager()
neo4j_conf = config.neo4j_conf
embedding_conf = config.embedding
embedding_client = EmbeddingClient(embedding_conf)
# 创建Neo4j检索器
knowledge_retriever = Neo4jRawRetriever(neo4j_conf)
ProjectBuilder.register(ProjectToolkitNeo4j, knowledge_retriever.driver)
code_executor = CodeExecutor(prompt_manager.prompts, llm_client_coder, config.max_retries)
dialog_manager = DialogManager(
llm_client,
business_structure,
bowei_api_docs,
code_executor,
knowledge_retriever,
prompt_manager,
)
# 读取 zhibiao.json
zhibiao_path = os.path.join(os.path.dirname(__file__), "zhibiao.json")
with open(zhibiao_path, "r", encoding="utf-8") as f:
zhibiao_data = json.load(f)
isTest = True
isTest = False
if isTest:
zhibiao_data = [
{
"指标名称": "杆塔总基数",
"指标描述": {
"指标映射": "从【架空输电线路本体工程/附件安装工程】项目划分中获取名称属于【'合计'】的费用",
"映射规则": "YX2-1~7"
},
"code": "",
"单位": "",
"单价类型": None,
"序号": "1",
"提取方式": None,
"指标类型": "工程量指标",
"数据来源": "定额数量"
}
]
for idx, item in enumerate(zhibiao_data):
name = item.get("指标名称", "")
datasource = item.get("数据来源", "")
if datasource in ("报表指标", "指标库"):
logger.info(f"跳过索引 {idx},数据来源为 {datasource}")
continue
query = item.get("指标描述", {}).get("指标映射", "")
if not query:
logger.warning(f"索引 {idx} 缺少指标映射,跳过")
continue
try:
# 调用用户交互理解接口(同步调用)
result = user_interaction.understand(query)
if not result:
logger.error(f"问题: {query} 没有找到符合要求的数据")
fail_count += 1
error_list.append(f"指标名称 {name} 问题 {query} 调用 understand 返回空结果")
continue
# 这里示例只打印理解结果,你可以根据业务逻辑替换为后续处理
logger.info(
f"指标名称 {name} 问题: {query} 理解结果: "
f"{[{'name': r.get('name'), 'constraints': r.get('constraints')} for r in result]}"
)
success_list.append({
"name": name,
"query": query,
"result": [{'name': r.get('name'), 'constraints': r.get('constraints')} for r in result]
})
success_count += 1
except Exception as e:
logger.error(f"指标名称 {name} 问题: {query} 处理异常: {e}")
fail_count += 1
error_list.append(f"指标名称 {name} 问题 {query} 异常: {e}")
total = success_count + fail_count
success_rate = (success_count / total) * 100 if total > 0 else 0
fail_rate = (fail_count / total) * 100 if total > 0 else 0
print(f"问题总数: {total}")
print(f"成功比例: {success_rate:.2f}%)")
print(f"失败比例: {fail_rate:.2f}%)")
print("错误列表:")
for error in error_list:
print(error)
# 将成功内容保存为 jsonl 文件
success_jsonl_path = os.path.join(os.path.dirname(__file__), f"zhibiao_{now_str}.jsonl")
with open(success_jsonl_path, "w", encoding="utf-8") as f:
for item in success_list:
f.write(json.dumps(item, ensure_ascii=False) + "\n")
if __name__ == "__main__":
main()
+1575 -429
View File
File diff suppressed because it is too large Load Diff
+46
View File
@@ -0,0 +1,46 @@
{"name": "杆塔总基数", "query": "从【架空输电线路本体工程/基础工程】及其子孙项目划分中查找编码中包含【YX2-1~7】的所有【定额】的【数量】之和", "result": [{"name": "基础工程", "constraints": {"type": "ProjectDivisionItem", "path": "架空输电线路本体工程/基础工程", "name": "基础工程"}}, {"name": "定额", "constraints": {"type": "ProjectQuantity", "类型": "定额", "编码": "YX2-1~7"}}]}
{"name": "角钢塔_塔材量", "query": "从【架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立】及其子孙项目划分中查找名称中包含【角钢】的所有【主材】的【数量】之和", "result": [{"name": "架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立", "constraints": {"type": "ProjectDivisionItem", "path": "架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立"}}, {"name": "主材", "constraints": {"type": "ProjectQuantity", "类型": "主材", "name": "包含角钢"}}]}
{"name": "角钢塔_其中:高强钢塔材量", "query": "从【架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立】及其子孙项目划分中查找名称中包含【角钢、高强】的所有【主材】的【数量】之和", "result": [{"name": "铁塔、钢管杆组立", "constraints": {"type": "ProjectDivisionItem", "path": "架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立", "name": "铁塔、钢管杆组立"}}, {"name": "主材", "constraints": {"type": "ProjectQuantity", "类型": "主材", "name": "包含角钢、高强"}}]}
{"name": "角钢塔_塔材装材费_元", "query": "从【架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立】及其子孙项目划分中查找名称中包含【角钢】的所有【主材】的【单价】之和", "result": [{"name": "架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立", "constraints": {"type": "ProjectDivisionItem", "path": "架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立"}}, {"name": "主材", "constraints": {"type": "ProjectQuantity", "类型": "主材", "name": "包含角钢"}}]}
{"name": "角钢塔_其中:高强钢塔材费用_元", "query": "从【架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立】及其子孙项目划分中查找名称中包含【角钢、高强】的所有【主材】的【单价】之和", "result": [{"name": "架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立", "constraints": {"type": "ProjectDivisionItem", "path": "架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立"}}, {"name": "主材", "constraints": {"type": "ProjectQuantity", "类型": "主材", "name": "包含角钢、高强"}}]}
{"name": "钢管塔_塔材量", "query": "从【架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立】及其子孙项目划分中查找名称中包含【钢管塔】的所有【主材】的【数量】之和", "result": [{"name": "铁塔、钢管杆组立", "constraints": {"type": "ProjectDivisionItem", "path": "架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立", "name": "铁塔、钢管杆组立"}}, {"name": "主材", "constraints": {"type": "ProjectQuantity", "类型": "主材", "name": "包含钢管塔"}}]}
{"name": "钢管塔_钢管价格_元", "query": "从【架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立】及其子孙项目划分中查找名称中包含【钢管塔】的所有【主材】的【单价】之和", "result": [{"name": "铁塔、钢管杆组立", "constraints": {"type": "ProjectDivisionItem", "path": "架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立", "name": "铁塔、钢管杆组立"}}, {"name": "主材", "constraints": {"type": "ProjectQuantity", "类型": "主材", "name": "包含钢管塔"}}]}
{"name": "钢管杆_塔材量", "query": "从【架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立】及其子孙项目划分中查找名称中包含【钢管杆】的所有【主材】的【数量】之和", "result": [{"name": "铁塔、钢管杆组立", "constraints": {"type": "ProjectDivisionItem", "path": "架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立", "name": "铁塔、钢管杆组立"}}, {"name": "主材", "constraints": {"type": "ProjectQuantity", "类型": "主材", "name": "包含钢管杆"}}]}
{"name": "钢管杆_钢管价格_元", "query": "从【架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立】及其子孙项目划分中查找名称中包含【钢管杆】的所有【主材】的【单价】之和", "result": [{"name": "铁塔、钢管杆组立", "constraints": {"type": "ProjectDivisionItem", "path": "架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立", "name": "铁塔、钢管杆组立"}}, {"name": "主材", "constraints": {"type": "ProjectQuantity", "类型": "主材", "name": "包含钢管杆"}}]}
{"name": "导线及线材_分裂数", "query": "从【架空输电线路本体工程/架线工程】及其子孙项目划分中查找编码中包含【['YX5-67', 'YX5-68', 'YX5-69', 'YX5-78', 'YX5-79', 'YX5-59', 'YX5-60', 'YX5-61', 'YX5-62', 'YX5-63', 'YX5-64', 'YX5-65', 'YX5-66', 'YX5-74', 'YX5-75', 'YX5-76', 'YX5-77', 'YX5-52', 'YX5-53', 'YX5-54', 'YX5-55', 'YX5-56', 'YX5-57', 'YX5-58', 'YX5-70', 'YX5-71', 'YX5-72', 'YX5-73', 'YX5-14', 'YX5-15', 'YX5-16', 'YX5-17', 'YX5-43', 'YX5-44', 'YX5-45', 'YX5-46', 'YX5-47', 'YX5-48', 'YX5-49', 'YX5-50', 'YX5-51', 'YX5-10', 'YX5-11', 'YX5-12', 'YX5-13', 'YX5-38', 'YX5-39', 'YX5-40', 'YX5-41', 'YX5-42', 'YX5-8', 'YX5-9']】的所有【定额】的【参数】之和", "result": [{"name": "架空输电线路本体工程/架线工程", "constraints": {"type": "ProjectDivisionItem", "path": "架空输电线路本体工程/架线工程", "name": "架线工程"}}, {"name": "定额", "constraints": {"type": "ProjectQuantity", "类型": "定额", "编码": ["YX5-67", "YX5-68", "YX5-69", "YX5-78", "YX5-79", "YX5-59", "YX5-60", "YX5-61", "YX5-62", "YX5-63", "YX5-64", "YX5-65", "YX5-66", "YX5-74", "YX5-75", "YX5-76", "YX5-77", "YX5-52", "YX5-53", "YX5-54", "YX5-55", "YX5-56", "YX5-57", "YX5-58", "YX5-70", "YX5-71", "YX5-72", "YX5-73", "YX5-14", "YX5-15", "YX5-16", "YX5-17", "YX5-43", "YX5-44", "YX5-45", "YX5-46", "YX5-47", "YX5-48", "YX5-49", "YX5-50", "YX5-51", "YX5-10", "YX5-11", "YX5-12", "YX5-13", "YX5-38", "YX5-39", "YX5-40", "YX5-41", "YX5-42", "YX5-8", "YX5-9"]}}]}
{"name": "导线及线材_其中:节能导线量", "query": "从【架空输电线路本体工程/架线工程/导地线架设@@架空输电线路本体工程/架线工程/导地线跨越架设】及其子孙项目划分中查找名称中包含【高导电率】的所有【主材】的【数量】之和", "result": [{"name": "架空输电线路本体工程/架线工程/导地线架设", "constraints": {"type": "ProjectDivisionItem", "path": "架空输电线路本体工程/架线工程/导地线架设"}}, {"name": "架空输电线路本体工程/架线工程/导地线跨越架设", "constraints": {"type": "ProjectDivisionItem", "path": "架空输电线路本体工程/架线工程/导地线跨越架设"}}, {"name": "主材", "constraints": {"type": "ProjectQuantity", "类型": "主材", "name": "包含高导电率"}}]}
{"name": "导线及线材_导线装材费_元", "query": "从【架空输电线路本体工程/架线工程/导地线架设@@架空输电线路本体工程/架线工程/导地线跨越架设】及其子孙项目划分中查找名称中包含【线】的所有【主材】的【单价】之和", "result": [{"name": "架空输电线路本体工程/架线工程/导地线架设", "constraints": {"type": "ProjectDivisionItem", "path": "架空输电线路本体工程/架线工程/导地线架设"}}, {"name": "架空输电线路本体工程/架线工程/导地线跨越架设", "constraints": {"type": "ProjectDivisionItem", "path": "架空输电线路本体工程/架线工程/导地线跨越架设"}}, {"name": "主材", "constraints": {"type": "ProjectQuantity", "类型": "主材", "name": "线"}}]}
{"name": "导线及线材_其中:节能导线费用_元", "query": "从【架空输电线路本体工程/架线工程/导地线架设@@架空输电线路本体工程/架线工程/导地线跨越架设】及其子孙项目划分中查找名称中包含【高导电率】的所有【主材】的【单价】之和", "result": [{"name": "架空输电线路本体工程/架线工程/导地线架设", "constraints": {"type": "ProjectDivisionItem", "path": "架空输电线路本体工程/架线工程/导地线架设"}}, {"name": "架空输电线路本体工程/架线工程/导地线跨越架设", "constraints": {"type": "ProjectDivisionItem", "path": "架空输电线路本体工程/架线工程/导地线跨越架设"}}, {"name": "主材", "constraints": {"type": "ProjectQuantity", "类型": "主材", "name": "包含高导电率"}}]}
{"name": "导线及线材_导线类型", "query": "从【架空输电线路本体工程/架线工程/导地线架设@@架空输电线路本体工程/架线工程/导地线跨越架设@@架空输电线路本体工程/架线工程/其他架线工程】及其子孙项目划分中查找名称中包含【['钢芯铝绞线', '铝包钢芯铝绞线', '中强度铝合金绞线', '铝合金芯铝绞线', '铝合金芯高导电率铝绞线', '钢芯高导电率铝绞线', '特高强度钢芯铝合金绞线', '扩径导线', '耐热导线', '碳纤维导线']】的所有【主材】的【参数】之和", "result": [{"name": "架空输电线路本体工程/架线工程/导地线架设", "constraints": {"type": "ProjectDivisionItem", "path": "架空输电线路本体工程/架线工程/导地线架设"}}, {"name": "架空输电线路本体工程/架线工程/导地线跨越架设", "constraints": {"type": "ProjectDivisionItem", "path": "架空输电线路本体工程/架线工程/导地线跨越架设"}}, {"name": "架空输电线路本体工程/架线工程/其他架线工程", "constraints": {"type": "ProjectDivisionItem", "path": "架空输电线路本体工程/架线工程/其他架线工程"}}, {"name": "主材", "constraints": {"type": "ProjectQuantity", "类型": "主材", "name": ["钢芯铝绞线", "铝包钢芯铝绞线", "中强度铝合金绞线", "铝合金芯铝绞线", "铝合金芯高导电率铝绞线", "钢芯高导电率铝绞线", "特高强度钢芯铝合金绞线", "扩径导线", "耐热导线", "碳纤维导线"]}}]}
{"name": "基础钢材量", "query": "从【架空输电线路本体工程/基础工程/基础砌筑】及其子孙项目划分中查找名称中包含【圆钢】的所有【主材】的【数量】之和", "result": [{"name": "基础砌筑", "constraints": {"type": "ProjectDivisionItem", "path": "架空输电线路本体工程/基础工程/基础砌筑", "name": "基础砌筑"}}, {"name": "圆钢", "constraints": {"type": "ProjectQuantity", "name": "圆钢", "类型": "主材"}}]}
{"name": "基础钢材价格", "query": "从【架空输电线路本体工程/基础工程/基础砌筑】及其子孙项目划分中查找名称中包含【圆钢】的所有【主材】的【单价】之和", "result": [{"name": "基础砌筑", "constraints": {"type": "ProjectDivisionItem", "path": "架空输电线路本体工程/基础工程/基础砌筑", "name": "基础砌筑"}}, {"name": "圆钢", "constraints": {"type": "ProjectQuantity", "name": "圆钢", "类型": "主材"}}]}
{"name": "本体费用合计_元", "query": "从【工程费用】中获取【架空输电线路本体工程.合计费】的属性", "result": [{"name": "架空输电线路本体工程", "constraints": {"type": "Fee", "name": "架空输电线路本体工程", "code": "BTGC"}}, {"name": "合计费", "constraints": {"type": "Fee", "name": "合计费"}}]}
{"name": "本体工程人工费_本体_元", "query": "从【架空输电线路本体工程】项目划分中获取名称属于【本体工程人工费_本体_元】的费用", "result": [{"name": "架空输电线路本体工程", "constraints": {"type": "ProjectDivisionItem", "name": "架空输电线路本体工程"}}, {"name": "本体工程人工费_本体_元", "constraints": {"type": "CostItem", "name": "本体工程人工费_本体_元"}}]}
{"name": "本体工程人工费_调试_元", "query": "从【架空输电线路本体工程】项目划分中获取名称属于【本体工程人工费_调试_元】的费用", "result": [{"name": "架空输电线路本体工程", "constraints": {"type": "ProjectDivisionItem", "name": "架空输电线路本体工程"}}, {"name": "本体工程人工费_调试_元", "constraints": {"type": "CostItem", "name": "本体工程人工费_调试_元"}}]}
{"name": "本体工程机械费_本体_元", "query": "从【架空输电线路本体工程】项目划分中获取名称属于【本体工程机械费_本体_元】的费用", "result": [{"name": "架空输电线路本体工程", "constraints": {"type": "ProjectDivisionItem", "name": "架空输电线路本体工程"}}, {"name": "本体工程机械费_本体_元", "constraints": {"type": "CostItem", "name": "本体工程机械费_本体_元"}}]}
{"name": "本体工程机械费_调试_元", "query": "从【架空输电线路本体工程】项目划分中获取名称属于【本体工程机械费_调试_元】的费用", "result": [{"name": "架空输电线路本体工程", "constraints": {"type": "ProjectDivisionItem", "name": "架空输电线路本体工程"}}, {"name": "本体工程机械费_调试_元", "constraints": {"type": "CostItem", "name": "本体工程机械费_调试_元"}}]}
{"name": "基础工程费用_元", "query": "从【架空输电线路本体工程/基础工程】项目划分中获取名称属于【基础工程费用_元】的费用", "result": [{"name": "基础工程", "constraints": {"type": "ProjectDivisionItem", "path": "架空输电线路本体工程/基础工程", "name": "基础工程"}}, {"name": "基础工程费用_元", "constraints": {"type": "CostItem", "name": "基础工程费用_元"}}]}
{"name": "杆塔工程费用_元", "query": "从【架空输电线路本体工程/杆塔工程】项目划分中获取名称属于【杆塔工程费用_元】的费用", "result": [{"name": "杆塔工程", "constraints": {"type": "ProjectDivisionItem", "path": "架空输电线路本体工程/杆塔工程", "name": "杆塔工程"}}, {"name": "杆塔工程费用_元", "constraints": {"type": "CostItem", "name": "杆塔工程费用_元"}}]}
{"name": "接地工程费用_元", "query": "从【架空输电线路本体工程/接地工程】项目划分中获取名称属于【接地工程费用_元】的费用", "result": [{"name": "接地工程", "constraints": {"type": "ProjectDivisionItem", "path": "架空输电线路本体工程/接地工程", "name": "接地工程"}}, {"name": "接地工程费用_元", "constraints": {"type": "CostItem", "name": "接地工程费用_元"}}]}
{"name": "架线工程费用_元", "query": "从【架空输电线路本体工程/架线工程】项目划分中获取名称属于【架线工程费用_元】的费用", "result": [{"name": "架线工程", "constraints": {"type": "ProjectDivisionItem", "path": "架空输电线路本体工程/架线工程", "name": "架线工程"}}, {"name": "架线工程费用_元", "constraints": {"type": "CostItem", "name": "架线工程费用_元"}}]}
{"name": "附件工程费用_元", "query": "从【架空输电线路本体工程/附件安装工程】项目划分中获取名称属于【附件工程费用_元】的费用", "result": [{"name": "附件安装工程", "constraints": {"type": "ProjectDivisionItem", "path": "架空输电线路本体工程/附件安装工程", "name": "附件安装工程"}}, {"name": "附件工程费用_元", "constraints": {"type": "CostItem", "name": "附件工程费用_元"}}]}
{"name": "辅助工程费用_元", "query": "从【架空输电线路本体工程/辅助工程】项目划分中获取名称属于【辅助工程费用_元】的费用", "result": [{"name": "辅助工程", "constraints": {"type": "ProjectDivisionItem", "path": "架空输电线路本体工程/辅助工程", "name": "辅助工程"}}, {"name": "辅助工程费用_元", "constraints": {"type": "CostItem", "name": "辅助工程费用_元"}}]}
{"name": "辅助工程费用_调试_元", "query": "从【架空输电线路本体工程/辅助工程】项目划分中获取名称属于【辅助工程费用_调试_元】的费用", "result": [{"name": "辅助工程", "constraints": {"type": "ProjectDivisionItem", "path": "架空输电线路本体工程/辅助工程", "name": "辅助工程"}}, {"name": "辅助工程费用_调试_元", "constraints": {"type": "CostItem", "name": "辅助工程费用_调试_元"}}]}
{"name": "辅助设施工程_元", "query": "从【工程费用】中获取【辅助设施工程.合计费】的属性", "result": [{"name": "辅助设施工程", "constraints": {"type": "ProjectDivisionItem", "name": "辅助设施工程"}}, {"name": "合计费", "constraints": {"type": "CostItem", "name": "合计费"}}]}
{"name": "其他费用合计_元", "query": "从【工程费用】中获取【其他费用.合计费】的属性", "result": [{"name": "工程费用", "constraints": {"type": "FeeScheduleItem", "name": "工程费用表"}}, {"name": "其他费用.合计费", "constraints": {"type": "Fee", "name": "合计费", "code": "其他费用"}}]}
{"name": "建场费合计_元", "query": "从【其他费用】中获取【建设场地征用及清理费】的属性", "result": [{"name": "其他费用", "constraints": {"type": "FeeScheduleItem", "name": "其他费用表"}}, {"name": "建设场地征用及清理费", "constraints": {"type": "Fee", "name": "建设场地征用及清理费"}}]}
{"name": "项目建设管理费合计_元", "query": "从【其他费用】中获取【项目建设管理费】的属性", "result": [{"name": "其他费用", "constraints": {"type": "FeeScheduleItem", "name": "其他费用表"}}, {"name": "项目建设管理费", "constraints": {"type": "Fee", "name": "项目建设管理费"}}]}
{"name": "其中:工程监理费_元", "query": "从【其他费用】中获取【工程监理费】的属性", "result": [{"name": "其他费用表", "constraints": {"type": "FeeScheduleItem", "name": "其他费用表"}}, {"name": "工程监理费", "constraints": {"type": "Fee", "name": "工程监理费"}}]}
{"name": "项目建设技术服务费合计_元", "query": "从【其他费用】中获取【项目建设技术服务费】的属性", "result": [{"name": "其他费用", "constraints": {"type": "FeeScheduleItem", "name": "其他费用表"}}, {"name": "项目建设技术服务费", "constraints": {"type": "Fee", "name": "项目建设技术服务费"}}]}
{"name": "其中:项目前期工作费_元", "query": "从【其他费用】中获取【项目前期工作费】的属性", "result": [{"name": "其他费用表", "constraints": {"type": "FeeScheduleItem", "name": "其他费用表"}}, {"name": "项目前期工作费", "constraints": {"type": "Fee", "name": "项目前期工作费"}}]}
{"name": "其中:勘察费_元", "query": "从【其他费用】中获取【勘察费】的属性", "result": [{"name": "其他费用表", "constraints": {"type": "FeeScheduleItem", "name": "其他费用表"}}, {"name": "勘察费", "constraints": {"type": "Fee", "name": "勘察费"}}]}
{"name": "其中:设计费_元", "query": "从【其他费用】中获取【设计费】的属性", "result": [{"name": "其他费用", "constraints": {"type": "FeeScheduleItem", "name": "其他费用表"}}, {"name": "设计费", "constraints": {"type": "Fee", "name": "设计费"}}]}
{"name": "其中:工程建设检测费_元", "query": "从【其他费用】中获取【工程建设检测费】的属性", "result": [{"name": "其他费用", "constraints": {"type": "FeeScheduleItem", "name": "其他费用表"}}, {"name": "工程建设检测费", "constraints": {"type": "Fee", "name": "工程建设检测费"}}]}
{"name": "生产准备费_元", "query": "从【其他费用】中获取【生产准备费】的属性", "result": [{"name": "其他费用表", "constraints": {"type": "FeeScheduleItem", "name": "其他费用表"}}, {"name": "生产准备费", "constraints": {"type": "Fee", "name": "生产准备费"}}]}
{"name": "其中:安全文明施工费_线路_元", "query": "从【架空输电线路本体工程】项目划分中获取名称属于【其中:安全文明施工费_线路_元】的费用", "result": [{"name": "架空输电线路本体工程", "constraints": {"type": "ProjectDivisionItem", "name": "架空输电线路本体工程"}}, {"name": "其中:安全文明施工费_线路_元", "constraints": {"type": "CostItem", "name": "其中:安全文明施工费_线路_元"}}]}
{"name": "其中:安全文明施工费_调试_元", "query": "从【架空输电线路本体工程】项目划分中获取名称属于【其中:安全文明施工费_调试_元】的费用", "result": [{"name": "架空输电线路本体工程", "constraints": {"type": "ProjectDivisionItem", "name": "架空输电线路本体工程"}}, {"name": "其中:安全文明施工费_调试_元", "constraints": {"type": "CostItem", "name": "其中:安全文明施工费_调试_元"}}]}
{"name": "基本预备费_元", "query": "从【工程费用】中获取【基本预备费.合计费】的属性", "result": [{"name": "基本预备费.合计费", "constraints": {"type": "Fee", "name": "基本预备费.合计费"}}]}
{"name": "静态投资_元", "query": "从【工程费用】中获取【工程静态投资(一~七项合计).合计费】的属性", "result": [{"name": "工程费用表", "constraints": {"type": "FeeScheduleItem", "name": "工程费用表"}}, {"name": "工程静态投资(一~七项合计).合计费", "constraints": {"type": "Fee", "name": "工程静态投资(一~七项合计).合计费"}}]}
{"name": "建设期利息_元", "query": "从【工程费用】中获取【建设期贷款利息.合计费】的属性", "result": [{"name": "建设期贷款利息", "constraints": {"type": "Fee", "name": "建设期贷款利息", "code": "DKLX"}}, {"name": "合计费", "constraints": {"type": "Fee", "name": "合计费"}}]}
{"name": "动态投资_元", "query": "从【工程费用】中获取【工程动态投资(一~八项合计).合计费】的属性", "result": [{"name": "工程费用", "constraints": {"type": "FeeScheduleItem", "name": "工程费用表"}}, {"name": "工程动态投资(一~八项合计).合计费", "constraints": {"type": "Fee", "name": "工程动态投资(一~八项合计).合计费"}}]}
{"name": "增值税抵扣税额_元", "query": "从【工程费用】中获取【其中:可抵扣增值税额.合计费】的属性", "result": [{"name": "工程费用", "constraints": {"type": "FeeScheduleItem", "name": "工程费用表"}}, {"name": "其中:可抵扣增值税额.合计费", "constraints": {"type": "Fee", "name": "其中:可抵扣增值税额.合计费"}}]}
+361 -361
View File
@@ -1,361 +1,361 @@
1.实体类型
EngineeringData
name: STRING
ProjectDivisionSet
name: STRING
ProjectDivisionTree
name: STRING
original_second_level: STRING
original_first_level: STRING
ProjectDivisionItem
type: STRING
取费表id: STRING
name: STRING
序号: STRING
取费表: STRING
GUID: STRING
专业类型: STRING
费率: STRING
资源库名称: STRING
代码: STRING
notCheck: STRING
颜色标记: STRING
编码: STRING
最小资源库编码: STRING
原合价: STRING
备注: STRING
List
合价不含税: STRING
数量: STRING
type: STRING
资源库名称: STRING
工作内容: STRING
取费表类型: STRING
类型: STRING
取费表名称: STRING
单位: STRING
清单名称: STRING
编码: STRING
计算规则: STRING
清单全码: STRING
GUID: STRING
guid: STRING
单价不含税: STRING
单价: STRING
name: STRING
项目特征: STRING
计算式: STRING
合价: STRING
取费表: STRING
ProjectQuantity
数量: STRING
资源库名称: STRING
合价不含税: STRING
特征段: STRING
费用类型: STRING
基准价含税: STRING
结算市场价不含税: STRING
投标单价: STRING
单位: STRING
结算市场价含税: STRING
基准价不含税: STRING
颜色标记: STRING
投标数量: STRING
编码: STRING
类型: STRING
规格型号: STRING
关联父级量: STRING
name: STRING
单价不含税: STRING
损耗率: STRING
截面积: STRING
线重: STRING
id: STRING
供货方: STRING
单重: STRING
集中配送: STRING
市场价含税: STRING
制造长度: STRING
单价含税: STRING
市场价不含税: STRING
增值税率: STRING
合价含税: STRING
运杂费率: STRING
设备类型: STRING
计算式: STRING
人工系数: STRING
定额范围: STRING
定额系数: STRING
基价: STRING
机械费: STRING
人工费: STRING
材料系数: STRING
中标计算式: STRING
机械系数: STRING
材料费: STRING
投标合价: STRING
所属定额库: STRING
批注: STRING
标记: STRING
定额调整系数: STRING
监造物料: STRING
备注: STRING
Quota
特征段: STRING
人工系数: STRING
数量: STRING
定额范围: STRING
定额系数: STRING
合价不含税: STRING
基价: STRING
机械费: STRING
资源库名称: STRING
人工费: STRING
材料系数: STRING
投标单价: STRING
中标计算式: STRING
费用类型: STRING
机械系数: STRING
类型: STRING
材料费: STRING
单位: STRING
颜色标记: STRING
投标合价: STRING
编码: STRING
关联父级量: STRING
投标数量: STRING
计算式: STRING
id: STRING
name: STRING
单价不含税: STRING
所属定额库: STRING
批注: STRING
标记: STRING
定额调整系数: STRING
颜色标记: STRING
备注: STRING
MainMaterial
数量: STRING
资源库名称: STRING
合价不含税: STRING
特征段: STRING
费用类型: STRING
基准价含税: STRING
结算市场价不含税: STRING
投标单价: STRING
单位: STRING
结算市场价含税: STRING
基准价不含税: STRING
颜色标记: STRING
投标数量: STRING
编码: STRING
类型: STRING
规格型号: STRING
关联父级量: STRING
name: STRING
单价不含税: STRING
损耗率: STRING
截面积: STRING
线重: STRING
id: STRING
供货方: STRING
单重: STRING
集中配送: STRING
市场价含税: STRING
制造长度: STRING
单价含税: STRING
市场价不含税: STRING
增值税率: STRING
合价含税: STRING
Equipment
特征段: STRING
单价含税: STRING
单位: STRING
资源库名称: STRING
合价不含税: STRING
类型: STRING
投标数量: STRING
投标单价: STRING
关联父级量: STRING
颜色标记: STRING
运杂费率: STRING
设备类型: STRING
编码: STRING
供货方: STRING
规格型号: STRING
单价不含税: STRING
id: STRING
name: STRING
数量: STRING
计算式: STRING
合价含税: STRING
MaterialOrEquipment
type: STRING
预算价不含税: STRING
单位: STRING
结算市场价不含税: STRING
暂估价: STRING
编码: STRING
结算市场价含税: STRING
全口径市场价不含税: STRING
全口径市场价含税: STRING
是否未计价: STRING
unique_id: STRING
供货方: STRING
结算预算价含税: STRING
结算预算价不含税: STRING
市场价含税: STRING
预算价含税: STRING
id: STRING
name: STRING
数量: STRING
市场价不含税: STRING
拆分: STRING
商品砼: STRING
children: STRING
CostSet
name: STRING
GUID: STRING
CostItem
name: STRING
cost: STRING
unique_id: STRING
id: STRING
FeeTableTemplateSet
name: STRING
typeList: STRING
FeeTableTemplateItem
type: STRING
name: STRING
profession: STRING
outlayID: STRING
FeeCollection
name: STRING
serialNumber: STRING
base: STRING
code: STRING
rate: STRING
remark: STRING
FeeScheduleSet
name: STRING
FeeScheduleItem
name: STRING
ProjectPropertySet
name: STRING
ProjectProperty
特殊地区: STRING
调差选择所在地: STRING
工程所在地: STRING
编制时间: STRING
工程版本: STRING
项目划分: STRING
工程阶段: STRING
工程名称: STRING
专业类型: STRING
地区类型: STRING
组价方式: STRING
人工调差系数: STRING
调差选择地区类型: STRING
机械调差系数: STRING
架线类型: STRING
安装机械调差系数: STRING
清单规范: STRING
市场价唯一: STRING
材料调差系数: STRING
甲供材料计入综合单价: STRING
人工按系数调差: STRING
最高投标限价(万元): STRING
工程总投资: STRING
是否按单位控制工程量精度: STRING
安装人工调差系数: STRING
不同土质定额归属不同清单: STRING
住房公积金缴费费率: STRING
是否是合并工程: STRING
招标人: STRING
相同清单合并: STRING
执行规范: STRING
安装材料调差系数: STRING
拆除调差系数年份: STRING
调差系数年份: STRING
工程税率: STRING
甲供材料计入本体: STRING
软件名称: STRING
电压等级: STRING
预算类型: STRING
本期台数: STRING
建筑人工调差系数: STRING
单台容量: STRING
建筑拆除人工调差系数: STRING
配置选项: STRING
施工企业配合调试费费率: STRING
安装其他设备运杂费率: STRING
安装材机调差系数: STRING
工程性质: STRING
表头设置: STRING
工程静态投资(万元): STRING
安装主要设备运杂费率: STRING
编制依据: STRING
工程动态投资(万元): STRING
基本预备费费率: STRING
阶段类型: STRING
BCL版本: STRING
社会保险费缴费费率: STRING
2. 实体间的关系
(:EngineeringData)-[:HAS_CHILD]->(:ProjectPropertySet)
(:EngineeringData)-[:HAS_CHILD]->(:FeeScheduleSet)
(:EngineeringData)-[:HAS_CHILD]->(:FeeTableTemplateSet)
(:EngineeringData)-[:HAS_CHILD]->(:ProjectDivisionSet)
(:ProjectDivisionSet)-[:HAS_CHILD]->(:ProjectDivisionSet)
(:ProjectDivisionSet)-[:HAS_CHILD]->(:ProjectDivisionItem)
(:ProjectDivisionItem)-[:HAS_CHILD]->(:List)
(:ProjectDivisionItem)-[:HAS_CHILD]->(:ProjectDivisionItem)
(:ProjectDivisionItem)-[:USE]->(:CostSet)
(:ProjectDivisionItem)-[:HAS_CHILD]->(:ProjectQuantity)
(:ProjectDivisionItem)-[:HAS_CHILD]->(:Quota)
(:ProjectDivisionItem)-[:HAS_CHILD]->(:MainMaterial)
(:ProjectDivisionItem)-[:HAS_CHILD]->(:Equipment)
(:ProjectQuantity)-[:HAS_CHILD]->(:MaterialOrEquipment)
(:FeeTableTemplateSet)-[:HAS_CHILD]->(:FeeTableTemplateSet)
(:FeeTableTemplateSet)-[:HAS_CHILD]->(:FeeTableTemplateItem)
(:FeeTableTemplateItem)-[:HAS_CHILD]->(:FeeCollection)
(:FeeCollection)-[:HAS_CHILD]->(:FeeCollection)
(:FeeScheduleSet)-[:HAS_CHILD]->(:FeeScheduleItem)
(:FeeScheduleItem)-[:HAS_CHILD]->(:Fee)
(:Fee)-[:HAS_CHILD]->(:Fee)
(:ProjectPropertySet)-[:HAS_CHILD]->(:ProjectProperty)
(:List)-[:HAS_CHILD]->(:ProjectQuantity)
(:List)-[:HAS_CHILD]->(:Equipment)
(:List)-[:HAS_CHILD]->(:MainMaterial)
(:List)-[:HAS_CHILD]->(:Quota)
(:List)-[:USE]->(:CostSet)
(:Quota)-[:HAS_CHILD]->(:MaterialOrEquipment)
(:CostSet)-[:HAS_CHILD]->(:CostItem)
(:ProjectDivisionTree)-[:HAS_CHILD]->(:ProjectDivisionItem)
(:ProjectQuantity)-[:HAS_CHILD]->(:ProjectQuantity)
(:ProjectQuantity)-[:HAS_CHILD]->(:MainMaterial)
(:MainMaterial)-[:HAS_CHILD]->(:ProjectQuantity)
(:MainMaterial)-[:HAS_CHILD]->(:MainMaterial)
(:ProjectQuantity)-[:USE]->(:CostSet)
(:ProjectDivisionTree)-[:USE]->(:CostSet)
(:ProjectDivisionSet)-[:HAS_CHILD]->(:ProjectDivisionTree)
1.实体类型
EngineeringData
name: STRING
ProjectDivisionSet
name: STRING
ProjectDivisionTree
name: STRING
original_second_level: STRING
original_first_level: STRING
ProjectDivisionItem
type: STRING
取费表id: STRING
name: STRING
序号: STRING
取费表: STRING
GUID: STRING
专业类型: STRING
费率: STRING
资源库名称: STRING
代码: STRING
notCheck: STRING
颜色标记: STRING
编码: STRING
最小资源库编码: STRING
原合价: STRING
备注: STRING
List
合价不含税: STRING
数量: STRING
type: STRING
资源库名称: STRING
工作内容: STRING
取费表类型: STRING
类型: STRING
取费表名称: STRING
单位: STRING
清单名称: STRING
编码: STRING
计算规则: STRING
清单全码: STRING
GUID: STRING
guid: STRING
单价不含税: STRING
单价: STRING
name: STRING
项目特征: STRING
计算式: STRING
合价: STRING
取费表: STRING
ProjectQuantity
数量: STRING
资源库名称: STRING
合价不含税: STRING
特征段: STRING
费用类型: STRING
基准价含税: STRING
结算市场价不含税: STRING
投标单价: STRING
单位: STRING
结算市场价含税: STRING
基准价不含税: STRING
颜色标记: STRING
投标数量: STRING
编码: STRING
类型: STRING
规格型号: STRING
关联父级量: STRING
name: STRING
单价不含税: STRING
损耗率: STRING
截面积: STRING
线重: STRING
id: STRING
供货方: STRING
单重: STRING
集中配送: STRING
市场价含税: STRING
制造长度: STRING
单价含税: STRING
市场价不含税: STRING
增值税率: STRING
合价含税: STRING
运杂费率: STRING
设备类型: STRING
计算式: STRING
人工系数: STRING
定额范围: STRING
定额系数: STRING
基价: STRING
机械费: STRING
人工费: STRING
材料系数: STRING
中标计算式: STRING
机械系数: STRING
材料费: STRING
投标合价: STRING
所属定额库: STRING
批注: STRING
标记: STRING
定额调整系数: STRING
监造物料: STRING
备注: STRING
Quota
特征段: STRING
人工系数: STRING
数量: STRING
定额范围: STRING
定额系数: STRING
合价不含税: STRING
基价: STRING
机械费: STRING
资源库名称: STRING
人工费: STRING
材料系数: STRING
投标单价: STRING
中标计算式: STRING
费用类型: STRING
机械系数: STRING
类型: STRING
材料费: STRING
单位: STRING
颜色标记: STRING
投标合价: STRING
编码: STRING
关联父级量: STRING
投标数量: STRING
计算式: STRING
id: STRING
name: STRING
单价不含税: STRING
所属定额库: STRING
批注: STRING
标记: STRING
定额调整系数: STRING
颜色标记: STRING
备注: STRING
MainMaterial
数量: STRING
资源库名称: STRING
合价不含税: STRING
特征段: STRING
费用类型: STRING
基准价含税: STRING
结算市场价不含税: STRING
投标单价: STRING
单位: STRING
结算市场价含税: STRING
基准价不含税: STRING
颜色标记: STRING
投标数量: STRING
编码: STRING
类型: STRING
规格型号: STRING
关联父级量: STRING
name: STRING
单价不含税: STRING
损耗率: STRING
截面积: STRING
线重: STRING
id: STRING
供货方: STRING
单重: STRING
集中配送: STRING
市场价含税: STRING
制造长度: STRING
单价含税: STRING
市场价不含税: STRING
增值税率: STRING
合价含税: STRING
Equipment
特征段: STRING
单价含税: STRING
单位: STRING
资源库名称: STRING
合价不含税: STRING
类型: STRING
投标数量: STRING
投标单价: STRING
关联父级量: STRING
颜色标记: STRING
运杂费率: STRING
设备类型: STRING
编码: STRING
供货方: STRING
规格型号: STRING
单价不含税: STRING
id: STRING
name: STRING
数量: STRING
计算式: STRING
合价含税: STRING
MaterialOrEquipment
type: STRING
预算价不含税: STRING
单位: STRING
结算市场价不含税: STRING
暂估价: STRING
编码: STRING
结算市场价含税: STRING
全口径市场价不含税: STRING
全口径市场价含税: STRING
是否未计价: STRING
unique_id: STRING
供货方: STRING
结算预算价含税: STRING
结算预算价不含税: STRING
市场价含税: STRING
预算价含税: STRING
id: STRING
name: STRING
数量: STRING
市场价不含税: STRING
拆分: STRING
商品砼: STRING
children: STRING
CostSet
name: STRING
GUID: STRING
CostItem
name: STRING
cost: STRING
unique_id: STRING
id: STRING
FeeTableTemplateSet
name: STRING
typeList: STRING
FeeTableTemplateItem
type: STRING
name: STRING
profession: STRING
outlayID: STRING
FeeCollection
name: STRING
serialNumber: STRING
base: STRING
code: STRING
rate: STRING
remark: STRING
FeeScheduleSet
name: STRING
FeeScheduleItem
name: STRING
ProjectPropertySet
name: STRING
ProjectProperty
特殊地区: STRING
调差选择所在地: STRING
工程所在地: STRING
编制时间: STRING
工程版本: STRING
项目划分: STRING
工程阶段: STRING
工程名称: STRING
专业类型: STRING
地区类型: STRING
组价方式: STRING
人工调差系数: STRING
调差选择地区类型: STRING
机械调差系数: STRING
架线类型: STRING
安装机械调差系数: STRING
清单规范: STRING
市场价唯一: STRING
材料调差系数: STRING
甲供材料计入综合单价: STRING
人工按系数调差: STRING
最高投标限价(万元): STRING
工程总投资: STRING
是否按单位控制工程量精度: STRING
安装人工调差系数: STRING
不同土质定额归属不同清单: STRING
住房公积金缴费费率: STRING
是否是合并工程: STRING
招标人: STRING
相同清单合并: STRING
执行规范: STRING
安装材料调差系数: STRING
拆除调差系数年份: STRING
调差系数年份: STRING
工程税率: STRING
甲供材料计入本体: STRING
软件名称: STRING
电压等级: STRING
预算类型: STRING
本期台数: STRING
建筑人工调差系数: STRING
单台容量: STRING
建筑拆除人工调差系数: STRING
配置选项: STRING
施工企业配合调试费费率: STRING
安装其他设备运杂费率: STRING
安装材机调差系数: STRING
工程性质: STRING
表头设置: STRING
工程静态投资(万元): STRING
安装主要设备运杂费率: STRING
编制依据: STRING
工程动态投资(万元): STRING
基本预备费费率: STRING
阶段类型: STRING
BCL版本: STRING
社会保险费缴费费率: STRING
2. 实体间的关系
(:EngineeringData)-[:HAS_CHILD]->(:ProjectPropertySet)
(:EngineeringData)-[:HAS_CHILD]->(:FeeScheduleSet)
(:EngineeringData)-[:HAS_CHILD]->(:FeeTableTemplateSet)
(:EngineeringData)-[:HAS_CHILD]->(:ProjectDivisionSet)
(:ProjectDivisionSet)-[:HAS_CHILD]->(:ProjectDivisionSet)
(:ProjectDivisionSet)-[:HAS_CHILD]->(:ProjectDivisionItem)
(:ProjectDivisionItem)-[:HAS_CHILD]->(:List)
(:ProjectDivisionItem)-[:HAS_CHILD]->(:ProjectDivisionItem)
(:ProjectDivisionItem)-[:USE]->(:CostSet)
(:ProjectDivisionItem)-[:HAS_CHILD]->(:ProjectQuantity)
(:ProjectDivisionItem)-[:HAS_CHILD]->(:Quota)
(:ProjectDivisionItem)-[:HAS_CHILD]->(:MainMaterial)
(:ProjectDivisionItem)-[:HAS_CHILD]->(:Equipment)
(:ProjectQuantity)-[:HAS_CHILD]->(:MaterialOrEquipment)
(:FeeTableTemplateSet)-[:HAS_CHILD]->(:FeeTableTemplateSet)
(:FeeTableTemplateSet)-[:HAS_CHILD]->(:FeeTableTemplateItem)
(:FeeTableTemplateItem)-[:HAS_CHILD]->(:FeeCollection)
(:FeeCollection)-[:HAS_CHILD]->(:FeeCollection)
(:FeeScheduleSet)-[:HAS_CHILD]->(:FeeScheduleItem)
(:FeeScheduleItem)-[:HAS_CHILD]->(:Fee)
(:Fee)-[:HAS_CHILD]->(:Fee)
(:ProjectPropertySet)-[:HAS_CHILD]->(:ProjectProperty)
(:List)-[:HAS_CHILD]->(:ProjectQuantity)
(:List)-[:HAS_CHILD]->(:Equipment)
(:List)-[:HAS_CHILD]->(:MainMaterial)
(:List)-[:HAS_CHILD]->(:Quota)
(:List)-[:USE]->(:CostSet)
(:Quota)-[:HAS_CHILD]->(:MaterialOrEquipment)
(:CostSet)-[:HAS_CHILD]->(:CostItem)
(:ProjectDivisionTree)-[:HAS_CHILD]->(:ProjectDivisionItem)
(:ProjectQuantity)-[:HAS_CHILD]->(:ProjectQuantity)
(:ProjectQuantity)-[:HAS_CHILD]->(:MainMaterial)
(:MainMaterial)-[:HAS_CHILD]->(:ProjectQuantity)
(:MainMaterial)-[:HAS_CHILD]->(:MainMaterial)
(:ProjectQuantity)-[:USE]->(:CostSet)
(:ProjectDivisionTree)-[:USE]->(:CostSet)
(:ProjectDivisionSet)-[:HAS_CHILD]->(:ProjectDivisionTree)
File diff suppressed because it is too large Load Diff
+167
View File
@@ -0,0 +1,167 @@
import streamlit as st
import os
import sys
import json
import logging
import os
from datetime import datetime
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# 获取当前时间,格式化为字符串
now_str = datetime.now().strftime("%Y%m%d%H%M%S")
current_file = os.path.splitext(os.path.basename(__file__))[0]
log_filename = f"{current_file}_{now_str}.log"
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[
logging.FileHandler(os.path.join("logs", log_filename), encoding="utf-8"),
logging.StreamHandler()
],
)
logger = logging.getLogger(current_file)
def setup_logger(logger_name):
"""
设置指定名称的logger,将其级别设置为WARNING并禁用传播
:param logger_name: logger的名称
"""
logger = logging.getLogger(logger_name)
logger.setLevel(logging.WARNING) # 设置httpcore及其子模块的级别
logger.propagate = False # 可选:禁用传播(防止被根logger处理)
return logger
logger_names = ["httpx", "openai", "langsmith.client", "neo4j", "urllib3", "httpcore"]
for name in logger_names:
setup_logger(name)
from src.config import Config
from src.document_loader import load_file
from src.embedding_client import EmbeddingClient
from src.multi_llm_client import MultiAPIKeyChatOpenAI
from src.code_executor import CodeExecutor
from src.neo4j_raw_retriever import Neo4jRawRetriever
from src.prompt_manager import PromptManager
from src.project import ProjectBuilder, ProjectToolkit
from src.project_implementation import ProjectToolkitNeo4j
from src.code_executor import CodeExecutor
config = Config()
business_structure = load_file(config.business_object_structure_path)
bowei_api_docs = load_file(config.bowei_api_docs_path)
#llm_client = MultiAPIKeyChatOpenAI(config.openai)
llm_client_coder = MultiAPIKeyChatOpenAI(config.openai_coder)
prompt_manager = PromptManager()
neo4j_conf = config.neo4j_conf
embedding_conf = config.embedding
embedding_client = EmbeddingClient(embedding_conf)
# 创建Neo4j检索器
knowledge_retriever = Neo4jRawRetriever(neo4j_conf)
ProjectBuilder.register(ProjectToolkitNeo4j, knowledge_retriever.driver)
code_executor = CodeExecutor(prompt_manager.prompts, llm_client_coder, config.max_retries)
def load_jsonl(file_path):
"""加载JSONL文件并返回JSON记录列表"""
records = []
try:
with open(file_path, 'r', encoding='utf-8') as file:
for line in file:
if line.strip():
records.append(json.loads(line))
return records
except Exception as e:
st.error(f"加载文件失败: {str(e)}")
return []
def run_code(data):
global code_executor
"""运行JSON记录中的代码并返回结果"""
if not data or 'code' not in data:
return {
"code": 40000,
"message": "没有可执行的代码",
"status": False,
"data": None
}
result = code_executor.execute_code(data['code'])
def main():
st.set_page_config(layout="wide", page_title="JSONL查看器")
st.title("JSONL文件查看器")
# 设置默认文件路径
default_file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../tests", "code.jsonl")
# 文件路径输入
file_path = st.text_input("JSONL文件路径", value=default_file_path)
if not os.path.exists(file_path):
st.warning(f"文件不存在: {file_path}")
return
# 加载JSONL文件
records = load_jsonl(file_path)
if not records:
st.warning("没有找到有效的记录")
return
# 创建两列布局
col1, col2 = st.columns([1, 3])
# 左侧列表
with col1:
st.subheader("记录列表")
selected_index = None
for i, record in enumerate(records):
if st.button(record.get('name', f"记录 {i+1}"), key=f"btn_{i}"):
selected_index = i
# 右侧详细信息
with col2:
if 'selected_index' not in st.session_state:
st.session_state.selected_index = 0
if selected_index is not None:
st.session_state.selected_index = selected_index
if st.session_state.selected_index < len(records):
selected_record = records[st.session_state.selected_index]
st.subheader(f"查询问题: {selected_record.get('name', '无名称')}")
st.info(selected_record.get('query', '无查询信息'))
st.subheader("代码")
st.code(selected_record.get('code', '无代码'), language='python')
# 运行代码按钮
if st.button("运行代码"):
with st.spinner('正在执行代码...'):
result = run_code(selected_record)
st.subheader("运行结果")
if result.get('status'):
st.success("执行成功")
st.json(result.get('data'))
else:
st.error(f"执行失败: {result.get('message', '未知错误')}")
if __name__ == "__main__":
main()
+117 -117
View File
@@ -1,117 +1,117 @@
import json
from typing import Dict, List, Any
import copy
class ExpenseProcessor:
def __init__(self):
pass
@staticmethod
def calculate_parent_costs(node: Dict[str, Any]) -> List[Dict[str, Any]]:
if "children" not in node:
if "id" in node and "cost" in node:
return [{"id": node["id"], "cost": node["cost"]}]
elif "cost" in node:
return [{"cost": node["cost"]}]
return []
result_nodes = []
processed_ids = set()
for child in node["children"]:
child_costs = ExpenseProcessor.calculate_parent_costs(child)
for cost_item in child_costs:
if "id" in cost_item:
found = False
for existing in result_nodes:
if "id" in existing and existing["id"] == cost_item["id"]:
existing["cost"] = str(float(existing["cost"]) + float(cost_item["cost"]))
found = True
break
if not found:
result_nodes.append(copy.deepcopy(cost_item))
processed_ids.add(cost_item["id"])
else:
found = False
for existing in result_nodes:
if "id" not in existing:
existing["cost"] = str(float(existing["cost"]) + float(cost_item["cost"]))
found = True
break
if not found:
result_nodes.append(copy.deepcopy(cost_item))
return result_nodes
@staticmethod
def process_node(node: Dict[str, Any]) -> Dict[str, Any]:
result = copy.deepcopy(node)
if "children" not in node or not node["children"]:
return result
cost_items = ExpenseProcessor.calculate_parent_costs(node)
if cost_items:
result["sum"] = cost_items
result["children"] = [ExpenseProcessor.process_node(child) for child in node["children"]]
return result
@staticmethod
def process_expense_preview(expense_preview: Dict[str, Any]) -> Dict[str, Any]:
result = copy.deepcopy(expense_preview)
for category_key, category_value in expense_preview.items():
for subcategory_key, subcategory_value in category_value.items():
if isinstance(subcategory_value, list):
result[category_key][subcategory_key] = [
ExpenseProcessor.process_node(item) for item in subcategory_value
]
return result
@classmethod
def load_and_process_from_file(cls, input_path: str, output_path: str = None) -> Dict[str, Any]:
"""
从文件加载 JSON 并处理
:param input_path: 输入文件路径
:param output_path: 输出文件路径可选
:return: 处理后的完整数据
"""
with open(input_path, "r", encoding="utf-8") as f:
data = json.load(f)
if "projectData" in data and "expensePreview" in data["projectData"]:
processed_data = copy.deepcopy(data)
processed_data["projectData"]["expensePreview"] = cls.process_expense_preview(
data["projectData"]["expensePreview"]
)
if output_path:
with open(output_path, "w", encoding="utf-8") as f:
json.dump(processed_data, f, ensure_ascii=False, indent=4)
print(f"处理完成,结果已保存到 {output_path}")
return processed_data
else:
raise ValueError("未找到 projectData.expensePreview 路径")
@classmethod
def process_raw_data(cls, raw_data: Dict[str, Any]) -> Dict[str, Any]:
"""
直接处理原始数据不涉及文件读写
:param raw_data: 原始数据格式应包含 projectData.expensePreview
:return: 处理后的数据
"""
if "projectData" in raw_data and "expensePreview" in raw_data["projectData"]:
processed_data = copy.deepcopy(raw_data)
processed_data["projectData"]["expensePreview"] = cls.process_expense_preview(
raw_data["projectData"]["expensePreview"]
)
return processed_data
else:
raise ValueError("未找到 projectData.expensePreview 路径")
if __name__ == "__main__":
input_file = "架空_clean.json" # 输入 JSON 文件路径
output_file = "output.json" # 输出 JSON 文件路径
# 使用类方法加载并处理 JSON 文件
ExpenseProcessor.load_and_process_from_file(input_file, output_file)
import json
from typing import Dict, List, Any
import copy
class ExpenseProcessor:
def __init__(self):
pass
@staticmethod
def calculate_parent_costs(node: Dict[str, Any]) -> List[Dict[str, Any]]:
if "children" not in node:
if "id" in node and "cost" in node:
return [{"id": node["id"], "cost": node["cost"]}]
elif "cost" in node:
return [{"cost": node["cost"]}]
return []
result_nodes = []
processed_ids = set()
for child in node["children"]:
child_costs = ExpenseProcessor.calculate_parent_costs(child)
for cost_item in child_costs:
if "id" in cost_item:
found = False
for existing in result_nodes:
if "id" in existing and existing["id"] == cost_item["id"]:
existing["cost"] = str(float(existing["cost"]) + float(cost_item["cost"]))
found = True
break
if not found:
result_nodes.append(copy.deepcopy(cost_item))
processed_ids.add(cost_item["id"])
else:
found = False
for existing in result_nodes:
if "id" not in existing:
existing["cost"] = str(float(existing["cost"]) + float(cost_item["cost"]))
found = True
break
if not found:
result_nodes.append(copy.deepcopy(cost_item))
return result_nodes
@staticmethod
def process_node(node: Dict[str, Any]) -> Dict[str, Any]:
result = copy.deepcopy(node)
if "children" not in node or not node["children"]:
return result
cost_items = ExpenseProcessor.calculate_parent_costs(node)
if cost_items:
result["sum"] = cost_items
result["children"] = [ExpenseProcessor.process_node(child) for child in node["children"]]
return result
@staticmethod
def process_expense_preview(expense_preview: Dict[str, Any]) -> Dict[str, Any]:
result = copy.deepcopy(expense_preview)
for category_key, category_value in expense_preview.items():
for subcategory_key, subcategory_value in category_value.items():
if isinstance(subcategory_value, list):
result[category_key][subcategory_key] = [
ExpenseProcessor.process_node(item) for item in subcategory_value
]
return result
@classmethod
def load_and_process_from_file(cls, input_path: str, output_path: str = None) -> Dict[str, Any]:
"""
从文件加载 JSON 并处理
:param input_path: 输入文件路径
:param output_path: 输出文件路径可选
:return: 处理后的完整数据
"""
with open(input_path, "r", encoding="utf-8") as f:
data = json.load(f)
if "projectData" in data and "expensePreview" in data["projectData"]:
processed_data = copy.deepcopy(data)
processed_data["projectData"]["expensePreview"] = cls.process_expense_preview(
data["projectData"]["expensePreview"]
)
if output_path:
with open(output_path, "w", encoding="utf-8") as f:
json.dump(processed_data, f, ensure_ascii=False, indent=4)
print(f"处理完成,结果已保存到 {output_path}")
return processed_data
else:
raise ValueError("未找到 projectData.expensePreview 路径")
@classmethod
def process_raw_data(cls, raw_data: Dict[str, Any]) -> Dict[str, Any]:
"""
直接处理原始数据不涉及文件读写
:param raw_data: 原始数据格式应包含 projectData.expensePreview
:return: 处理后的数据
"""
if "projectData" in raw_data and "expensePreview" in raw_data["projectData"]:
processed_data = copy.deepcopy(raw_data)
processed_data["projectData"]["expensePreview"] = cls.process_expense_preview(
raw_data["projectData"]["expensePreview"]
)
return processed_data
else:
raise ValueError("未找到 projectData.expensePreview 路径")
if __name__ == "__main__":
input_file = "dataset/json/主网预算/架空_clean.json" # 输入 JSON 文件路径
output_file = "dataset/json/主网预算/output.json" # 输出 JSON 文件路径
# 使用类方法加载并处理 JSON 文件
ExpenseProcessor.load_and_process_from_file(input_file, output_file)
+206
View File
@@ -0,0 +1,206 @@
import chardet
import xml.etree.ElementTree as ET
import json
import re
def clean_bracketed_strings(input_str: str) -> str:
# 替换【'xxx'】为【xxx】
result = re.sub(r"'([^']+)'", r"\1】", input_str)
# 替换【['xxx']】为【xxx】
result = re.sub(r"\['([^']+)'\]】", r"\1】", result)
# 替换【['xxx','yyy']】为【xxx,yyy】(多个项的情况)
result = re.sub(r"\[((?:'[^']+',?)+)\]】", lambda m: "" + m.group(1).replace("'", "") + "", result)
return result
def read_xml_as_string(file_path):
# 先读取部分字节探测编码
with open(file_path, 'rb') as f:
raw_data = f.read()
result = chardet.detect(raw_data)
encoding = result['encoding']
# 使用探测到的编码重新读取为字符串
return raw_data.decode(encoding)
def parse_keyword(keyword, indicator_name):
# 特殊处理:电压等级
# if indicator_name == "电压等级":
# return {"映射规则": "1", "指标映射": [keyword]}
# 处理范围表达式(包含"||"分隔符)
if "||" in keyword:
parts = keyword.split("||")
table_rows = []
all_codes = []
for part in parts:
if "@@" not in part:
continue
codes_str, value = part.split("@@", 1)
code_ranges = codes_str.split("")
for code_range in code_ranges:
# 处理连续编号(如YX5-67~69
if "~" in code_range:
prefix, range_part = code_range.rsplit("-", 1)
start_str, end_str = range_part.split("~")
try:
start = int(start_str)
end = int(end_str)
for num in range(start, end + 1):
all_codes.append(f"{prefix}-{num}")
except ValueError:
all_codes.append(code_range)
else:
all_codes.append(code_range)
table_rows.append(f"| {codes_str} | {value} |")
rule_table = "| 资源识别规则 | 指标值 |\n|-------|-------|\n" + "\n".join(table_rows)
return {"映射规则": rule_table, "指标映射": all_codes}
# 处理数学公式(包含"/"和括号)
if "/" in keyword and "(" in keyword and ")" in keyword:
# 提取分子(括号前部分)
molecule = keyword.split("/")[0].strip()
# 提取分母(括号内部分)
denominator_start = keyword.find("(") + 1
denominator_end = keyword.find(")")
denominator_expr = keyword[denominator_start:denominator_end]
# 分割分母中的加法项
denominator_items = [item.strip() for item in denominator_expr.split("+")]
return {"映射规则": keyword, "指标映射": [molecule] + denominator_items}
# 处理加法表达式
if "+" in keyword:
items = [item.strip() for item in keyword.split("+")]
return {"映射规则": keyword, "指标映射": items}
# 默认处理(普通关键字)
return {"映射规则": keyword, "指标映射": [keyword]}
def xml_to_json(xml_content, output_path):
root = ET.fromstring(xml_content)
records = root.findall('.//records/record')
result = []
# 定义需要特殊处理的数据来源类型
scope_based_sources = ["主材单价", "主材参数", "主材数量", "定额参数", "定额数量", "工程费用"]
direct_sources = ["报表指标", "指标库"]
project_division = ["项目划分费用"]
for record in records:
unit = record.get("单位")
unit_type = record.get("单价类型")
order = record.get("序号")
extraction_method = record.get("提取方式")
indicator_type = record.get("指标类型")
index_extraction_scope = record.get("指标提取范围")
data_sources = record.get("数据来源")
indicator_name = record.get("指标名称")
keyword = record.get("关键字")
parsed = parse_keyword(keyword, indicator_name)
base_item = {
"指标名称": indicator_name,
"code": "",
"单位": unit,
"单价类型": unit_type,
"序号": order,
"提取方式": extraction_method,
"指标类型": indicator_type,
"数据来源": data_sources
}
if data_sources in direct_sources:
base_item["指标描述"] = {
"指标映射": parsed["指标映射"],
"映射规则": parsed["映射规则"]
}
result.append(base_item)
elif data_sources in project_division:
mapping_desc = f"从【{index_extraction_scope}】项目划分中获取名称属于【{indicator_name}】的费用"
base_item["指标描述"] = {
"指标映射": mapping_desc,
"映射规则": parsed["映射规则"]
}
result.append(base_item)
elif data_sources in scope_based_sources:
temp0 = parsed["指标映射"]
temp1 = parsed["映射规则"]
if index_extraction_scope:
# 取数据来源的最后两个字(如"单价"、"参数"等)
temp2 = data_sources[-2:]
# 取数据来源的开头两个字(如"定额"、"清单"等)
temp3 = data_sources[0:2]
if temp3 in ["清单", "定额", "人材机"]:
mapping_desc = f"从【{index_extraction_scope}】及其子孙项目划分中查找编码中包含【{temp0}】的所有【{temp3}】的【{temp2}】之和"
elif temp3 in ["主材", "设备"]:
mapping_desc = f"从【{index_extraction_scope}】及其子孙项目划分中查找名称中包含【{temp0}】的所有【{temp3}】的【{temp2}】之和"
else:
mapping_desc = f"从【{data_sources}】中获取{temp0}的属性"
base_item["指标描述"] = {
"指标映射": mapping_desc,
"映射规则": temp1
}
result.append(base_item)
else:
# 处理未定义的数据来源类型
base_item["指标描述"] = {
"指标映射": parsed["指标映射"],
"映射规则": parsed["映射规则"]
}
result.append(base_item)
for item in result:
desc = item.get("指标描述", {})
mapping_rule = desc.get("映射规则")
indicator_map = desc.get("指标映射")
if isinstance(mapping_rule, str) and isinstance(indicator_map, str):
match = re.search(r"@([^\.]+)\.", mapping_rule)
if match:
source = match.group(1)
# 替换【工程费用】为提取的 source(如【其他费用】)
new_mapping = re.sub(r"【.*?】", f"{source}", indicator_map)
new_mapping = re.sub(r"@[^.]+\.", "", new_mapping)
item["指标描述"]["指标映射"] = new_mapping.replace('[', '').replace(']', '')
if isinstance(indicator_map, list):
first_elem = indicator_map[0]
if first_elem.startswith("(") and first_elem.endswith(")"):
del indicator_map[0]
# 保存为 JSON 文件
with open(output_path, 'w', encoding='utf-8') as f:
json.dump(result, f, ensure_ascii=False, indent=2)
for item in result:
desc = item.get("指标描述", {})
mapping_rule = desc.get("映射规则")
indicator_map = desc.get("指标映射")
if isinstance(indicator_map, str):
new_mapping = clean_bracketed_strings(indicator_map)
item["指标描述"]["指标映射"] = new_mapping
# 保存为 JSON 文件
with open(output_path, 'w', encoding='utf-8') as f:
json.dump(result, f, ensure_ascii=False, indent=2)
return "结果已保存"
xml_content = read_xml_as_string('dataset/主网架空线路造价分析指标.xml')
json_output = xml_to_json(xml_content, output_path= "./tests/zhibiao.json")
print("转换完毕!")