zbparse/README.md

463 lines
16 KiB
Markdown
Raw Normal View History

2025-02-07 15:27:24 +08:00
项目地址:[zy123/zbparse - zbparse - 智标领航代码仓库](http://47.98.59.178:3000/zy123/zbparse)
git clone地址http://47.98.59.178:3000/zy123/zbparse.git
选择develop分支develop-xx 后面的xx越近越新。
正式环境121.41.119.164:5000
测试环境47.98.58.178:5000
大解析指从招标文件解析入口进去upload.py
小解析从投标文件生成入口进去little_zbparse 和get_deviation两个接口后端一起调
## 项目结构:
2025-02-13 22:22:04 +08:00
![1](C:\Users\Administrator\Desktop\md_files\1.png)
2025-02-07 15:27:24 +08:00
.env存放一些密钥大模型、textin等它是gitignore忽略了因此在服务器上git pull项目的时候这个文件不会更新因为密钥比较重要需要手动维护服务器相应位置的.env。
2025-02-13 22:22:04 +08:00
#### **如何更新服务器上的版本:**
2025-02-07 15:27:24 +08:00
1. 进入项目文件夹
2025-02-13 22:22:04 +08:00
![1](C:\Users\Administrator\Desktop\md_files\0.png)
2025-02-07 15:27:24 +08:00
**注意:**需要确认.env是否存在在服务器默认是隐藏的
输入cat .env
如果不存在在项目文件夹下sudo vim .env
将密钥粘贴进去!!!
2. git pull
3. sudo docker-compose up --build -d 更新并重启
或者 sudo docker-compose build 先构建镜像
sudo docker-compose up -d 等空间时再重启
4. sudo docker-compose logs flask_app --since 1h 查看最近1h的日志如果重启后报错也能查看推荐重启后都运行一下这个
requirements.txt一般无需变动除非代码中使用了新的库也要手动在该文件中添加包名及对应的版本
2025-02-13 22:22:04 +08:00
#### **如何本地启动本项目:**
2025-02-07 15:27:24 +08:00
1. requirements.txt里的环境要配好
2025-02-11 16:25:19 +08:00
conda create -n zbparse python=3.8
conda activate zbparse
pip install -r requirements.txt
2. .env环境配好 一般不需要在电脑环境变量中额外配置了但是要在Pycharm中安装插件使得项目能将env中的环境变量配置到系统环境变量中
2025-02-07 15:27:24 +08:00
3. 点击下拉框Edit configurations
2025-02-18 09:29:34 +08:00
![1](md_files/11.png)
2025-02-07 15:27:24 +08:00
2025-02-18 09:29:34 +08:00
设置run_serve.py为启动脚本![1](md_files/10.png)
2025-02-11 16:25:19 +08:00
注意这里的working directory要设置到最外层文件夹而不是flask_app
2025-02-07 15:27:24 +08:00
4. postman打post请求测试
http://127.0.0.1:5000/upload
body:
{
"file_url":"xxxx",
"zb_type":2
}
2025-02-11 16:25:19 +08:00
file_url如何获取[OSS管理控制台](https://oss.console.aliyun.com/bucket/oss-cn-wuhan-lr/bid-assistance/object?path=test%2F)
bid-assistance/test 里面找个文件的url推荐'094定稿-湖北工业大学xxx'
注意这里的url地址有时效性要经常重新获取新的url
2025-02-07 15:27:24 +08:00
2025-02-13 22:22:04 +08:00
#### 清理服务器上的文件夹
1. 编写shell文件sudo vim clean_dir.sh
命名为clean_dir.sh
2025-02-18 09:29:34 +08:00
清理/home/Z/zbparse_output_dev下的output1这些二级目录下的c8d2140d-9e9a-4a49-9a30-b53ba565db56这种uuid的三级目录只保留最近7天
2025-02-13 22:22:04 +08:00
```
#!/bin/bash
# 需要清理的 output 目录路径
ROOT_DIR="/home/Z/zbparse_output_dev"
# 检查目标目录是否存在
if [ ! -d "$ROOT_DIR" ]; then
echo "目录 $ROOT_DIR 不存在!"
exit 1
fi
echo "开始清理 $ROOT_DIR 下超过 7 天的目录..."
echo "以下目录将被删除:"
# -mindepth 2 表示从第二层目录开始查找,防止删除 output 下的直接子目录(如 output1、output2
# -depth 采用深度优先遍历,确保先处理子目录再处理父目录
find "$ROOT_DIR" -mindepth 2 -depth -type d -mtime +7 -print -exec rm -rf {} \;
echo "清理完成。"
```
2. 添加权限。
```
sudo chmod +x ./clean_dir.sh
```
3. 执行
```
sudo ./clean_dir.sh
```
2025-02-18 09:29:34 +08:00
4. 以 root 用户的身份编辑 crontab 文件从而设置或修改系统定时任务cron jobs。每天零点10分清理
2025-02-13 22:22:04 +08:00
```
sudo crontab -e
在里面添加:
10 0 * * * /home/Z/clean_dir.sh
```
2025-02-18 09:29:34 +08:00
**目前测试服务器和正式服务器都写上了!无需变动**
2025-02-13 22:22:04 +08:00
2025-02-07 15:27:24 +08:00
## flask_app结构介绍
2025-02-11 16:25:19 +08:00
### 项目中做限制的地方
#### **账号、服务器分流**
服务器分流目前linux服务器和windows服务器主要是硬件上的分流文件切分需要消耗CPU资源大模型基底还是调用阿里共用的tpm qpm。
账号分流qianwen_plus下的
```
api_keys = cycle([
os.getenv("DASHSCOPE_API_KEY"),
# os.getenv("DASHSCOPE_API_KEY_BACKUP1"),
# os.getenv("DASHSCOPE_API_KEY_BACKUP2")
])
api_keys_lock = threading.Lock()
def get_next_api_key():
with api_keys_lock:
return next(api_keys)
api_key = get_next_api_key()
```
只需轮流使用不同的api_key即可。目前没有启用。
#### **大模型的限制**
2025-02-11 16:25:19 +08:00
general/llm下的doubao.py 和通义千问long_plus.py
**目前是linux和windows各部署一套因此项目中的qps是对半的即calls=?**
1. 这是qianwen-long的限制针对阿里qpm为1200每秒就是20又linux和windows服务器对半就是10TPM无上限
2025-02-11 16:25:19 +08:00
```
@sleep_and_retry
@limits(calls=10, period=1) # 每秒最多调用10次
2025-02-11 16:25:19 +08:00
def rate_limiter():
pass # 这个函数本身不执行任何操作,只用于限流
```
2. 这是qianwen-plus的限制针对tpm为1000万每个请求2万tokens那么linux和windows总的qps为8时8x60x2=960<1000单个为4
**经过2.11号测试calls=4时最高TPM为800因此把目前稳定版把calls设为5**
**2.12,用turbo作为超限后的承载目前把calls设为7**
2025-02-11 16:25:19 +08:00
```
@sleep_and_retry
@limits(calls=7, period=1) # 每秒最多调用7次
2025-02-11 16:25:19 +08:00
def qianwen_plus(user_query, need_extra=False):
logger = logging.getLogger('model_log') # 通过日志名字获取记录器
```
3. qianwen_turbo的限制TPM为500万由于它是plus后的手段稳妥一点qps设为6两个服务器分流即calls=3
```
@sleep_and_retry
@limits(calls=3, period=1) # 500万tpm每秒最多调用6次两个服务器分流就是3次 plus超限后的保底手段稳妥一点
```
2025-02-11 16:25:19 +08:00
**重点!!**后续阿里扩容之后成倍修改这块**calls=?**
如果不用linux和windows负载均衡这里的calls也要乘2
#### **接口的限制**
2025-02-11 16:25:19 +08:00
1. start_up.py的def create_app()函数限制了对每个接口同时100次请求。这里事实上不再限制了因为100已经足够大了默认限制做到大模型限制这块。
```
app.connection_limiters['upload'] = ConnectionLimiter(max_connections=100)
app.connection_limiters['get_deviation'] = ConnectionLimiter(max_connections=100)
app.connection_limiters['default'] = ConnectionLimiter(max_connections=100)
app.connection_limiters['judge_zbfile'] = ConnectionLimiter(max_connections=100)
```
2. ConnectionLimiter.py以及每个接口上的装饰器
```
@require_connection_limit(timeout=1800)
def zbparse():
```
这里限制了每个接口内部执行的时间暂时设置到了30分钟不包括排队时间超时就是解析失败
#### **后端的限制:**
2025-02-11 16:25:19 +08:00
目前后端发起招标请求如果发送超过100max_connections=100个请求我这边会排队后面的请求这时后端的计时器会将这些请求也视作正在解析中事实上它们还在排队等待中这样会导致在极端情况下新进的解析文件速度大于解析的速度排队越来越长后面的文件会因为等待时间过长而直接失败而不是'解析失败'。
2025-02-07 15:27:24 +08:00
### general
是公共函数存放的文件夹llm下是各类大模型读取文件下是docx pdf文件的读取以及文档清理clean_pdf去页眉页脚页码
![1](md_files/2.png)
general下的llm下的清除file_id.py 需要**每周运行至少一次**防止file_id数量超出我这边对每次请求结束都有file_id记录并清理向应该还没加
llm下的model_continue_query是'模型继续回答'脚本,应对超长文本模型一次无法输出完的情况,继续提问,拼接成完整的内容。
general下的file2markdown是textin 文件--》markdown
general下的format_change是pdf-》docx 或doc/docx->pdf
general下的merge_pdfs.py是拼接文件的1.拼接招标公告+投标人须知 2.拼接评标细则章节+资格审查章节
**general中比较重要的**
**后处理:**
general下的**post_processing**,解析后的后处理部分包括extract_info、 资格审查、技术偏离 商务偏离 所需提交的证明材料,都在这块生成。
post_processing中的**inner_post_processing**专门提取*extracted_info*
post_processing中的**process_functions_in_parallel**提取
资格审查、技术偏离、 商务偏离、 所需提交的证明材料
![1](md_files/14.png)
大解析upload用了post_processing完整版
little_zbparse.py、小解析main.py用了inner_post_processing
get_deviation.py、偏离表数据解析main.py用了process_functions_in_parallel
**截取pdf**
*截取pdf_main.py*是顶级函数,
二级是*截取pdf货物标版*.py和*截取pdf工程标版.py* 非general下
三级是*截取pdf通用函数.py*
2025-02-07 17:08:33 +08:00
如何判断截取位置是否正确根据output文件夹中的切分情况打开各个文件查看是否切分准确目前的逻辑主要是按大章切分即'招标公告'章节)
**如果切分不准确,如何定位正则表达式?**
首先判断当前是工程标解析还是货物标解析即zb_type=1还是2
如果是2那么是货物标解析那么就是*截取pdf_main.py*调用*截取pdf货物标版*.py如下图selection=1代表截取'招标公告'那么如果招标公告没有切准就在这块修改。这里可以发现get_notice是通用函数即*截取pdf通用函数.py*中的get_notice函数那么继续往内部跳转。
若开头没截准就改begin_pattern末尾没截准就改end_pattern
![1](md_files/18.png)
![1](md_files/19.png)
另外:在*截取pdf货物标版*.py中还有extract_pages_twice函数即第一次没有切分到之后会运行该函数这边又有一套begin_pattern和end_pattern即二次提取
**如何测试?**
![1](md_files/20.png)
输入pdf_path和你要切分的序号selection=1代表切公告依次类推可以看切出来的效果如何。
2025-02-07 15:27:24 +08:00
**无效标和废标公共代码**
获取无效标与废标项的主要执行代码。对docx文件进行预处理=》正则=》temp.txt=》大模型筛选
如果提的不全,可能是正则没涵盖到位,也可能是大模型提示词漏选了。
这里如果段落中既被正则匹配又被follow_up_keywords中的任意一个匹配那么不会添加到temp中即不会被大模型筛选它会**直接添加**到最后的返回中!
![1](md_files/12.png)
**投标人须知正文条款提取成json文件**
将截取到的ztbfile_tobidders_notice_part2.pdf 即须知正文转为clause1.json 文件,便于后续提取**开评定标流程**、**投标文件要求**、**重新招标、不再招标和终止招标**
这块的主要逻辑就是匹配形如'一、总则'这样的大章节
然后匹配形如'1.1' '1.1.1'这样的序号由于是按行读取pdf一个序号后面的内容可能有好几行因此遇到下一个序号如'2.1')开头,之前的内容都视为上一个序号的。
### old_version
都是废弃文件代码,未在正式、测试环境中使用的,不用管
![1](md_files/3.png)
### routes
是接口以及主要实现部分,一一对应
![1](md_files/4.png)
get_deviation对应偏离表数据解析main获得偏离表数据
judge_zbfile对应判断是否是招标文件
little_zbparse对应小解析main负责解析extract_info
test_zbparse是测试接口无对应
upload对应工程标解析和货物标解析即大解析
**混淆澄清**:小解析可以指代一个过程,即从'投标文件生成'这个入口进去的解析后端会同时调用little_zbparse和get_deviation。这个过程称为'小解析'。
但是little_zbparse也叫小解析命名如此因为最初只需返回这些数据(extract_info),后续才陆续返回商务、技术偏离...
utils是接口这块的公共功能函数。其中validate_and_setup_logger函数对不同的接口请求对应到不同的output文件夹如upload->output1。后续增加接口也可直接在这里写映射关系。
![1](md_files/5.png)
重点关注大解析:**upload.py**和**货物标解析main.py**
### static
存放解析的输出和提示词
其中output用gitignore了git push不会推送这块内容。
各个文件夹(output1 output2..)对应不同的接口请求
![1](md_files/6.png)
### test_case&testdir
test_case是测试用例是对一些函数的测试。好久没更新了
testdir是平时写代码的测试的地方
它们都不影响正式和测试环境的解析
![1](md_files/7.png)
### 工程标&货物标
是两个解析流程中不一样的地方(一样的都写在**general**中了)
![1](md_files/8.png)
主要是货物标额外解析了采购要求提取采购需求main+技术参数要求提取+商务服务其他要求提取)
### 最后:
ConnectionLimiter.py定义了接口超时时间->超时后断开与后端的连接
![1](md_files/9.png)
logger_setup.py 为每个请求创建单独的log每个log对应一个log.txt
start_up.py是启动脚本run_serve也是启动脚本是对start_up.py的简单封装目前dockerfile定义的直接使用run_serve启动
## 持续关注
```
yield sse_format(tech_deviation_response)
yield sse_format(tech_deviation_star_response)
yield sse_format(zigefuhe_deviation_response)
yield sse_format(shangwu_deviation_response)
yield sse_format(shangwu_star_deviation_response)
yield sse_format(proof_materials_response)
```
1. 工程标解析目前仍没有解析采购要求这一块,因此后处理返回的只有'资格审查'和''证明材料"和"extracted_info",没有''商务偏离''及'商务带星偏离',也没有'技术偏离'和'技术带星偏离',而货物标解析是完全版。
其中''证明材料"和"extracted_info"是直接返给后端保存的
2. 大解析中返回了技术评分,后端接收后不仅显示给前端,还会返给向,用于生成技术偏离表
3. 小解析时get_deviation.py其实也可以返回技术评分但是没有返回因为没人和我对接暂时注释了。
![1](md_files/16.png)
4.商务评议和技术评议偏离表,即评分细则的偏离表,暂时没做,但是**商务评分、技术评分**无论大解析还是小解析都解析了,稍微对该数据处理一下返回给后端就行。
![1](md_files/17.png)
这个是解析得来的结果,适合给前端展示,但是要生成商务技术评议偏离表的话,需要再调一次大模型,对该数据进行重新归纳,以字符串列表为佳。再传给后端。(未做)
### 如何定位问题
1. 查看static下的output文件夹 upload大解析对应output1
2. docker-compose文件中规定了数据卷挂载的路径- /home/Z/zbparse_output_dev:/flask_project/flask_app/static/output
也就是说static/output映射到了服务器的Z/zbparse_output_dev文件夹
3. 根据时间查找哪个子文件夹uuid作为子文件名
4. 查看是否有final_result.json文件如果有说明解析流程正常结束了问题可能出在后端a.后端接口请求超限30分钟 b.后处理存在解析数据的时候出错)
也可能出现在自身解析可以查看子文件内的log.txt查看日志。
5. 若解析正常有final_result但解析不准可以根据以下定位
a.查看子文件夹下的文件切分是否准确例如如果评标办法不准确那么查看ztbfile_evaluation_methon是否正确切到了评分细则。如果切到了那就改general/商务技术评分提取里的提示词否则修改截取pdf那块关于'评标办法'的正则表达式。
b.总之是**先看切的准不准,再看提示词能否优化**,都要定位到对应的代码中!