AI原生应用及其架构

本书作者:测试开发刚哥,文章版权归个人所有,未经授权请勿转载。 1.1 大模型技术发展回顾和产业价值 1.1.1 大模型发展回顾与展望 2022年11月,ChatGPT横空出世,模型即服务(MaaS)模式快速兴起,AI从实验室走向商业场景。2024年,OpenAI推出o1模型和4o模型,大模型技术迎来质的飞跃。2025年,多家大模型厂商支持模型上下文协议(MCP),Google推出A2A架构,AI从单点工具向系统级生产力工具转变。 1.1.2 大模型的五大产业价值 效率新工具、服务新体验、产品新形态、决策新助手、科研新模式。 1.2 AI时代应用架构的演进 1.2.1 IT应用架构的演进脉络 云原生架构是指,Kubernetes等技术通过容器化、集群化管理,实现按量使用、秒级弹性的极致资源调度。 每一次架构升级,都是在满足业务规模更大、需求变化更快、资源成本更低的述求。 1.2.2 云原生应用架构向AI原生应用架构的跃迁 如果说,云原生解决的是如何高效地运行,那么AI原生是在此基础上解决如何智能地运行。 在云原生应用架构中,我们讨论的是容器如何管理、服务如何拆分、流量如何治理。而在AI原生应用架构下,其目标是在满足可扩展、可观测、安全合规的同时,最大化释放模型的智能潜力。 在大语言模型(LLM)出现之前,AI只能以功能模块形态嵌入系统。LLM具备通用理解、推理和生成能力,并能通过函数调用、外部工具联动和知识库,形成可扩展的Agent体系。AI原生应用的运行逻辑不再完全由工程师编写的代码所决定,而是由大模型进行自出判断、行动和生成,并具备以下3个特征: 以LLM为核心,用自然语言统一交互协议; 以多模态感知扩展输入边界,以Agent框架编排工具链; 以数据飞轮驱动模型持续进化,实现系统的自我优化。 1.3 AI原生应用及其架构的定义 AI原生应用架构,涵盖了模型、应用开发框架、提示词、RAG、记忆、工具、网关、运行时、可观测、评估和安全等关键要素。 1.3.1 大模型推理决策 传统应用使用编程语言(Java、C++等)进行编码,AI原生应用使用大语言模型(LLM),通过Prompt(提示词)等自然语言方式进行构建。 LLM具有语义理解与推理方面的能力,能够在面对模糊和复杂的开放式任务时,自主生成和调整业务执行逻辑,并根据需要完成工具调用与流程编排。 1.3.2 Agent编排和执行 传统应用是工具,Agent是助手。单Agent无法完成复杂任务时,可以协同多Agent编排完成复杂任务。自身能力受限时,可以扩展工具,甚至自己编写工具完成任务。 1.3.3 数据优化决策 模型输出可能存在偏差,甚至在某些情况下完全不符合用户或业务方的预期。为了解决这个问题,AI原生应用必须具备基于数据驱动的持续进化能力。在多轮交互中,持续保留并利用历史信息,理解用户的偏好、行为习惯与目标。同时,应用还需要通过数据采集构建高质量的评测数据集,并结合行业数据、用户反馈数据和客户业务数据进行持续评估与优化。 1.3.4 工具调用与环境连接 大语言模型运行机制的本质仍是基于“输入Token→输出Token”的序列生成过程。受限于这一机制,模型既无法直接感知外部环境,也无法获取实时更新的知识,更缺乏对物理世界的直接操控能力。 AI原生应用通常通过工具调用的方式扩展模型的环境连接能力。一是支持语音、图像乃至动作等多模态输入;二是支持联网检索获取最新信息,并且通过API对接外部系统,或直接驱动企业内部系统的业务流程。“模型+工具”协同运行。 1.4 AI原生应用架构成熟度 1.4.1 AI原生应用架构成熟度的定义 AI原生应用架构成熟度,是指用于综合衡量AI原生应用在技术实现、业务融合与安全可信等方面所达到的水平,客观反映其从简单功能集成到复杂智能决策的演进阶段与发展层次。 1.4.2 AI原生应用架构成熟度的演进 1、概念验证级:单点功能辅助 2、早期试用级:场景化初步闭环 3、成熟应用级:核心业务深度集成 4、完全成熟级:企业级自适应迭代 1.4.3 AI原生应用架构成熟度的评估 以五大能力特征作为核心评估维度:自然语言交互能力、多模态理解与生成能力、动态推理与自主决策能力、持续学习与迭代能力、安全可信。 本评估体系旨在为AI原生应用的规划、开发、部署与优化提供一套完整的诊断工具和行动指南,最终推动其从概念验证走向规模化、高价值的产业应用。 参考资料: https://developer.aliyun.com/ebook/8479

