feat: 删除旧的Chroma数据库文件和相关文档
- 删除多个Chroma数据库文件,包括kb_2、kb_3、kb_13、kb_14、kb_15、kb_16、kb_18等的SQLite和二进制文件 - 移除测试知识库和上传的文档,确保数据一致性 - 更新文档处理器以增强初始化和错误处理逻辑,确保向量数据库路径的正确设置
This commit is contained in:
parent
6fa9f1f18e
commit
67087e0664
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.
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.
|
|
@ -0,0 +1,107 @@
|
||||||
|
# 低空飞机:探索蓝天新维度
|
||||||
|
|
||||||
|
## 摘要
|
||||||
|
本文深入探讨低空飞机这一独特航空领域,从其定义、发展历程、分类、关键技术、运行环境、市场应用、安全管理到未来展望等多个方面展开全面剖析。通过详细阐述,旨在为读者呈现低空飞机在航空领域的重要地位、运作机制以及广阔的发展前景,揭示其对经济发展、社会生活和航空技术进步的深远影响。
|
||||||
|
|
||||||
|
## 一、引言
|
||||||
|
随着航空技术的不断进步和人们对空域利用需求的增加,低空飞机逐渐成为航空领域的一个焦点。低空飞机以其灵活便捷、适应多种场景的特点,在旅游观光、农林作业、航空摄影、应急救援等诸多领域发挥着越来越重要的作用。了解低空飞机对于推动航空产业多元化发展、拓展空域资源利用以及满足社会多样化需求具有重要意义。
|
||||||
|
|
||||||
|
## 二、低空飞机的定义与概述
|
||||||
|
### 2.1 低空的界定
|
||||||
|
在航空领域,低空通常指距地面或水面1000米以下的空域。这部分空域与人们的日常生活和地面活动联系紧密,其独特的地理和气象条件既为低空飞行带来挑战,也创造了独特的应用机会。
|
||||||
|
|
||||||
|
### 2.2 低空飞机的定义
|
||||||
|
低空飞机是指主要在低空空域(通常1000米以下)进行飞行活动的一类飞机。它们通常具有较低的飞行速度、较小的起飞着陆场地要求以及灵活的操控性能,以适应低空复杂多变的环境和多样化的任务需求。
|
||||||
|
|
||||||
|
### 2.3 与其他飞机的区别
|
||||||
|
与大型民航客机相比,低空飞机体积较小,载客量一般较少,通常在数人到数十人之间。其飞行速度相对较慢,巡航速度大多在每小时几百公里以内,而民航客机巡航速度可达每小时800 - 900公里左右。此外,低空飞机对跑道要求较低,许多型号可在短距跑道甚至土跑道上起降,而民航客机需要配备完善设施的大型机场跑道。
|
||||||
|
|
||||||
|
## 三、低空飞机的发展历程
|
||||||
|
### 3.1 早期探索阶段(20世纪初 - 20世纪中叶)
|
||||||
|
航空业诞生初期,飞机技术尚处于萌芽阶段,飞行高度普遍较低。早期的低空飞行主要是为了探索飞行技术的可行性和展示航空的魅力。例如,莱特兄弟的首次飞行就在相对较低的高度进行,开启了人类动力飞行的新纪元。这一时期的飞机结构简单,性能有限,但为低空飞行的后续发展奠定了基础。
|
||||||
|
|
||||||
|
### 3.2 发展应用阶段(20世纪中叶 - 20世纪末)
|
||||||
|
随着航空技术的不断进步,飞机性能得到显著提升,低空飞机开始在一些特定领域得到应用。在农业领域,低空飞机被用于农药喷洒和播种,大大提高了作业效率。同时,在航空摄影、旅游观光等领域也逐渐出现了低空飞机的身影。这一阶段,低空飞机的类型逐渐丰富,技术不断改进,开始展现出其在低空领域的独特优势。
|
||||||
|
|
||||||
|
### 3.3 快速增长阶段(21世纪初 - 至今)
|
||||||
|
进入21世纪,随着科技的飞速发展,新材料、新技术的应用使得低空飞机的性能进一步优化。同时,全球经济的发展和人们对个性化出行、多样化服务的需求增加,推动了低空飞机市场的快速增长。在应急救援、航空物探、空中巡逻等领域,低空飞机发挥着不可或缺的作用。此外,一些国家对低空空域的逐步开放,也为低空飞机的发展提供了更广阔的空间。
|
||||||
|
|
||||||
|
## 四、低空飞机的分类
|
||||||
|
### 4.1 按用途分类
|
||||||
|
1. **旅游观光类**:这类低空飞机通常注重乘客的舒适性和观景体验,客舱设计宽敞明亮,拥有大面积的观景窗户。例如一些轻型运动飞机,可搭载2 - 4名乘客,飞行高度一般在几百米左右,沿着特定的旅游线路飞行,让乘客欣赏到美丽的自然风光或城市景观。
|
||||||
|
2. **农林作业类**:主要用于农业和林业生产,如农药喷洒、种子播种、森林病虫害监测等。这类飞机通常具有较大的载药量或载种量,飞行高度较低且飞行轨迹较为规律。例如农用飞机,可在距离农作物几十米的高度进行大面积的农药喷洒作业,提高农业生产效率。
|
||||||
|
3. **航空摄影与测绘类**:配备高精度的摄影和测绘设备,能够在低空获取高分辨率的图像和地形数据。它们飞行稳定性好,可按照预设的航线和高度精确飞行,为地图绘制、城市规划、资源勘探等提供重要的数据支持。
|
||||||
|
4. **应急救援类**:用于应对自然灾害、事故救援等紧急情况,具备快速响应、低空悬停、吊运物资和救援人员等能力。直升机是应急救援中常见的低空飞机类型,可在复杂地形和恶劣天气条件下迅速抵达救援现场。
|
||||||
|
5. **商务出行类**:为商务人士提供快速、便捷的点对点出行服务,具有较高的飞行速度和舒适性。一些小型喷气式飞机或涡桨飞机属于此类,可搭载数名乘客,从小型机场或私人机场起飞,避开繁忙的民航航线,节省出行时间。
|
||||||
|
|
||||||
|
### 4.2 按动力类型分类
|
||||||
|
1. **活塞式发动机飞机**:以活塞式发动机为动力,通过燃油在气缸内燃烧产生动力驱动螺旋桨旋转,从而推动飞机前进。这类飞机结构相对简单,成本较低,适合低速、短距飞行,常用于轻型运动飞机和农林作业飞机。
|
||||||
|
2. **涡轮螺旋桨发动机飞机**:由涡轮发动机驱动螺旋桨,产生拉力使飞机飞行。涡轮螺旋桨发动机功率较大,飞行速度和航程相对活塞式发动机飞机有一定提升,适用于中短程的客货运输、航空摄影测绘等任务。
|
||||||
|
3. **喷气式发动机飞机**:利用燃气向后高速喷出产生的反作用力推动飞机前进,具有较高的飞行速度和巡航高度。喷气式低空飞机通常用于商务出行等对速度要求较高的领域,但成本相对较高,对机场设施要求也较为严格。
|
||||||
|
4. **电动飞机**:近年来随着电池技术的发展,电动飞机逐渐崭露头角。以电力驱动电机带动螺旋桨旋转,具有环保、噪音低等优点,但目前受电池能量密度限制,飞行时间和航程较短,主要应用于轻型运动飞机和一些特定的低空作业场景。
|
||||||
|
|
||||||
|
### 4.3 按机体结构分类
|
||||||
|
1. **固定翼飞机**:具有固定的机翼,通过机翼与气流的相对运动产生升力。固定翼低空飞机飞行速度相对较快,航程较远,适合长距离的低空作业和运输任务。例如轻型运动固定翼飞机,广泛应用于旅游观光和航空摄影领域。
|
||||||
|
2. **旋翼飞机**:通过旋转的旋翼产生升力和推力,能够垂直起降和在空中悬停。直升机是典型的旋翼飞机,具有高度的灵活性,可在狭小空间内起降,适用于应急救援、农林作业等需要频繁起降和低空悬停的任务。
|
||||||
|
3. **扑翼飞机**:模仿鸟类飞行原理,通过机翼的扑动产生升力和推力。扑翼飞机目前尚处于研究和试验阶段,虽然技术难度较大,但具有潜在的优势,如较低的能耗和更好的机动性,未来有望在低空飞行领域得到应用。
|
||||||
|
|
||||||
|
## 五、低空飞机的关键技术
|
||||||
|
### 5.1 飞行控制技术
|
||||||
|
1. **自动驾驶系统**:为减轻飞行员负担,提高飞行安全性和稳定性,低空飞机配备先进的自动驾驶系统。该系统可根据预设的航线、高度和速度自动控制飞机飞行,具备自动导航、自动保持姿态等功能。例如,在长途的航空摄影任务中,自动驾驶系统可确保飞机按照精确的航线飞行,保证拍摄数据的准确性。
|
||||||
|
2. **飞控计算机**:作为飞行控制系统的核心,飞控计算机实时采集飞机的各种传感器数据,如姿态、速度、高度等,并根据预设的控制算法对飞机的舵面、发动机等执行机构发出控制指令。先进的飞控计算机能够快速处理大量数据,实现对飞机的精确控制。
|
||||||
|
3. **电传飞控技术**:用电信号传输代替传统的机械连杆操纵系统,将飞行员的操纵指令转化为电信号传递给飞控计算机,由飞控计算机根据飞机状态和飞行条件进行综合处理后,再控制执行机构动作。电传飞控技术提高了飞机的操纵性能和响应速度,同时减轻了飞机结构重量。
|
||||||
|
|
||||||
|
### 5.2 导航与通信技术
|
||||||
|
1. **全球定位系统(GPS)**:为低空飞机提供精确的定位和导航信息,使飞行员能够实时了解飞机的位置、速度和航向。GPS接收机通过接收卫星信号计算飞机的三维坐标,结合电子地图和导航软件,引导飞机按照预定航线飞行。
|
||||||
|
2. **甚高频通信系统(VHF)**:用于飞机与地面塔台、其他飞机之间的语音通信,确保飞行过程中的信息传递畅通。VHF通信系统工作在甚高频频段,具有通信距离适中、信号稳定等特点,是低空飞行中常用的通信手段。
|
||||||
|
3. **自动相关监视 - 广播(ADS - B)**:通过飞机上的发射机自动向地面站和其他飞机广播自身的位置、高度、速度等信息,使地面管制部门和周围飞机能够实时掌握其飞行状态。ADS - B技术提高了飞行的安全性和空域管理效率,有助于避免空中冲突。
|
||||||
|
|
||||||
|
### 5.3 材料与结构技术
|
||||||
|
1. **复合材料的应用**:为减轻飞机重量,提高飞机的性能和经济性,低空飞机广泛采用复合材料,如碳纤维增强复合材料、玻璃纤维增强复合材料等。这些复合材料具有高强度、低密度、耐腐蚀等优点,可有效降低飞机的结构重量,提高燃油效率和飞行性能。
|
||||||
|
2. **轻量化结构设计**:在飞机结构设计上,采用先进的轻量化设计理念,优化飞机的结构布局,减少不必要的材料使用。例如,采用一体化设计的机翼和机身结构,减少连接件数量,提高结构的整体性和强度重量比。
|
||||||
|
3. **适用于低空环境的结构优化**:考虑到低空飞行可能面临的复杂气流和气象条件,对飞机结构进行针对性优化。加强飞机的抗风能力和结构刚度,确保在低空复杂环境下飞行的安全性和可靠性。
|
||||||
|
|
||||||
|
### 5.4 动力系统技术
|
||||||
|
1. **发动机性能优化**:针对低空飞机的不同应用需求,对发动机进行性能优化。例如,对于农林作业飞机,要求发动机具有良好的低空性能和可靠性,能够在长时间的低空低速飞行中稳定工作;对于商务出行飞机,则注重发动机的燃油效率和推力,以提高飞行速度和航程。
|
||||||
|
2. **混合动力与电动动力系统研发**:随着环保意识的增强和能源技术的发展,混合动力和电动动力系统在低空飞机领域的研发取得进展。混合动力系统结合传统燃油发动机和电动发动机的优势,提高能源利用效率;电动动力系统则具有零排放、噪音低等优点,为低空飞机的可持续发展提供了新的方向。
|
||||||
|
3. **发动机与飞机的匹配设计**:确保发动机与飞机的整体性能相匹配,包括发动机的安装位置、进气和排气系统设计等。合理的匹配设计能够提高发动机的工作效率,降低飞机的阻力,提升飞机的整体飞行性能。
|
||||||
|
|
||||||
|
## 六、低空飞机的运行环境
|
||||||
|
### 6.1 空域环境
|
||||||
|
1. **低空空域的划分与管理**:不同国家和地区对低空空域进行了详细划分和管理,以确保低空飞行的安全和有序。一般将低空空域划分为管制空域、监视空域和报告空域等不同类型,对不同类型空域的飞行活动实施不同的管理措施。例如,在管制空域内飞行需要提前向空中交通管制部门申请许可,并按照指定的航线和高度飞行。
|
||||||
|
2. **与其他空域的协调**:低空飞机的飞行需要与中高空的民航飞行、军事飞行等进行协调。通过建立完善的空域协调机制,避免不同空域飞行活动之间的冲突。例如,在机场附近的低空区域,需要与民航客机的起降活动进行合理调配,确保飞行安全。
|
||||||
|
3. **空域资源的开发与利用**:随着低空飞机数量的增加,对空域资源的需求也日益增长。各国积极探索低空空域资源的合理开发与利用,通过优化空域规划、采用先进的空中交通管理技术等方式,提高空域资源的利用率,满足低空飞行发展的需求。
|
||||||
|
|
||||||
|
### 6.2 气象环境
|
||||||
|
1. **低空气象特点**:低空气象条件复杂多变,与高空气象有较大差异。低空存在较强的地面风和湍流,容易受到地形、建筑物等因素的影响。此外,低空的云雾、降水、雾霾等天气现象对飞行安全也构成较大威胁。
|
||||||
|
2. **气象对低空飞行的影响**:强风、湍流会影响飞机的飞行姿态和稳定性,增加飞行操纵难度;云雾会降低能见度,影响飞行员的视线,增加飞行风险;降水可能导致飞机表面结冰,影响飞机的空气动力学性能。因此,低空飞行需要密切关注气象变化,合理规划飞行任务。
|
||||||
|
3. **气象监测与预警**:为保障低空飞行安全,建立完善的气象监测与预警系统至关重要。通过地面气象观测站、气象雷达、卫星云图等手段实时监测气象变化,并及时向飞行员发布气象预警信息。飞行员根据气象信息调整飞行计划,采取相应的应对措施。
|
||||||
|
|
||||||
|
### 6.3 地理环境
|
||||||
|
1. **地形地貌对低空飞行的影响**:不同的地形地貌,如山区、平原、水域等,对低空飞行产生不同的影响。在山区飞行,需要考虑地形起伏对飞行高度和航线的限制,避免与山峰碰撞;在水域上空飞行,要注意水面反射对视线的干扰以及特殊的气象条件,如海风、海雾等。
|
||||||
|
2. **障碍物识别与规避**:低空飞行过程中,地面上的建筑物、高压线、通信塔等障碍物对飞行安全构成潜在威胁。飞机上配备的地形感知与告警系统(TAWS)等设备能够实时监测飞机周围的地形和障碍物信息,向飞行员发出告警,提醒飞行员采取规避措施。
|
||||||
|
3. **机场与起降场地**:低空飞机对机场和起降场地的要求相对较低,但不同类型的低空飞机仍有不同的需求。一些轻型运动飞机可在短距跑道或土跑道上起降,而商务喷气式飞机则需要具备一定设施的小型机场跑道。此外,一些特殊用途的低空飞机,如直升机,可在野外开阔场地或建筑物顶部的停机坪起降。
|
||||||
|
|
||||||
|
## 七、低空飞机的市场应用
|
||||||
|
### 7.1 旅游观光市场
|
||||||
|
1. **空中观光项目的发展**:随着人们生活水平的提高和对旅游体验多样化的追求,空中观光旅游项目日益受到欢迎。低空飞机搭载游客在低空飞行,欣赏城市全景、自然风光、名胜古迹等独特景观。例如,在一些著名的旅游城市,游客可以乘坐轻型飞机或直升机从空中俯瞰城市的标志性建筑和美丽的海岸线。
|
||||||
|
2. **特色旅游线路开发**:各地纷纷开发具有特色的低空旅游线路,结合当地的自然和人文资源,为游客提供独特的旅游体验。如在山区开发的空中穿越峡谷、飞越森林的线路,以及在草原地区的低空俯瞰草原风光和牧民生活的线路等。
|
||||||
|
3. **对旅游经济的带动作用**:低空旅游观光产业的发展不仅为游客提供了新颖的旅游方式,还带动了当地旅游经济的增长。吸引了更多游客前来旅游,促进了酒店、餐饮、交通等相关产业的发展,增加了就业机会和地方财政收入。
|
||||||
|
|
||||||
|
### 7.2 农林作业市场
|
||||||
|
1. **农药喷洒与播种**:低空飞机在农业领域的农药喷洒和播种作业中具有高效、精准的优势。能够在短时间内覆盖大面积农田,均匀地喷洒农药或播种,提高农业生产效率,减少人力成本。同时,通过精准控制喷洒和播种的剂量,减少农药和种子的浪费,降低对环境的污染。
|
||||||
|
2. **森林病虫害监测与防治**:对于大面积的森林,低空飞机可快速进行病虫害监测,通过搭载的红外相机、高光谱成像仪等设备,及时发现病虫害发生区域,并准确掌握病虫害的严重程度。根据监测结果,及时进行防治作业,保护森林资源的健康生长。
|
||||||
|
3. **农业航空服务的发展趋势**:随着农业现代化的推进,农业航空服务市场不断扩大。未来,低空飞机在农业领域的应用将更加智能化、精准化,结合无人机技术、大数据和人工智能等,实现对农业生产的全方位、精细化服务。
|
||||||
|
|
||||||
|
### 7.3 航空摄影与测绘市场
|
||||||
|
1. **高分辨率图像获取**:在航空摄影领域,低空飞机能够获取高分辨率的地面图像,为城市规划、房地产开发、广告制作等提供高质量的影像资料。与卫星遥感相比,低空航空摄影具有更高的分辨率和灵活性,能够满足不同客户对图像细节的需求。
|
||||||
|
2. **地形测绘与地理信息采集**:利用低空飞机搭载测绘设备,进行地形测绘和地理信息采集工作。通过精确测量地面的高程、地貌等信息,绘制高精度的地形图和地理信息数据库,为交通建设、水利工程、资源勘探等项目提供重要的数据支持。
|
||||||
|
3. **新兴应用领域拓展**:随着技术的发展,航空摄影与测绘在一些新兴领域也得到应用,如城市三维建模、虚拟现实场景制作等。通过低空飞行获取的大量图像和数据,构建逼真的城市三维模型,为城市规划、旅游推广等提供新的展示手段。
|
||||||
|
|
||||||
|
### 7.4 应急救援市场
|
||||||
|
1. **自然灾害救援**:在地震、洪水、山体滑坡等自然灾害发生时,低空飞机能够迅速抵达受灾现场,进行人员搜救、物资运输、灾情侦察等救援工作。直升机可在复杂地形和恶劣天气条件下悬停作业,吊运救援人员和物资,为受灾群众提供及时的援助。
|
||||||
|
2. **事故救援与医疗转运**:在交通事故、工业事故等现场,低空飞机可快速将伤员送往医院进行救治,缩短救援时间,提高伤员的生存率。一些配备了先进医疗设备的空中救护车,能够在飞行过程中对伤员进行紧急医疗处理。
|
||||||
|
3. **应急救援体系建设**:低空飞机在应急救援中的应用推动了应急救援体系的建设和完善。各地加强了低空应急救援力量的配备,建立了统一的指挥调度平台,提高了应急救援的响应速度和协同作战能力。
|
||||||
|
|
||||||
|
### 7.5 商务出行市场
|
||||||
|
1. **点对点出行服务**:为商务人士提供快速、便捷的点对点出行服务,避开繁忙的民航航线和地面交通拥堵。小型喷气式飞机或涡桨飞机可从城市周边的小型机场或私人机场起飞,直接抵达目的地机场,节省出行时间,提高商务出行效率。
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -1,11 +0,0 @@
|
||||||
这是一个测试文档。
|
|
||||||
|
|
||||||
用于测试文档处理和向量化功能。
|
|
||||||
|
|
||||||
文档内容包含多个段落,用于测试文档分割功能。
|
|
||||||
|
|
||||||
每个段落都会被分割成不同的文档块(chunks)。
|
|
||||||
|
|
||||||
这些文档块会被向量化并存储到向量数据库中。
|
|
||||||
|
|
||||||
然后可以通过向量搜索找到相关的文档内容。
|
|
||||||
|
|
@ -293,7 +293,17 @@ class DocumentService:
|
||||||
|
|
||||||
self.session.desc = f"获取文档 {doc_id} 的文档块 > document"
|
self.session.desc = f"获取文档 {doc_id} 的文档块 > document"
|
||||||
# Get chunks from document processor
|
# Get chunks from document processor
|
||||||
chunks_data = (await get_document_processor(self.session)).get_document_chunks(document.knowledge_base_id, doc_id)
|
try:
|
||||||
|
doc_processor = await get_document_processor(self.session)
|
||||||
|
chunks_data = doc_processor.get_document_chunks(document.knowledge_base_id, doc_id)
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"获取文档处理器失败: {str(e)}"
|
||||||
|
self.session.desc = f"ERROR: {error_msg}"
|
||||||
|
logger.error(error_msg)
|
||||||
|
# 如果是因为嵌入模型未配置,返回空列表而不是抛出异常
|
||||||
|
if "未找到嵌入模型配置" in str(e) or "embeddings 未设置" in str(e):
|
||||||
|
return []
|
||||||
|
raise
|
||||||
|
|
||||||
self.session.desc = f"获取文档 {doc_id} 的文档块 > chunks_data"
|
self.session.desc = f"获取文档 {doc_id} 的文档块 > chunks_data"
|
||||||
# Convert to DocumentChunk objects
|
# Convert to DocumentChunk objects
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,18 @@ class DocumentProcessor:
|
||||||
)
|
)
|
||||||
|
|
||||||
async def initialize(self, session: Session = None):
|
async def initialize(self, session: Session = None):
|
||||||
|
# 先设置向量数据库路径(即使后续初始化失败,路径也应该被设置)
|
||||||
|
# 向量数据库存储路径(Chroma兼容)
|
||||||
|
vector_db_path = settings.vector_db.persist_directory
|
||||||
|
if not os.path.isabs(vector_db_path):
|
||||||
|
# 如果是相对路径,则基于项目根目录计算绝对路径
|
||||||
|
# 项目根目录是backend的父目录
|
||||||
|
backend_dir = Path(__file__).parent.parent.parent
|
||||||
|
vector_db_path = str(backend_dir / vector_db_path)
|
||||||
|
self.vector_db_path = vector_db_path
|
||||||
|
if session:
|
||||||
|
session.desc = f"初始化向量数据库 - 路径 = {self.vector_db_path}"
|
||||||
|
|
||||||
# 初始化嵌入模型 - 根据配置选择提供商
|
# 初始化嵌入模型 - 根据配置选择提供商
|
||||||
await self._init_embeddings(session)
|
await self._init_embeddings(session)
|
||||||
|
|
||||||
|
|
@ -61,16 +73,6 @@ class DocumentProcessor:
|
||||||
# # 初始化连接池
|
# # 初始化连接池
|
||||||
# self.pgvector_pool = PGVectorConnectionPool()
|
# self.pgvector_pool = PGVectorConnectionPool()
|
||||||
# logger.info("新版本PGVector使用psycopg3连接字符串: %s", self.connection_string)
|
# logger.info("新版本PGVector使用psycopg3连接字符串: %s", self.connection_string)
|
||||||
# else:
|
|
||||||
# 向量数据库存储路径(Chroma兼容)
|
|
||||||
vector_db_path = settings.vector_db.persist_directory
|
|
||||||
if not os.path.isabs(vector_db_path):
|
|
||||||
# 如果是相对路径,则基于项目根目录计算绝对路径
|
|
||||||
# 项目根目录是backend的父目录
|
|
||||||
backend_dir = Path(__file__).parent.parent.parent
|
|
||||||
vector_db_path = str(backend_dir / vector_db_path)
|
|
||||||
self.vector_db_path = vector_db_path
|
|
||||||
session.desc = f"初始化向量数据库 - 路径 = {self.vector_db_path}"
|
|
||||||
|
|
||||||
async def _init_embeddings(self, session: Optional[Any] = None):
|
async def _init_embeddings(self, session: Optional[Any] = None):
|
||||||
"""初始化嵌入模型。"""
|
"""初始化嵌入模型。"""
|
||||||
|
|
@ -325,6 +327,16 @@ class DocumentProcessor:
|
||||||
# return collection_name
|
# return collection_name
|
||||||
# else:
|
# else:
|
||||||
# Chroma兼容模式
|
# Chroma兼容模式
|
||||||
|
# 检查 vector_db_path 和 embeddings 是否已初始化
|
||||||
|
if not hasattr(self, 'vector_db_path') or not self.vector_db_path:
|
||||||
|
error_msg = "DocumentProcessor 未正确初始化:vector_db_path 未设置"
|
||||||
|
logger.error(error_msg)
|
||||||
|
raise ValueError(error_msg)
|
||||||
|
if not hasattr(self, 'embeddings') or not self.embeddings:
|
||||||
|
error_msg = "DocumentProcessor 未正确初始化:embeddings 未设置"
|
||||||
|
logger.error(error_msg)
|
||||||
|
raise ValueError(error_msg)
|
||||||
|
|
||||||
from langchain_chroma import Chroma
|
from langchain_chroma import Chroma
|
||||||
kb_vector_path = os.path.join(self.vector_db_path, f"kb_{knowledge_base_id}")
|
kb_vector_path = os.path.join(self.vector_db_path, f"kb_{knowledge_base_id}")
|
||||||
|
|
||||||
|
|
@ -356,6 +368,14 @@ class DocumentProcessor:
|
||||||
if len(documents) == 0:
|
if len(documents) == 0:
|
||||||
session.desc = f"WARNING: 文档列表为空,不执行添加操作"
|
session.desc = f"WARNING: 文档列表为空,不执行添加操作"
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# 检查 vector_db_path 是否已初始化
|
||||||
|
if not hasattr(self, 'vector_db_path') or not self.vector_db_path:
|
||||||
|
error_msg = "DocumentProcessor 未正确初始化:vector_db_path 未设置"
|
||||||
|
session.desc = f"ERROR: {error_msg}"
|
||||||
|
logger.error(error_msg)
|
||||||
|
raise ValueError(error_msg)
|
||||||
|
|
||||||
from langchain_chroma import Chroma
|
from langchain_chroma import Chroma
|
||||||
|
|
||||||
kb_vector_path = os.path.join(self.vector_db_path, f"kb_{knowledge_base_id}")
|
kb_vector_path = os.path.join(self.vector_db_path, f"kb_{knowledge_base_id}")
|
||||||
|
|
@ -458,6 +478,16 @@ class DocumentProcessor:
|
||||||
def delete_document_from_vector_store(self, knowledge_base_id: int, document_id: int) -> None:
|
def delete_document_from_vector_store(self, knowledge_base_id: int, document_id: int) -> None:
|
||||||
"""从向量存储中删除文档"""
|
"""从向量存储中删除文档"""
|
||||||
try:
|
try:
|
||||||
|
# 检查 vector_db_path 和 embeddings 是否已初始化
|
||||||
|
if not hasattr(self, 'vector_db_path') or not self.vector_db_path:
|
||||||
|
error_msg = "DocumentProcessor 未正确初始化:vector_db_path 未设置"
|
||||||
|
logger.error(error_msg)
|
||||||
|
raise ValueError(error_msg)
|
||||||
|
if not hasattr(self, 'embeddings') or not self.embeddings:
|
||||||
|
error_msg = "DocumentProcessor 未正确初始化:embeddings 未设置"
|
||||||
|
logger.error(error_msg)
|
||||||
|
raise ValueError(error_msg)
|
||||||
|
|
||||||
# Chroma兼容模式
|
# Chroma兼容模式
|
||||||
from langchain_chroma import Chroma
|
from langchain_chroma import Chroma
|
||||||
kb_vector_path = os.path.join(self.vector_db_path, f"kb_{knowledge_base_id}")
|
kb_vector_path = os.path.join(self.vector_db_path, f"kb_{knowledge_base_id}")
|
||||||
|
|
@ -625,51 +655,116 @@ class DocumentProcessor:
|
||||||
|
|
||||||
def _get_chunks_chroma(self, knowledge_base_id: int, document_id: int) -> List[Dict[str, Any]]:
|
def _get_chunks_chroma(self, knowledge_base_id: int, document_id: int) -> List[Dict[str, Any]]:
|
||||||
"""Chroma存储的处理逻辑"""
|
"""Chroma存储的处理逻辑"""
|
||||||
|
# 检查 vector_db_path 和 embeddings 是否已初始化
|
||||||
|
if not hasattr(self, 'vector_db_path') or not self.vector_db_path:
|
||||||
|
error_msg = "DocumentProcessor 未正确初始化:vector_db_path 未设置"
|
||||||
|
logger.error(error_msg)
|
||||||
|
raise ValueError(error_msg)
|
||||||
|
if not hasattr(self, 'embeddings') or not self.embeddings:
|
||||||
|
error_msg = "DocumentProcessor 未正确初始化:embeddings 未设置"
|
||||||
|
logger.error(error_msg)
|
||||||
|
raise ValueError(error_msg)
|
||||||
|
|
||||||
from langchain_chroma import Chroma
|
from langchain_chroma import Chroma
|
||||||
# 构建向量数据库路径
|
# 构建向量数据库路径
|
||||||
vector_db_path = os.path.join(self.vector_db_path, f"kb_{knowledge_base_id}")
|
vector_db_path = os.path.join(self.vector_db_path, f"kb_{knowledge_base_id}")
|
||||||
|
|
||||||
|
logger.info(f"获取文档块: kb_id={knowledge_base_id}, doc_id={document_id}, vector_db_path={vector_db_path}")
|
||||||
|
|
||||||
if not os.path.exists(vector_db_path):
|
if not os.path.exists(vector_db_path):
|
||||||
logger.warning(f"向量数据库不存在: {vector_db_path}")
|
logger.warning(f"向量数据库不存在: {vector_db_path}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# 加载向量数据库
|
# 加载向量数据库
|
||||||
vectorstore = Chroma(
|
try:
|
||||||
persist_directory=vector_db_path,
|
vectorstore = Chroma(
|
||||||
embedding_function=self.embeddings
|
persist_directory=vector_db_path,
|
||||||
)
|
embedding_function=self.embeddings
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"加载向量数据库失败: {vector_db_path}, 错误: {str(e)}")
|
||||||
|
return []
|
||||||
|
|
||||||
# 获取所有文档的元数据,筛选出指定文档的分段
|
# 获取所有文档的元数据,筛选出指定文档的分段
|
||||||
collection = vectorstore._collection
|
try:
|
||||||
all_docs = collection.get(include=["metadatas", "documents"])
|
collection = vectorstore._collection
|
||||||
all_ids_data = collection.get()
|
total_count = collection.count()
|
||||||
|
logger.info(f"向量数据库中共有 {total_count} 个向量")
|
||||||
chunks = []
|
|
||||||
chunk_index = 0
|
if total_count == 0:
|
||||||
|
logger.warning(f"向量数据库为空: {vector_db_path}")
|
||||||
for i, metadata in enumerate(all_docs["metadatas"]):
|
return []
|
||||||
if metadata.get("document_id") == str(document_id):
|
|
||||||
chunk_content = all_docs["documents"][i]
|
all_docs = collection.get(include=["metadatas", "documents"])
|
||||||
vector_id = all_ids_data["ids"][i]
|
all_ids_data = collection.get()
|
||||||
|
|
||||||
chunk = {
|
if not all_docs or "metadatas" not in all_docs or not all_docs["metadatas"]:
|
||||||
"id": f"chunk_{document_id}_{chunk_index}",
|
logger.warning(f"向量数据库中没有元数据: {vector_db_path}")
|
||||||
"content": chunk_content,
|
return []
|
||||||
"metadata": metadata,
|
|
||||||
"page_number": metadata.get("page"),
|
logger.info(f"获取到 {len(all_docs['metadatas'])} 个元数据项")
|
||||||
"chunk_index": chunk_index,
|
|
||||||
"start_char": metadata.get("start_char"),
|
chunks = []
|
||||||
"end_char": metadata.get("end_char"),
|
chunk_index = 0
|
||||||
"vector_id": vector_id
|
document_id_str = str(document_id)
|
||||||
}
|
|
||||||
chunks.append(chunk)
|
# 记录所有 document_id 以便调试
|
||||||
chunk_index += 1
|
all_document_ids = set()
|
||||||
|
for metadata in all_docs["metadatas"]:
|
||||||
return chunks
|
doc_id = metadata.get("document_id")
|
||||||
|
if doc_id:
|
||||||
|
all_document_ids.add(str(doc_id))
|
||||||
|
|
||||||
|
logger.info(f"向量数据库中的所有 document_id: {sorted(all_document_ids)}")
|
||||||
|
logger.info(f"正在查找 document_id: {document_id_str} (类型: {type(document_id_str)})")
|
||||||
|
|
||||||
|
for i, metadata in enumerate(all_docs["metadatas"]):
|
||||||
|
metadata_doc_id = metadata.get("document_id")
|
||||||
|
if metadata_doc_id:
|
||||||
|
metadata_doc_id_str = str(metadata_doc_id)
|
||||||
|
if metadata_doc_id_str == document_id_str:
|
||||||
|
chunk_content = all_docs["documents"][i] if i < len(all_docs["documents"]) else ""
|
||||||
|
vector_id = all_ids_data["ids"][i] if i < len(all_ids_data["ids"]) else None
|
||||||
|
|
||||||
|
# 使用元数据中的 chunk_index,如果没有则使用递增索引
|
||||||
|
chunk_idx = metadata.get("chunk_index", chunk_index)
|
||||||
|
|
||||||
|
chunk = {
|
||||||
|
"id": f"chunk_{document_id}_{chunk_idx}",
|
||||||
|
"content": chunk_content,
|
||||||
|
"metadata": metadata,
|
||||||
|
"page_number": metadata.get("page"),
|
||||||
|
"chunk_index": chunk_idx,
|
||||||
|
"start_char": metadata.get("start_char"),
|
||||||
|
"end_char": metadata.get("end_char"),
|
||||||
|
"vector_id": vector_id
|
||||||
|
}
|
||||||
|
chunks.append(chunk)
|
||||||
|
chunk_index += 1
|
||||||
|
|
||||||
|
# 按 chunk_index 排序
|
||||||
|
chunks.sort(key=lambda x: x["chunk_index"])
|
||||||
|
|
||||||
|
logger.info(f"找到 {len(chunks)} 个文档块 (document_id={document_id})")
|
||||||
|
return chunks
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取文档块时出错: kb_id={knowledge_base_id}, doc_id={document_id}, 错误: {str(e)}", exc_info=True)
|
||||||
|
return []
|
||||||
|
|
||||||
def search_similar_documents(self, knowledge_base_id: int, query: str, k: int = 5) -> List[Dict[str, Any]]:
|
def search_similar_documents(self, knowledge_base_id: int, query: str, k: int = 5) -> List[Dict[str, Any]]:
|
||||||
"""在知识库中搜索相似文档"""
|
"""在知识库中搜索相似文档"""
|
||||||
try:
|
try:
|
||||||
|
# 检查 vector_db_path 和 embeddings 是否已初始化
|
||||||
|
if not hasattr(self, 'vector_db_path') or not self.vector_db_path:
|
||||||
|
error_msg = "DocumentProcessor 未正确初始化:vector_db_path 未设置"
|
||||||
|
logger.error(error_msg)
|
||||||
|
raise ValueError(error_msg)
|
||||||
|
if not hasattr(self, 'embeddings') or not self.embeddings:
|
||||||
|
error_msg = "DocumentProcessor 未正确初始化:embeddings 未设置"
|
||||||
|
logger.error(error_msg)
|
||||||
|
raise ValueError(error_msg)
|
||||||
|
|
||||||
# if settings.vector_db.type == "pgvector":
|
# if settings.vector_db.type == "pgvector":
|
||||||
# # PostgreSQL pgvector存储
|
# # PostgreSQL pgvector存储
|
||||||
# collection_name = f"{settings.vector_db.pgvector_table_name}_kb_{knowledge_base_id}"
|
# collection_name = f"{settings.vector_db.pgvector_table_name}_kb_{knowledge_base_id}"
|
||||||
|
|
@ -766,5 +861,29 @@ async def get_document_processor(session: Session = None):
|
||||||
session.desc = "获取文档处理器实例"
|
session.desc = "获取文档处理器实例"
|
||||||
if document_processor is None:
|
if document_processor is None:
|
||||||
document_processor = DocumentProcessor()
|
document_processor = DocumentProcessor()
|
||||||
await document_processor.initialize(session)
|
try:
|
||||||
|
await document_processor.initialize(session)
|
||||||
|
except Exception as e:
|
||||||
|
# 如果初始化失败,清除部分初始化的实例
|
||||||
|
document_processor = None
|
||||||
|
error_msg = f"DocumentProcessor 初始化失败: {str(e)}"
|
||||||
|
if session:
|
||||||
|
session.desc = f"ERROR: {error_msg}"
|
||||||
|
logger.error(error_msg)
|
||||||
|
raise ValueError(error_msg) from e
|
||||||
|
|
||||||
|
# 检查实例是否已正确初始化
|
||||||
|
if not hasattr(document_processor, 'embeddings') or not document_processor.embeddings:
|
||||||
|
error_msg = "DocumentProcessor 未正确初始化:embeddings 未设置"
|
||||||
|
if session:
|
||||||
|
session.desc = f"ERROR: {error_msg}"
|
||||||
|
logger.error(error_msg)
|
||||||
|
# 尝试重新初始化
|
||||||
|
try:
|
||||||
|
document_processor = DocumentProcessor()
|
||||||
|
await document_processor.initialize(session)
|
||||||
|
except Exception as e:
|
||||||
|
document_processor = None
|
||||||
|
raise ValueError(f"DocumentProcessor 重新初始化失败: {str(e)}") from e
|
||||||
|
|
||||||
return document_processor
|
return document_processor
|
||||||
|
|
@ -35,8 +35,13 @@ class LLMConfigService:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def get_default_embedding_config(self, session: Session) -> Optional[LLMConfig]:
|
async def get_default_embedding_config(self, session: Session) -> Optional[LLMConfig]:
|
||||||
"""获取默认嵌入模型配置"""
|
"""获取默认嵌入模型配置,如果没有默认配置则尝试使用任何激活的嵌入模型配置"""
|
||||||
|
if session is None:
|
||||||
|
logger.error("get_default_embedding_config: session 为 None,无法查询配置")
|
||||||
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# 首先尝试获取默认嵌入模型配置
|
||||||
stmt = select(LLMConfig).where(
|
stmt = select(LLMConfig).where(
|
||||||
and_(
|
and_(
|
||||||
LLMConfig.is_default == True,
|
LLMConfig.is_default == True,
|
||||||
|
|
@ -44,20 +49,49 @@ class LLMConfigService:
|
||||||
LLMConfig.is_active == True
|
LLMConfig.is_active == True
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
config = None
|
config = (await session.execute(stmt)).scalar_one_or_none()
|
||||||
if session != None:
|
|
||||||
config = (await session.execute(stmt)).scalar_one_or_none()
|
|
||||||
if not config:
|
|
||||||
if session != None:
|
|
||||||
session.desc = "ERROR: 未找到默认嵌入模型配置"
|
|
||||||
return None
|
|
||||||
|
|
||||||
session.desc = f"获取默认嵌入模型配置 > 结果:{config}"
|
if config:
|
||||||
return config
|
session.desc = f"找到默认嵌入模型配置: {config.name} (ID: {config.id})"
|
||||||
|
return config
|
||||||
|
|
||||||
|
# 如果没有默认配置,尝试获取任何激活的嵌入模型配置作为后备
|
||||||
|
session.desc = "未找到默认嵌入模型配置,尝试查找任何激活的嵌入模型配置"
|
||||||
|
logger.info("未找到默认嵌入模型配置,尝试查找任何激活的嵌入模型配置")
|
||||||
|
|
||||||
|
stmt = select(LLMConfig).where(
|
||||||
|
and_(
|
||||||
|
LLMConfig.is_embedding == True,
|
||||||
|
LLMConfig.is_active == True
|
||||||
|
)
|
||||||
|
).order_by(LLMConfig.created_at) # 按创建时间排序,取第一个
|
||||||
|
|
||||||
|
config = (await session.execute(stmt)).scalar_one_or_none()
|
||||||
|
|
||||||
|
if config:
|
||||||
|
session.desc = f"使用激活的嵌入模型配置(非默认): {config.name} (ID: {config.id})"
|
||||||
|
logger.info(f"使用激活的嵌入模型配置(非默认): {config.name} (ID: {config.id})")
|
||||||
|
return config
|
||||||
|
|
||||||
|
# 如果还是没找到,记录详细信息
|
||||||
|
session.desc = "ERROR: 未找到任何激活的嵌入模型配置"
|
||||||
|
logger.error("未找到任何激活的嵌入模型配置")
|
||||||
|
|
||||||
|
# 尝试查询所有嵌入模型配置(包括未激活的),用于调试
|
||||||
|
all_embedding_stmt = select(LLMConfig).where(LLMConfig.is_embedding == True)
|
||||||
|
all_embedding = (await session.execute(all_embedding_stmt)).scalars().all()
|
||||||
|
if all_embedding:
|
||||||
|
logger.warning(f"找到 {len(all_embedding)} 个嵌入模型配置,但都不是激活状态:")
|
||||||
|
for cfg in all_embedding:
|
||||||
|
logger.warning(f" - {cfg.name} (ID: {cfg.id}, is_active={cfg.is_active}, is_default={cfg.is_default})")
|
||||||
|
else:
|
||||||
|
logger.warning("数据库中没有任何嵌入模型配置")
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if session != None:
|
session.desc = f"ERROR: 获取嵌入模型配置失败: {str(e)}"
|
||||||
session.desc = f"ERROR: 获取默认嵌入模型配置失败: {str(e)}"
|
logger.error(f"获取嵌入模型配置失败: {str(e)}", exc_info=True)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def get_config_by_id(self, config_id: int) -> Optional[LLMConfig]:
|
async def get_config_by_id(self, config_id: int) -> Optional[LLMConfig]:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue