
基于Agent的金融问答系统:代码重构 原创
前言
在上一章【项目实战】基于Agent的金融问答系统:前后端流程打通,我们已经完成了金融问答系统的前后端搭建,形成了可用的Demo。本章,我们将介绍代码重构的过程,并介绍一些优化点。
代码重构简介
在开启本章介绍之前,请允许我花点时间啰嗦两句,聊一聊代码重构的哪些事儿。
在过去经历的项目中,代码重构很少被人重视。看着像💩一样的代码(抱歉爆粗口,我所经历的一些项目包括我自己曾经写的代码,回看确实像💩一样),它们并没有被好好清理,然后我们在💩上面不断加需求,导致需求迭代越来越难,Bug越来越多...
这种事情现在每天还在不断地发生着,所以我决定有必要聊一聊代码重构。
什么是代码重构
代码重构是指对现有代码进行修改,以改善其结构、可读性和可维护性,而不改变其外部行为。重构的主要目的是提高代码质量,使其更易于理解和扩展。
代码重构的目的
• 提高可读性:使代码更易于理解,便于团队成员快速上手。
• 增强可维护性:降低后续修改和扩展的难度,减少潜在的错误。
• 优化性能:在不改变功能的情况下,提升代码的执行效率。
• 消除重复代码:通过抽象和重用,减少冗余,提高代码的整洁性。
代码重构的重要性
据统计,不好的代码会占用更多开发的时间。
代码重构的难点
通过代码重构提升代码质量既然如此重要,那么为什么很少有项目开展呢?
究其原因,可能有三点:
• 第一种:没有精力重构。开发工程师经常性被老板或者产品牵着鼻子走,完成一个需求接着一个新的需求,所以很少开展重构工作。这种情况在技术性为导向的项目还好,在以产品或市场为导向的项目中,尤其严重。
• 第二种:没有重构的思维。很多的开发工程师没有重构的思维甚至想法,他们以完成需求交付为目的,需求交付了也就代表他的工作结束了。
我曾经与谷歌回来的一位朋友有次交流,我们探讨的内容是:为什么国内的研发人员代码质量意识薄弱?他说其中一个很重要的原因是:硅谷的很多从业者,是因为热爱,热爱编程、热爱技术,所以视自己写的代码为一件艺术品,力求精益求精;而国内有很多从业者,是因为生存,是因为做开发给钱多,是一份养家糊口的一份工作而已,因为缺少热爱,所以交差了事即可。对此,我深以为然。 • 第三种:没有重构的方法论。虽然我们很像做重构,但是重构工作就像修复一辆越开越慢的车子,如果没有科学的方法,有可能出现拆了重装之后,反而多了几个螺丝的问题,这会让老板更加恐怖。
本章,我将试图以这个金融问答系统为例,简单介绍一些代码重构的原则、方法。
代码重构的过程
1、搭建测试框架以及用例集
在开展代码重构前,我们要搭建好一个便于回归测试的测试框架,通过边重构边回归的方式,可以快速定位问题所在,以此降低问题排查的成本。
我们在app目录下,已经创建了一个test_framework.py中,继续补充测试用例集,例如:
在大厂中,回归测试一般会使用单元测试框架(如pytest)来进行执行,由于本例中我们的方法较为简单,所以就没有使用pytest。
2、消灭代码中的坏味道
2.1、统一管理配置相关内容
在之前实现的RAG管理模块中,有很多的配置是硬编码写在代码初始化中的,例如:
我们可以将所有的配置相关抽取到一个settings.py中,然后在使用的代码中通过引用settings.py来进行配置。
说明:
- 为了有别于变量的命名,对于配置我们使用大写的变量名,例如:CHROMA_HOST、CHROMA_PORT等。
说明:
• 上述代码中通过import settings,在使用配置时通过settings.CHROMA_SERVER_TYPE、settings.CHROMA_HOST等来引用。
2.2、处理参数过长的问题
在原始代码中,随着我们的需求迭代,在创建RAG时需要传入多个的参数,例如:
• chroma_server_type
• host
• port
• persist_path
• collection_name
如果按照原来的方法写函数,那么函数的参数列表就会非常长,如下:
对于这种参数的问题,我们可以通过使用字典来处理,如下:
说明:
• db_config是一个字典,可以包含多个配置参数,例如:chroma_server_type、host、port、persist_path、collection_name等。
• db_config中的参数可以通过**关键字来解包,从而传入到函数中。
• RagManager 的初始化函数中,通过**关键字来解包db_config,从而传入到ChromaDB的初始化函数中。
2.3、减少重复代码
在【项目实战】基于Agent的金融问答系统:RAG检索模块初建成中,我们曾实现了一个pdf_processor.py, 该函数主要的工作是:
如果我们要将PDF文件给ElasticSearch服务里,那么这个过程大部分实现逻辑都是一样的,只是插入的对象不同,一个是向向量数据库中插入,一个是向elasticsearch中插入。
这种情况下,
• 不好的做法:复制上述代码到一个新的函数中,然后将最后一步insert_docs_chromadb()改为insert_docs_elasticsearch(),这样会导致代码重复。
• 较好的做法:对上述的插入过程进行重构,将插入函数通过函数类来调用,通过一个参数vector_db_class来决定插入向量数据库还是ElasticSearch。
说明:
• 在类的初始化函数中,我们通过一个参数vector_db来连接对应的数据库实例,同时传入db_type告知PDF处理器需要操作的数据库类型。
• 在处理PDF文件时,我们通过参数db_type来决定插入向量数据库还是ElasticSearch。
• 在插入文档 insert_docs 中,根据上一步骤传入的 insert_function 来调用具体的插入函数:如果是插入向量数据库,则传入的函数为self.insert_to_vector_db,那么调用时也会调用 insert_to_vector_db ;如果是插入ElasticSearch,则传入的函数为self.insert_to_elasticsearch,那么调用时会调用 insert_to_elasticsearch 。
2.4、使用静态扫描工具优化代码风格
我们可以使用静态扫描工具对代码进行风格优化,如Pylint、Flake8等,一般情况下PyCharm中会自带这些工具。
具体方法:
- 启动PyCharm
- 打开工程时,选择app目录
- 打开任意.py文件后,右上角会有静态扫描问题提示(如下图)
- 根据静态扫描的问题,进行代码风格修正(常见代码风格问题请见附录部分)
3、回归测试
在进行上面每一步重构时,都需要使用test_framework.py进行回归测试,确保重构后的代码没有引入新的错误。
由于本项目重构细节的内容非常多,不能一一列举,重构后的内容请查看Gitee或者Github仓库的代码。
本文转载自公众号一起AI技术 作者:Dongming