Flask使用SQLite数据库

SQLite是一个小型的轻量数据库,特别适合个人学习使用。因为SQLite不需要额外的数据库服务器,同时它也是内嵌在Python中的。缺点就是如果有大量的写请求过来,它是串行处理的,速度很慢。

2026-01-05 · 1 分钟 · 184 字 · 测试开发刚哥 · Python生态 · #Flask

HttpRunner3的$符号是如何解析的

本书作者:测试开发刚哥,文章版权归个人所有,未经授权请勿转载。 变量解析 $符号在很多测试框架中都会拿来用做变量解析,大名鼎鼎的JMeter就有这个用法。HttpRunner3也支持$符号,比如: Step( RunRequest("登录") .post("/login") .with_headers(**{"Content-Type": "application/json"}) .with_json({"username": "dongfanger", "password": "123456"}) .extract() .with_jmespath("body.token", "token") .validate() .assert_equal("status_code", 200) ), Step( RunRequest("搜索商品") .get("searchSku?skuName=电子书") .with_headers(**{"token": "$token"}) .extract() .with_jmespath("body.skuId", "skuId") .with_jmespath("body.price", "skuPrice") .validate() .assert_equal("status_code", 200) ), 从登录提取的变量token,在搜索商品中使用$token来引用。 parser.py HttpRunner对$符号的解析是在parser.py文件中实现的: 这些方法可以分为四类: url build_url() 变量 extract_variables() get_mapping_variable() parse_variables_mapping() regex_findall_variables() parse_data() parse_string() 函数 get_mapping_function() parse_function_params() regex_findall_functions() parse_string_value() parse_data() parse_string() 参数化("${parameterize(account.csv)}") parse_parameters() 本文专门针对变量这一块源码来进行剖析。 全局变量池 HttpRunner3在运行时的所有变量都是存储在__session_variables这个字典中的: 在脚本开始时会把config的预设变量加载进来,然后会在执行Step测试步骤时,把各个步骤的变量也放到全局变量池里面。 解析过程 在runner.py的第350行代码能看到解析变量的调用: 然后parse_variables_mapping()函数需要仔细看: def parse_variables_mapping( variables_mapping: VariablesMapping, functions_mapping: FunctionsMapping = None ) -> VariablesMapping: parsed_variables: VariablesMapping = {} while len(parsed_variables) != len(variables_mapping): for var_name in variables_mapping: if var_name in parsed_variables: continue var_value = variables_mapping[var_name] variables = extract_variables(var_value) # check if reference variable itself if var_name in variables: # e.g. # variables_mapping = {"token": "abc$token"} # variables_mapping = {"key": ["$key", 2]} raise exceptions.VariableNotFound(var_name) # check if reference variable not in variables_mapping not_defined_variables = [ v_name for v_name in variables if v_name not in variables_mapping ] if not_defined_variables: # e.g. {"varA": "123$varB", "varB": "456$varC"} # e.g. {"varC": "${sum_two($a, $b)}"} raise exceptions.VariableNotFound(not_defined_variables) try: parsed_value = parse_data( var_value, parsed_variables, functions_mapping ) except exceptions.VariableNotFound: continue parsed_variables[var_name] = parsed_value return parsed_variables tests/parser_test.py有一段测试代码: ...

JaCoCo增量覆盖率的基本实现原理

