LLM应用测试策略:基于SGLang框架的工程化实践 1. 项目概述为什么LLM应用需要专属的测试策略最近在做一个基于大语言模型的金融问答机器人项目上线前我们团队踩了不少坑。最典型的一次是一个看似简单的提示词优化在本地测试时效果拔群但一到生产环境响应速度直接慢了5倍还时不时抛出诡异的API超时错误。这让我深刻意识到传统的软件测试方法在面对LLM应用这种“非确定性”系统时有点力不从心了。你没法像测一个计算器函数那样给定输入11就断言输出必须是2。LLM的输出是概率性的、上下文依赖的而且严重受外部API如OpenAI、通义千问的稳定性影响。这就是为什么我们需要像SGLang这样的框架并为其量身定制一套从单元到集成的测试策略。SGLang不是一个简单的LLM调用封装它是一个专注于高效编排和执行LLM提示程序的运行时和编译器。你可以把它想象成LLM世界的“高性能计算引擎”它通过RadixAttention一种基于KV缓存重用的注意力机制优化、自动并行化、连续批处理等技术大幅提升复杂提示比如多轮对话、思维链、函数调用的执行效率。但引擎再强如果燃料提示词不对路或者零件外部依赖不匹配照样会抛锚。因此本文要聊的就是如何为基于SGLang构建的LLM应用搭建一套可靠的“质检流水线”。这套策略的目标很明确在提升开发效率利用SGLang的快的同时保障应用的确定性、稳定性和性能通过测试的稳。无论你是正在用SGLang开发智能客服、代码助手还是像我一样在做金融领域的RAG应用这套从微观到宏观的测试思路都能帮你把“玄学”的LLM应用变得更具工程化的可控性。2. 测试策略全景图构建LLM应用的质量防线为SGLang应用设计测试不能照搬Web或移动端的那一套。我们需要建立一个分层的、有针对性的防御体系。这个体系的核心思想是由内而外由确定到不确定逐步扩大测试范围同时利用工具将非确定性输出转化为可衡量的指标。2.1 测试金字塔在LLM场景下的重塑传统的测试金字塔单元测试 - 集成测试 - 端到端测试依然适用但每一层的含义和实现方式需要重新定义。单元测试基石层测试对象是最小的、内部逻辑确定的代码单元。在SGLang应用中这不再是测试一个LLM调用返回了什么具体文本而是测试提示词模板的渲染逻辑给定不同的变量输入生成的最终提示词是否符合预期格式特殊字符是否被正确转义输出解析器对于SGLang中定义的gen函数其stop_tokens,temperature等参数设置是否生效后续的解析代码能否将LLM的非结构化输出如JSON字符串正确解析为结构化的Python对象业务逻辑函数在调用SGLang runtime之前或之后的纯业务逻辑比如数据清洗、结果校验、条件判断等。核心方法这里的关键是Mock模拟掉SGLang runtime或LLM API的调用。我们只关心我们写的代码逻辑对不对不关心此刻OpenAI的API是否稳定。使用unittest.mock或pytest-mock可以轻松实现。集成测试枢纽层这是LLM应用测试的重中之重。在这一层我们开始连接真实或仿真的组件。测试对象是多个模块协作的正确性。对于SGLang应用主要包括SGLang Runtime集成测试我们的程序是否能正确初始化SGLang runtime配置模型路径如Qwen-7B-Chat、加载适配器如LoRA权重等。RAG流程集成测试从用户问题-检索器如LangChain的VectorStoreRetriever-获取上下文-拼接提示词-通过SGLang调用LLM-解析答案的整个链条是否通畅。这里可以使用嵌入模型和向量数据库的测试双胞胎例如使用内存型的ChromaDB和轻量级的sentence-transformers模型来模拟生产环境。外部服务集成测试与数据库、缓存Redis、或其他微服务的交互是否正常。同样可以使用测试数据库或Mock。关键路径验证针对一些核心用户场景用一组预先定义好的输入和期望输出进行测试。这里的“期望输出”可能不是精确字符串而是通过评估器Evaluator来衡量的比如检查输出是否包含某个关键词、是否遵循了指定的JSON格式、情感是否积极等。端到端测试验收层模拟真实用户操作测试完整的应用流程。对于一个Web服务这可能意味着使用Selenium或Playwright打开浏览器输入问题检查返回的答案。这一层测试成本高、速度慢、脆弱但能发现跨整个系统的交互问题。通常只针对最重要的“快乐路径”进行。非功能测试贯穿层性能测试这是SGLang的强项但也必须验证。我们需要测试在并发请求下SGLang应用的吞吐量Tokens per second和延迟Time to First Token, 生成耗时是否满足要求。特别是要测试SGLang的连续批处理能力看其是否真如宣传那样能在多请求下有效复用KV缓存。稳定性/混沌测试模拟LLM API偶尔失败、网络抖动、GPU内存不足等情况看我们的应用是否有健全的重试、降级或优雅失败机制。2.2 核心测试资产提示词版本管理与评估体系LLM应用的核心资产是提示词。测试策略必须包含对提示词的管理和评估。提示词版本化像管理代码一样管理提示词模板。使用Git进行版本控制任何对提示词的修改都应通过代码审查和测试验证。可以将提示词存储在单独的.yaml或.json文件中方便维护和对比。构建评估数据集这是将“感觉”转化为“指标”的关键。你需要为你的应用领域构建一个测试集例如金融问答100个常见的用户问题如“什么是市盈率”、“请对比一下A股和美股的主要区别”。每个测试用例除了输入问题还应包含参考答案专家提供的标准答案用于精确匹配评估。关键信息点答案中必须包含的关键词或知识点列表用于模糊评估。拒绝回答的范畴对于超出领域的问题模型是否正确地拒绝了回答。自动化评估流水线将上述测试集集成到CI/CD流程中。每次代码或提示词更新后自动运行集成测试调用评估器对结果进行打分。评估器可以是规则型检查格式、关键词包含、不包含敏感词等。模型型使用另一个更强大的LLM如GPT-4作为裁判评判答案的相关性、正确性、有害性等。虽然成本高且慢但对于关键测试定期运行非常有价值。3. 单元测试实战Mock与确定性逻辑验证让我们深入到代码层面看看如何为SGLang应用编写有效的单元测试。假设我们有一个简单的金融术语解释函数它使用SGLang来调用模型。3.1 被测函数示例# financial_agent.py import sglang as sgl from sglang import function class FinancialAgent: def __init__(self, model_path: str): # 初始化SGLang runtime这里会被Mock self.runtime sgl.Runtime(model_pathmodel_path) function def explain_term(self, term: str) - str: 解释一个金融术语 prompt f 你是一个资深的金融顾问。请用简洁明了的语言解释以下金融术语。 术语{term} 解释 # 调用SGLang执行生成 response self.runtime.run( prompt, max_tokens150, temperature0.1, # 低温度保证解释的稳定性 stop[\n\n] # 遇到两个换行则停止 ) return response.text.strip() def process_and_format(self, term: str) - dict: 业务逻辑解释术语并格式化为结构化输出 explanation self.explain_term(term) # 一些后续处理逻辑比如提取关键句 sentences explanation.split(。) key_sentence sentences[0] if sentences else explanation return { term: term, explanation: explanation, key_point: key_sentence }3.2 使用pytest和unittest.mock进行测试我们要测试的是process_and_format方法中的业务逻辑以及explain_term函数的参数传递是否正确但绝不能真实调用LLM。# test_financial_agent.py import pytest from unittest.mock import Mock, patch, AsyncMock from financial_agent import FinancialAgent class TestFinancialAgent: def test_process_and_format_logic(self): 测试业务逻辑处理部分完全Mock掉LLM调用 # 1. 创建被测对象并Mock其runtime属性 agent FinancialAgent(model_pathfake/path) # 创建一个模拟的response对象其text属性是我们预设的返回值 mock_response Mock() mock_response.text 市盈率是公司市值与其净利润的比率。它用于评估股票估值水平。比率越高可能代表市场对其增长预期越高。 # 2. 将agent对象的explain_term方法替换为一个返回固定值的Mock # 这样当process_and_format内部调用explain_term时得到的是我们预设的文本 agent.explain_term Mock(return_valuemock_response.text) # 3. 执行测试 result agent.process_and_format(市盈率) # 4. 验证断言 assert result[term] 市盈率 assert 市盈率 in result[explanation] assert result[key_point] 市盈率是公司市值与其净利润的比率 # 验证分割逻辑 # 验证explain_term被以正确的参数调用了一次 agent.explain_term.assert_called_once_with(市盈率) patch(financial_agent.sgl.Runtime) # 通过装饰器Mock整个Runtime类 def test_explain_term_parameters(self, mock_runtime_class): 测试explain_term是否以正确的参数调用了SGLang runtime # 1. 准备Mock mock_runtime_instance Mock() mock_run_method Mock() mock_runtime_instance.run mock_run_method mock_runtime_class.return_value mock_runtime_instance # 让Runtime构造函数返回我们的Mock实例 # 2. 创建被测对象此时其内部的self.runtime已经是我们的Mock了 agent FinancialAgent(model_pathqwen-7b-chat) # 3. 执行函数 test_term 量化宽松 agent.explain_term(test_term) # 4. 验证SGLang runtime的run方法是否被正确调用 # 首先检查run方法是否被调用了一次 assert mock_run_method.call_count 1 # 获取run方法被调用时的参数 call_args, call_kwargs mock_run_method.call_args # 验证第一个位置参数prompt是否包含了我们的术语 actual_prompt call_args[0] assert test_term in actual_prompt # 验证关键字参数 assert call_kwargs[max_tokens] 150 assert call_kwargs[temperature] 0.1 assert call_kwargs[stop] [\n\n]关键技巧在单元测试中我们的目标是验证我们编写的代码逻辑。因此所有外部依赖SGLang、LLM API、数据库都必须被Mock。patch装饰器是pytest中非常强大的工具它可以在测试期间将指定的模块、类或函数替换为Mock对象。3.3 测试提示词模板渲染提示词模板的渲染本身也是一个纯函数非常适合单元测试。# prompt_templates.py def render_financial_advice_prompt(user_query: str, context: list[str]) - str: 渲染金融建议提示词 context_str \n.join([f- {c} for c in context]) prompt f 基于以下背景信息 {context_str} 请回答用户的问题{user_query} 要求 1. 答案专业、准确。 2. 如果背景信息不足请明确说明。 3. 使用中文回答。 return prompt # test_prompt_templates.py def test_render_financial_advice_prompt(): context [当前市场波动较大, 投资者风险偏好降低] user_query 我现在应该买入股票吗 result render_financial_advice_prompt(user_query, context) # 断言渲染结果包含了所有必要元素 assert user_query in result for ctx in context: assert ctx in result assert 背景信息不足 in result # 检查要求中的关键词 assert 中文 in result # 检查格式确保上下文被正确格式化为列表 assert - 当前市场波动较大 in result单元测试小结这一层的测试运行极快毫秒级给了开发者快速反馈的勇气。它确保了代码骨架的健壮性。每次修改提示词模板或解析逻辑后跑一遍单元测试能立刻发现语法错误或逻辑漏洞。4. 集成测试实战连接真实组件与评估驱动集成测试是检验SGLang应用是否“能用”的关键。在这一步我们会使用真实的SGLang Runtime但通常连接一个轻量级模型如Qwen-1.8B-Chat或本地部署的测试模型以避免高昂的API成本和不稳定性。4.1 搭建测试专用的SGLang环境首先我们需要一个独立的测试配置。# conftest.py (pytest的全局配置文件) import pytest import sglang as sgl from sglang import function pytest.fixture(scopesession) def test_sglang_runtime(): 启动一个测试用的SGLang runtime整个测试会话只启动一次 # 使用一个非常小的、本地的模型进行集成测试 # 例如可以提前下载好Qwen1.5-1.8B-Chat的GGUF量化版 runtime sgl.Runtime( model_path./models/qwen1.5-1.8b-chat-q4_k_m.gguf, tokenizer_pathQwen/Qwen1.5-1.8B-Chat, # 或本地路径 # 使用CPU或最小的GPU资源 devicecuda:0, # 或 cpu # 限制资源避免影响开发机 max_total_token_num2048, ) yield runtime runtime.shutdown() pytest.fixture def financial_agent_with_test_runtime(test_sglang_runtime): 提供一个绑定了测试runtime的Agent实例 from financial_agent import FinancialAgent agent FinancialAgent(model_path) # 路径在fixture中已设置 # 替换其runtime为测试fixture提供的runtime agent.runtime test_sglang_runtime return agent4.2 编写集成测试用例现在我们可以编写测试验证整个链条。# test_integration_financial_rag.py import pytest from your_rag_module import FinancialRAGSystem # 假设的RAG系统类 class TestFinancialRAGIntegration: pytest.mark.integration pytest.mark.slow # 标记为慢测试可以单独运行 def test_rag_flow_with_real_model(self, financial_agent_with_test_runtime): 集成测试使用真实的小模型测试RAG全流程 # 1. 准备测试数据 # 使用一个内存向量库如FAISS和测试用的嵌入模型如all-MiniLM-L6-v2 # 这里假设你的RAG系统初始化时能接受这些测试组件 from langchain.embeddings import HuggingFaceEmbeddings from langchain.vectorstores import FAISS from langchain.docstore.in_memory import InMemoryDocstore # 创建测试知识片段 test_docs [ 市盈率PE Ratio是股票估值指标计算方式为股价 / 每股收益。, 国债逆回购是一种短期贷款个人通过国债回购市场把资金借出去获得固定利息。 ] embeddings HuggingFaceEmbeddings(model_nameall-MiniLM-L6-v2) vectorstore FAISS.from_texts(test_docs, embeddings) # 2. 初始化RAG系统 rag_system FinancialRAGSystem( vectorstorevectorstore, llm_agentfinancial_agent_with_test_runtime ) # 3. 执行测试查询 query 请解释一下市盈率 answer, sources rag_system.query(query, top_k1) # 4. 进行断言 - 这里不是精确匹配而是评估 # 断言1答案不为空 assert answer is not None and len(answer) 10 # 断言2答案中应包含核心概念关键词规则评估 assert any(keyword in answer for keyword in [股价, 每股收益, 估值, 比率]) # 断言3检索到了相关来源 assert len(sources) 0 assert 市盈率 in sources[0] # 来源内容应包含查询词 # 断言4可以加入基于LLM的评估可选成本高 # 例如调用一个评估函数用另一个LLM判断答案相关性 # evaluation_score llm_evaluator.evaluate_relevance(query, answer) # assert evaluation_score 0.7 pytest.mark.integration def test_agent_response_format(self, financial_agent_with_test_runtime): 测试Agent的响应格式和基本能力 agent financial_agent_with_test_runtime # 测试一个定义明确、知识库中存在的术语 result agent.process_and_format(国债逆回购) # 验证返回的结构 assert isinstance(result, dict) assert set(result.keys()) {term, explanation, key_point} assert result[term] 国债逆回购 # 验证解释不为空且长度合理 assert 20 len(result[explanation]) 500 # 关键点应该是解释的第一句话或摘要 assert len(result[key_point]) len(result[explanation])4.3 利用评估器进行自动化验证对于更复杂的输出我们需要评估器。可以自己实现一个简单的规则评估器。# evaluators.py class RuleBasedEvaluator: staticmethod def evaluate_answer_contains_keywords(answer: str, mandatory_keywords: list, forbidden_keywords: list None) - dict: 基于关键词的规则评估 返回一个包含分数和详细结果的字典 score 0 details {} # 检查必须包含的关键词 found_keywords [] for kw in mandatory_keywords: if kw in answer: score 1 found_keywords.append(kw) details[mandatory_keywords_found] found_keywords details[mandatory_keywords_missing] list(set(mandatory_keywords) - set(found_keywords)) # 检查禁止包含的关键词 if forbidden_keywords: forbidden_found [kw for kw in forbidden_keywords if kw in answer] if forbidden_found: score 0 # 一旦出现禁止词直接0分 details[forbidden_keywords_found] forbidden_found # 简单计算一个比例分数 total_possible len(mandatory_keywords) normalized_score score / total_possible if total_possible 0 else 1.0 return { score: normalized_score, details: details, passed: normalized_score 0.7 # 假设70%为通过线 } staticmethod def evaluate_json_format(answer: str) - dict: 评估输出是否为合法JSON import json try: parsed json.loads(answer) return {score: 1.0, details: {is_valid_json: True, parsed_keys: list(parsed.keys())}, passed: True} except json.JSONDecodeError as e: return {score: 0.0, details: {is_valid_json: False, error: str(e)}, passed: False} # 在集成测试中使用 def test_rag_with_evaluator(rag_system): test_cases [ { query: 什么是CPI, mandatory_keywords: [消费者, 价格, 指数, 通胀], forbidden_keywords: [我不知道, 无法回答] }, # ... 更多测试用例 ] all_passed True for tc in test_cases: answer, _ rag_system.query(tc[query]) eval_result RuleBasedEvaluator.evaluate_answer_contains_keywords( answer, tc[mandatory_keywords], tc.get(forbidden_keywords) ) if not eval_result[passed]: print(f测试失败 - 问题{tc[query]} 评估结果{eval_result}) all_passed False assert all_passed, 部分测试用例未通过评估集成测试小结这一层测试确保了各个模块能协同工作。通过使用轻量级模型和内存数据库我们可以在可接受的时间和成本内频繁运行集成测试及时捕获接口变更或模型行为漂移带来的问题。5. 性能与稳定性测试压测SGLang运行时SGLang的核心卖点是性能因此性能测试必不可少。我们需要验证在压力下应用是否仍能保持稳定的吞吐和低延迟。5.1 使用Locust进行负载测试我们可以模拟多个并发用户向我们的FastAPI服务封装了SGLang Agent发送请求。# locustfile.py from locust import HttpUser, task, between import json class SGLangLLMUser(HttpUser): wait_time between(0.5, 2) # 用户等待时间 task def query_financial_term(self): 测试查询金融术语 payload { term: 量化宽松 } headers {Content-Type: application/json} # 假设我们的服务端点 /explain with self.client.post(/explain, jsonpayload, headersheaders, catch_responseTrue) as response: if response.status_code 200: data response.json() # 可以添加一些业务逻辑验证比如检查返回字段是否存在 if explanation not in data: response.failure(Missing explanation field) else: # 记录响应时间Locust会自动统计 pass else: response.failure(fStatus code: {response.status_code}) task(3) # 权重为3更频繁地执行 def query_rag(self): 测试RAG问答 payload { question: 美联储加息对股市有什么影响 } headers {Content-Type: application/json} with self.client.post(/rag/query, jsonpayload, headersheaders, catch_responseTrue) as response: if response.status_code 200: data response.json() if answer not in data: response.failure(Missing answer field) else: response.failure(fStatus code: {response.status_code})运行Locustlocust -f locustfile.py并模拟上百个并发用户观察吞吐量RPS每秒成功处理的请求数。平均响应时间、P95/P99响应时间特别是首个Token的生成时间Time to First Token, TTFT和整个生成的耗时。错误率HTTP 5xx或业务逻辑错误的比例。5.2 监控SGLang运行时指标在压力测试期间同时监控SGLang运行时的内部状态如果框架暴露了相关指标和系统资源GPU利用率使用nvidia-smi观察是否达到瓶颈。GPU内存观察KV缓存的使用情况防止OOM。批处理大小SGLang的连续批处理是否生效平均每个批次处理多少个请求缓存命中率如果使用了RadixAttention观察其对于重复提示前缀的缓存复用效率。5.3 混沌测试模拟故障LLM应用对外部服务模型API、向量数据库的依赖很强必须有容错机制。# test_chaos.py import pytest from unittest.mock import patch from your_app import FinancialAgent import openai # 假设底层使用OpenAI API class TestChaos: def test_llm_api_timeout_and_retry(self): 测试LLM API超时后的重试机制 agent FinancialAgent() # 模拟第一次调用超时第二次调用成功 with patch(your_app.openai.ChatCompletion.create) as mock_create: # 第一次调用抛出超时异常 mock_create.side_effect [ openai.error.Timeout, # 第一次模拟超时 MockChoices(这是一个模拟的成功响应) # 第二次模拟成功 ] # 这里应该触发我们业务代码中的重试逻辑 result agent.explain_term_with_retry(测试, max_retries3) # 验证重试了mock被调用了两次 assert mock_create.call_count 2 # 验证最终得到了结果 assert 模拟 in result def test_fallback_to_cheaper_model(self): 测试当主模型失败时是否降级到备用模型 # 模拟主模型如GPT-4调用失败 # 验证业务逻辑是否自动切换到了备用模型如Qwen-7B # 并验证降级后的响应质量是否在可接受范围内 pass性能与稳定性测试小结这部分测试保障了应用上线后能扛住真实流量并且在出现局部故障时不会整体崩溃。对于SGLang应用要特别关注其在高并发下的KV缓存管理和内存使用情况。6. 持续集成与部署流水线将上述所有测试自动化并集成到CI/CD流水线中如GitHub Actions, GitLab CI。# .github/workflows/test.yml name: LLM App CI on: [push, pull_request] jobs: test: runs-on: [self-hosted, gpu] # 建议使用带GPU的自托管Runner运行集成测试 steps: - uses: actions/checkoutv3 - name: Set up Python uses: actions/setup-pythonv4 with: python-version: 3.10 - name: Install dependencies run: | pip install -r requirements.txt pip install -r requirements-test.txt # 测试专用依赖 - name: Download test model run: | # 下载一个用于集成测试的小模型例如Qwen1.5-1.8B-Chat的GGUF文件 wget -O ./models/test_model.gguf https://huggingface.co/.../qwen1.5-1.8b-chat-q4_k_m.gguf - name: Run unit tests run: | pytest tests/unit -v --tbshort - name: Run integration tests (with small model) run: | # 标记为integration和slow的测试可能需要GPU pytest tests/integration -m integration and not slow -v --tbshort - name: Run slow integration tests (optional, scheduled) if: github.event_name push github.ref refs/heads/main run: | # 仅在主分支推送时运行更全面的慢测试 pytest tests/integration -m slow -v --tbshort - name: Run security and style checks run: | bandit -r ./src black --check ./src ./tests对于每次Pull Request自动运行单元测试和快速的集成测试。对于合并到主分支的代码可以额外运行更耗时的性能测试和基于GPT-4的深度评估。7. 常见问题与排查技巧实录在实际开发和测试中我遇到了不少典型问题这里分享一些排查思路。问题1集成测试时SGLang Runtime初始化失败报错CUDA out of memory。原因测试环境GPU内存不足或者模型加载配置如max_total_token_num设置过高。排查检查测试用的模型是否过大。集成测试务必使用量化后的小模型如Qwen-1.8B-Chat的q4_k_m版本。在Runtime初始化时显式设置较小的max_total_token_num例如4096限制KV缓存大小。考虑在CPU上运行集成测试。虽然慢但能保证稳定性。可以通过环境变量控制device cuda:0 if torch.cuda.is_available() else cpu。问题2单元测试中Mock了SGLang但实际运行时代码逻辑变了Mock没更新导致测试通过但实际失败。原因Mock过于粗粒度没有覆盖函数签名或内部调用的变化。技巧使用autospecTrue参数创建Mock可以自动从原始对象复制规格确保Mock与被Mock对象有相同的接口。mock_runtime Mock(specsgl.Runtime, autospecTrue)定期比如每次重构后在不Mock的情况下运行一两次集成测试作为“冒烟测试”验证核心路径是否依然畅通。问题3评估器Evaluator的规则过于死板导致好的答案被误判失败。原因规则评估依赖于精确关键词但LLM的回答同义词丰富。解决使用词干提取或同义词扩展。例如评估“上涨”时也接受“增长”、“攀升”等。结合嵌入向量相似度。将期望关键词和实际答案都转化为向量计算余弦相似度设定阈值。分层评估核心术语必须精确匹配如“市盈率”而描述性语句则采用相似度评估。问题4性能测试中随着并发增加吞吐量并没有线性增长甚至下降。原因可能是GPU计算瓶颈、内存带宽瓶颈或者是SGLang的批处理/调度策略达到极限。排查使用nvtop或nvidia-smi dmon监控GPU的sm流处理器利用率和内存带宽。如果sm利用率低可能是计算不是瓶颈而是内存IO或调度问题。调整SGLang的max_batch_size和max_sequence_length参数找到当前硬件和模型下的最优值。检查提示词长度是否差异过大导致批处理效率低下因为需要padding。SGLang的RadixAttention对此有优化但仍需观察。问题5提示词微调后评估分数反而下降了。原因过拟合了少量测试用例或者引入了歧义。流程A/B测试将新旧提示词在更大的、未参与训练的评估集上跑一遍。错误分析手动检查分数下降的案例看新提示词在哪里出了问题。是语气变了还是漏掉了关键指令提示词版本回滚Git管理的好处立刻体现可以轻松回退到上一个稳定版本。为LLM应用构建测试体系尤其是围绕SGLang这样的高性能框架是一个从“艺术”走向“工程”的过程。它开始可能显得繁琐但一旦建立起自动化的测试流水线你就会发现迭代速度和质量都有了质的飞跃。你不再需要手动点一遍所有功能也不再害怕修改提示词。每一次提交都有测试守护这给了在复杂非确定性系统中前进的底气。