本书作者:测试开发刚哥,文章版权归个人所有,未经授权请勿转载。 什么是增量覆盖率 如图所示,在master分支提交了HelloController,然后从master拉了个新分支test;提交了第1次代码,增加了WorldController;提交了第2次代码,增加了DonController。增量的获取方式有两种: 版本对比:在分支上,第2次提交和第1次提交版本对比的增量代码,就是DonController;第2次提交和刚从master拉取分支时版本对比的增量代码,除了DonController还有WorldController; 分支对比:把test分支和master分支进行对比,增量代码就是DonController和WorldController; 增量,就是通过版本对比或分支对比,多出来的新增加的那部分代码。覆盖率是指程序运行以后,有多少代码被执行到了,除以总的代码数算出来的,即覆盖率=执行代码行数/总代码行数。增量覆盖率是针对增量代码来计算的,也就是增量覆盖率=执行代码行数/增量代码行数。 基本实现原理 使用JaCoCo的On-the-fly模式,以tcpserver方式启动,将远程机器的覆盖率数据通过TCP通信dump到本地jacoco.exec文件,二次开发JaCoCo源码,实现增量覆盖率分析和输出HTML报告。 JaCoCo默认覆盖率 JaCoCo是一个开源的覆盖率工具,它的On-the-fly模式,无须侵入应用启动脚本,只需在JVM中通过-javaagent参数指定jar文件启动的代理程序,代理程序在ClassLoader装载一个class前判断是否需要注入class文件,将统计代码插入class,覆盖率分析就可以在JVM执行的过程中完成。 启动脚本示例: java -javaagent:jacocoagent.jar -jar target/app.jar 默认情况下,JaCoCo会在JVM停掉以后,生成覆盖率数据,一个jacoco.exec文件。 官方手册也有相关说明: output默认为file:At VM termination execution data is written to the file specified in the destfile attribute。这种方式有2个局限,一是必须停掉应用;二是只能把覆盖率数据生成到本地文件。 output还有另外一个选项,tcpserver: java -javaagent:jacocoagent.jar=output=tcpserver,address=0.0.0.0,port=2014 -jar target/app.jar address:服务端地址; port:服务端端口; 通过这种方式启动后,会同时启动一个TCP服务端,使用lsof -i :2014命令查看端口进程: 然后就能通过TCP通信,来获取覆盖率数据,也就是所谓的”dump“。 代码片段: @Override public void dump(String dumpPath, String ip, int port) throws IOException { dumpPath = FileUtil.getAbsolutePath(dumpPath); if (!FileUtil.exist(dumpPath)) { FileUtil.mkdir(dumpPath); } FileOutputStream localFile = new FileOutputStream(dumpPath + File.separator + "jacoco.exec"); ExecutionDataWriter localWriter = new ExecutionDataWriter(localFile); SocketAddress socketAddress = new InetSocketAddress(ip, port); Socket socket = new Socket(); try { socket.connect(socketAddress, 10000); RemoteControlWriter writer = new RemoteControlWriter(socket.getOutputStream()); RemoteControlReader reader = new RemoteControlReader(socket.getInputStream()); reader.setSessionInfoVisitor(localWriter); reader.setExecutionDataVisitor(localWriter); log.info("开始dump:{} {}", ip, port); writer.visitDumpCommand(true, false); if (!reader.read()) { log.error("Socket closed unexpectedly"); throw new IOException("Socket closed unexpectedly."); } log.info("dump完成:{} {}", ip, port); } catch (Exception e) { log.info("dump失败:{}", e.getMessage()); } finally { socket.close(); localFile.close(); } } dump经过TCP通信,将远程机器的覆盖率数据取到本地后,生成jacoco.exec文件,然后可以使用jacococli.jar生成HTML报告: ...

JavaWeb开发基础Servlet请求分发与重定向

本书作者:测试开发刚哥,文章版权归个人所有,未经授权请勿转载。 请求分发 Servlet的RequestDispatcher接口定义: package javax.servlet; import java.io.IOException; public interface RequestDispatcher { String FORWARD_REQUEST_URI = "javax.servlet.forward.request_uri"; String FORWARD_CONTEXT_PATH = "javax.servlet.forward.context_path"; String FORWARD_PATH_INFO = "javax.servlet.forward.path_info"; String FORWARD_SERVLET_PATH = "javax.servlet.forward.servlet_path"; String FORWARD_QUERY_STRING = "javax.servlet.forward.query_string"; String INCLUDE_REQUEST_URI = "javax.servlet.include.request_uri"; String INCLUDE_CONTEXT_PATH = "javax.servlet.include.context_path"; String INCLUDE_PATH_INFO = "javax.servlet.include.path_info"; String INCLUDE_SERVLET_PATH = "javax.servlet.include.servlet_path"; String INCLUDE_QUERY_STRING = "javax.servlet.include.query_string"; String ERROR_EXCEPTION = "javax.servlet.error.exception"; String ERROR_EXCEPTION_TYPE = "javax.servlet.error.exception_type"; String ERROR_MESSAGE = "javax.servlet.error.message"; String ERROR_REQUEST_URI = "javax.servlet.error.request_uri"; String ERROR_SERVLET_NAME = "javax.servlet.error.servlet_name"; String ERROR_STATUS_CODE = "javax.servlet.error.status_code"; void forward(ServletRequest var1, ServletResponse var2) throws ServletException, IOException; void include(ServletRequest var1, ServletResponse var2) throws ServletException, IOException; } 它有2个方法,forward和include: ...

Java官方笔记2面向对象编程基本概念

本书作者:测试开发刚哥,文章版权归个人所有,未经授权请勿转载。 对象Object 对象有2个特征:状态state和行为behavior。我们可以用真实世界的对象来做类比,这样有助于理解面向对象编程,比如狗的状态(名字、颜色、品种)和行为(叫、抓、摇尾巴),自行车的状态(当前档位、当前踏板节奏、当前速度)和行为(切换档位、切换踏板节奏、踩刹车)。 软件对象也是由状态和行为组成: 状态,在软件对象中,叫做字段fields,或变量variables。 行为,在软件对象中,叫做方法methods,或函数functions。 所谓的数据封装data encapsulation,就是字段只在内部,不对外暴露,仅由方法进行操作,对象与对象之间通过方法来相互访问。 Java是纯粹的面向对象编程语言。 类Class 类,可以视为对象的模版,基于类来创造出同一类对象。 比如自行车的类: class Bicycle { int cadence = 0; int speed = 0; int gear = 1; void changeCadence(int newValue) { cadence = newValue; } void changeGear(int newValue) { gear = newValue; } void speedUp(int increment) { speed = speed + increment; } void applyBrakes(int decrement) { speed = speed - decrement; } void printStates() { System.out.println("cadence:" + cadence + " speed:" + speed + " gear:" + gear); } } 类也是有2个特征:字段和方法。比如Bicycle类有字段cadence、speed、gear和方法changeCadence()、changeGear()、speedUp()等。 ...

JMeter测试计划配置项解析

测试计划(Test Plan),是JMeter目录树的初始根节点,它的编辑区如下:

JUnit5的条件测试、嵌套测试、重复测试

本书作者:测试开发刚哥,文章版权归个人所有,未经授权请勿转载。 条件测试 JUnit5支持条件注解,根据布尔值判断是否执行测试。 自定义条件 @EnabledIf和@DisabledIf注解用来设置自定义条件,示例: @Test @EnabledIf("customCondition") void enabled() { // ... } @Test @DisabledIf("customCondition") void disabled() { // ... } boolean customCondition() { return true; } 其中customCondition()方法用来返回布尔值,它可以接受一个ExtensionContext类型的参数。如果定义在测试类外部,那么需要是static方法。 内置条件 JUnit5的org.junit.jupiter.api.condition包中内置了一些条件注解。 操作系统条件 @EnabledOnOs和DisabledOnOs,示例: @Test @EnabledOnOs(MAC) void onlyOnMacOs() { // ... } @TestOnMac void testOnMac() { // ... } @Test @EnabledOnOs({ LINUX, MAC }) void onLinuxOrMac() { // ... } @Test @DisabledOnOs(WINDOWS) void notOnWindows() { // ... } @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Test @EnabledOnOs(MAC) @interface TestOnMac { } JRE条件 ...

pytest参数化进阶

用过unittest的朋友,肯定知道可以借助DDT实现参数化。用过JMeter的朋友,肯定知道JMeter自带了4种参数化方式(见参考资料)。pytest同样支持参数化,而且很简单很实用。

SpringBoot官方笔记2使用

本书作者:测试开发刚哥,文章版权归个人所有,未经授权请勿转载。 Build Systems 选择Maven or Gradle,而不要Ant(not particularly well supported) In practice, you do not need to provide a version for any of these dependencies (spring-boot-dependencies) in your build configuration, as Spring Boot manages that for you. When you upgrade Spring Boot itself, these dependencies are upgraded as well in a consistent way. Each release of Spring Boot is associated with a base version of the Spring Framework. Starters are a set of convenient dependency descriptors that you can include in your application. ...