
计算机界有很多传奇人物但能同时深度影响操作系统、编程语言、文本处理工具链又对“人工智能”这个热门话题保持清醒甚至略带怀疑态度的Ken Thompson 一定是绕不开的一位。作为 Unix 的共同创造者、B 语言的作者、Go 语言的联合设计者、图灵奖得主他的很多观点都被开发者反复引用其中关于“什么是人工智能”的表述放在今天大模型、AI Agent 全面爆发的背景下依然有很强的现实意义。本文并不是一篇人物传记而是想以 Ken Thompson 的工程哲学为线索聊一聊传统软件工程和 AI 工程之间的本质差异并结合当前 AI 应用开发过程中的模型部署、AI Agent、AI 幻觉等热门话题给出一些可落地的工程建议和代码示例。适合正在做 AI 应用落地、关注 AI 工程实践或者想从“确定性编程”走向“概率性编程”的开发者阅读。1. 背景与核心概念1.1 Ken Thompson 是谁Ken Thompson 是贝尔实验室的传奇工程师1969 年与 Dennis Ritchie 一起创造了 Unix 操作系统后来发明了 B 语言推动了 C 语言的诞生。他还参与设计了 Go 语言并且在正则表达式、自动机理论、文本搜索算法等领域做出了基础性贡献。1983 年他因“对通用操作系统理论的发展特别是 Unix 操作系统的实现”获得图灵奖。如果要概括他的工程哲学大概可以归结为几条简单、明确、可组合的模块胜过复杂黑盒。工具要能解决真实问题而不是制造新的问题。系统应该透明开发者应该能理解它为什么这样运行。少即是多很多复杂需求其实是被过度设计出来的。这套哲学直接影响了 Unix 的“一切皆文件”思想也影响了 grep、awk、sed 这类小而锋利的工具。理解这一点再去看他对 AI 的态度就会觉得“合理但又有启发”。1.2 广为流传的 AI 观点在不少访谈中Ken Thompson 表达过对“人工智能”这个词的保留态度。其中一句流传较广的说法大意是AI 这个词通常用来指那些“还没成功实现”的东西。一旦某项技术真正能解决问题了人们就不再叫它 AI 了。这个说法虽然不是严格的学术定义也不是他唯一一次关于 AI 的表态但非常能够代表老派系统工程师看待 AI 的视角一项技术是否成熟不取决于它叫什么名字而取决于它能不能稳定、可靠、可解释地解决实际问题。从这个角度出发我们可以把 Ken Thompson 的观点理解成一种“工程意义上的祛魅”大模型再强大也只是软件系统中的一个组件它应该遵循工程规律而不是被当成一个能包办一切的魔法黑盒。1.3 为什么今天重新讨论这个话题2023 年以来大语言模型迅速进入应用开发领域AI Agent、RAG检索增强生成、模型微调、本地化部署成为高频词汇。但很多项目在落地时都遇到相似的困境模型回答不稳定、推理成本不可控、输出格式不统一、安全边界模糊。这时候重新看 Ken Thompson 的观点其实是在提醒我们AI 应用开发不能只关注模型能力更要关注“系统整体”的确定性、可观测性和容错性。换句话说把大模型当作一个“极聪明但不一定可靠的实习生”来管理可能比把它当作“全知全能的神”更接近工程现实。2. 环境准备与版本说明后文我会给出一个简单的 AI 工程示例用来演示“确定性工程 非确定性模型”的结合方式。下面先说明示例环境方便你本地复现。2.1 运行环境操作系统Windows 10/11、macOS 或 Linux 均可。Python 版本3.9 及以上建议 3.10。依赖库requests、pydantic。示例中调用大模型接口的部分我以 OpenAI 兼容接口为例。当前很多模型服务商和本地部署框架如 vLLM、Ollama都提供 OpenAI 兼容的 HTTP 接口所以这套代码可以平滑迁移。如果你使用的是国内的大模型服务也可以按照官方文档把 base_url 和 api_key 替换掉。2.2 目录结构ai_engineering_demo/ ├── main.py ├── llm_client.py └── validator.pymain.py主流程演示调用链。llm_client.py封装模型 HTTP 调用。validator.py对模型输出做规则校验与兜底。2.3 安装依赖pip install requests pydantic版本说明pydantic 2.x 和 2.x 以下版本 API 略有差异老项目如果用的是 1.x示例中model_dump()需要改成.dict()。建议新项目直接用 pydantic 2.x。3. 传统软件工程与 AI 工程的差异3.1 确定性代码预期明确传统软件开发的核心是“确定性逻辑”。给定同样的输入函数应当返回同样的输出。比如下面的 Python 函数def calculate_discount(price: float, rate: float) - float: if price 0: raise ValueError(price cannot be negative) if not 0 rate 1: raise ValueError(rate must be between 0 and 1) return price * rate这段代码的行为是可以精确预测的传入100, 0.8一定返回80.0。传入非法参数一定抛异常。开发者可以放心地把它嵌入业务系统并依赖它的结果去做后续决策。确定性代码的优势非常明显可测试、可调试、可审计。任何一个分支都可以写单测覆盖任何一个异常都可以在日志中明确复现。3.2 概率性模型输出不确定大模型的推理过程本质上是概率性的。同样一个问题模型可能在不同时间给出不同答案同一个 prompt 稍作措辞调整输出也可能跟着变化。这种特性在对话场景中是优势但在业务系统中却是巨大的挑战。举个例子如果你让模型从一段用户输入中抽取“姓名、手机号、地址”三个字段模型可能这一次返回 JSON下一次返回纯文本这一次字段名是name下一次变成full_name。这种不稳定性会让下游解析代码崩溃也会让测试工作变得非常困难。下面是模型输出不稳定时的一个典型 JSON 解析失败场景import json model_output 姓名张三\n手机号13800138000\n地址北京市朝阳区 # 假设我们期望 model_output 是 JSON直接解析 try: data json.loads(model_output) print(data) except json.JSONDecodeError as e: print(解析失败:, e)输出解析失败: Expecting value: line 1 column 1 (char 0)这是 AI 工程中最常见的问题之一模型输出格式不符合预期。很多没有工程化经验的团队会把希望寄托在“多试几次”或者“换个模型”上但根本解法是在模型外面套一层“确定性约束”。3.3 从 Ken Thompson 的视角看差异如果用 Ken Thompson 的工程哲学来审视问题就变得清晰了大模型是一个“子系统”而任何子系统都必须保证对外接口的稳定契约。你不能因为模型很厉害就放弃系统层面的边界管理。换句话说模型负责“理解语义、生成内容”这是它的强项。系统负责“格式化输出、校验字段、处理异常、记录日志”这是传统工程的强项。把两者拆开各司其职才是 AI 工程落地的正确思路。这也是我后面代码示例的核心思想。4. 详解 AI 工程中的核心问题4.1 模型部署与本地化AI 应用开发绕不开模型部署。大体上有两条路线一是直接调用云服务商的 API。优点是接入成本低不用管 GPU 资源缺点是数据要经过公网部分企业不允许敏感数据出域。二是本地化部署开源模型。推荐使用 vLLM 或 Ollama它们都提供 OpenAI 兼容接口可以很方便地接入现有代码。示例命令如下以 Ollama 为例# 安装完成后启动 Ollama 服务 ollama serve # 拉取并运行模型这里以 qwen2.5:7b 为例 ollama run qwen2.5:7b本地部署的优势是数据不出内网模型可以在离线环境下运行缺点是推理速度受硬件影响如果 GPU 显存不足7B 模型也可能跑不起来。4.2 AI Agent 的工程边界AI Agent 是当前最热门的应用方向之一本质上是让模型具备“感知、决策、行动”的循环能力。但在工程上Agent 系统比单个模型调用复杂得多模型每轮决策都是概率性的错误会不断累积。Agent 可以调用外部工具工具权限如果不能精细控制容易越权。长链路任务容易出现“幻觉步骤”即模型会编造一个不存在的 API 或文件路径。Ken Thompson 的“信任但要验证”思想在 Agent 场景下尤其适用。你不能默认 Agent 调用工具的结果是可靠的每一层返回都应该做校验、日志和熔断。4.3 AI 幻觉最大的确定性敌人AI 幻觉Hallucination指的是模型生成看似合理但实际不正确的内容。比如让模型总结一份文档它可能凭空多出一段原文中不存在的内容让模型写代码它可能使用一个并不存在的函数。幻觉本质上源于大模型的语言建模目标它只负责预测“下一个最可能的词”并不负责判断“事实是否成立”。所以工程上降低幻觉的手段通常是用 RAG 把相关的检索结果注入 prompt让模型尽量基于给定资料回答。对输出做事实校验关键数据必须和源数据比对。设置温度参数为 0 或低值降低随机性。低温度参数示例response client.chat.completions.create( modelqwen2.5:7b, messages[{role: user, content: 请用一句话介绍你自己}], temperature0.1 )5. 完整实战案例一个带确定性保障的 AI 调用下面我们来做一个有实际意义的示例让模型从一段文本中抽取联系人信息并用规则层保证输出一定是合法 JSON字段一定齐全类型一定正确。这个示例的设计思路是用模型做信息抽取非确定性部分。用 pydantic 和正则做格式校验确定性部分。校验失败时自动重试一次再失败则返回结构化错误容错部分。这正好体现了“模型负责聪明系统负责可靠”的工程理念。5.1 模型调用封装文件llm_client.pyimport requests class LLMClient: 一个简单的 OpenAI 兼容接口封装 def __init__(self, base_url: str, api_key: str, model: str): self.base_url base_url.rstrip(/) self.api_key api_key self.model model def chat(self, prompt: str, temperature: float 0.1) - str: url f{self.base_url}/v1/chat/completions headers { Authorization: fBearer {self.api_key}, Content-Type: application/json, } payload { model: self.model, temperature: temperature, messages: [ {role: system, content: 你是一个信息抽取助手只输出 JSON不要输出任何多余说明。}, {role: user, content: prompt}, ], } resp requests.post(url, jsonpayload, headersheaders, timeout60) resp.raise_for_status() data resp.json() return data[choices][0][message][content]5.2 输出校验器文件validator.pyimport json import re from typing import Optional from pydantic import BaseModel, field_validator class ContactInfo(BaseModel): 联系人信息结构 name: str phone: str address: str field_validator(phone) def validate_phone(cls, v: str) - str: if not re.match(r^1\d{10}$, v): raise ValueError(手机号格式不正确) return v class OutputValidator: 把模型输出转换成结构化对象失败时抛出异常 staticmethod def parse(text: str) - ContactInfo: # 去掉可能的 Markdown 包裹 text text.strip() text re.sub(r^(json)?, , text).strip() text re.sub(r$, , text).strip() data json.loads(text) return ContactInfo(**data)OutputValidator做了两件事第一把模型输出从文本转成 JSON第二用 pydantic 模型做字段和类型校验。如果模型输出不合法这里会直接抛异常上游可以感知到问题。5.3 主流程文件main.pyimport json from llm_client import LLMClient from validator import OutputValidator def extract_contact_with_retry(client: LLMClient, text: str, max_retry: int 2): prompt f 请从下面的文本中抽取联系人信息并严格按照 JSON 格式输出。 要求字段 - name: 姓名 - phone: 11 位手机号 - address: 地址 文本 {text} last_error None for attempt in range(max_retry): try: raw client.chat(prompt) contact OutputValidator.parse(raw) return contact except Exception as e: last_error e print(f第 {attempt 1} 次解析失败: {e}) # 重试失败返回结构化错误 raise RuntimeError(f模型输出无法解析: {last_error}) if __name__ __main__: # 配置你的模型服务 client LLMClient( base_urlhttp://localhost:11434, api_keyollama, modelqwen2.5:7b ) sample_text 我叫李四手机号是13800138000住在杭州市西湖区文三路100号请帮我记一下。 try: result extract_contact_with_retry(client, sample_text) print(抽取成功, result.model_dump()) except Exception as e: print(最终错误, e)5.4 运行与验证如果你本地已经通过 Ollama 启动了模型服务直接运行python main.py预期输出因为模型输出存在一定随机性字段内容应保持一致抽取成功 {name: 李四, phone: 13800138000, address: 杭州市西湖区文三路100号}如果模型返回了无法解析的内容程序会在重试后输出错误信息而不会带着脏数据继续执行。这就是确定性保障的价值宁可让流程失败也不要让坏数据进入下游。5.5 结果说明从上面的代码可以看到真正“聪明”的部分只有llm_client.py里的模型调用而校验、纠错、重试、结构化这些关键工程逻辑全部在传统代码里完成。这就是 Ken Thompson 式工程思想的落地不要信任模型的输出格式要相信系统的校验能力。6. 常见问题与排查思路AI 应用开发中很多问题看起来是“模型不行”实际上可能是工程链路的问题。下面整理几个高频场景并给出排查思路。问题现象常见原因解决思路模型返回内容无法解析为 JSONprompt 缺少格式约束或模型本身格式能力弱在 prompt 中给示例使用支持 JSON mode 的接口参数用规则清洗 Markdown 包裹JSON 字段名不稳定模型没有强约束每次推理可能变换字段名不要直接使用原始输出用 pydantic 做字段映射与校验手机号被模型改写模型在“尽力纠正”你的数据对关键业务字段建立规则白名单不让模型自由发挥本地模型响应太慢显存不足、推理参数配置过高降低 max_tokens、使用量化版本、增大 batch 或换 GPU中文内容出现乱码终端编码、HTTP 响应编码不一致确认终端 UTF-8requests 中设置resp.encoding utf-8调用云 API 报 429触发限流增加指数退避重试避免固定频率反复请求这里额外强调一个常见误区有的同学在 prompt 里写了“必须返回 JSON”但模型仍然返回了带解释的文字。这不一定是模型不听话而是 prompt 本身没有给出足够的“格式锚点”。可以在 system prompt 中给一个最小示例比如只输出 JSON示例格式 {name: 张三, phone: 13800138000, address: 北京市}大多数情况下给出示例比单纯强调“必须 JSON”更有效。7. AI 工程最佳实践与工程建议7.1 模型调用层统一封装调用接口所有模型请求走同一个客户端。对超时、限流、网络错误做重试但重试次数要有限制通常 2 到 3 次足够。不要在业务代码里散落 prompt把所有 prompt 收敛到独立的 prompt 管理模块。设置合理的 temperature偏向确定性任务时建议 0 到 0.2。7.2 输出校验层始终对模型输出做结构化校验不要默认它是合法 JSON。关键业务字段要建立规则校验比如手机号、邮箱、金额。校验失败时不要拼凑数据应该返回明确错误让上层流程感知。7.3 安全与权限涉及权限场景时遵循最小权限原则AI Agent 只需要读取数据的权限绝不额外授予删除、导入等危险权限。模型输出的内容可能包含错误或误导信息关键决策必须有人工审核环节。在生产环境使用模型 API 时敏感数据要做脱敏处理避免把用户隐私原样发送到模型服务。7.4 可观测性每一次模型请求都记录输入 prompt、模型输出、解析结果、耗时。对解析失败做独立的错误统计方便定位是模型问题还是校验规则问题。建议保留一个专用的“回归测试集”每次更换模型或调整 prompt 后跑一遍确保抽取精度没有下降。工程化 AI 应用的核心不是让模型变强而是让整个系统在模型“偶尔犯错”的情况下依然保持可预测、可控制、可恢复。8. 对 AI 开发者的实用心态建议聊完技术细节再回到 Ken Thompson 那句广为流传的话。与其把它理解成“否定 AI”不如把它理解成一种冷静的工程判断凡是能稳定解决实际问题的技术最终都会沉淀为普通工具不再是噱头。今天的编译器和数据库在几十年前同样被认为是“人工智能”只是后来它们太可靠了反而没有人再提“AI”这个词。凡是不能稳定解决问题的方案无论包装得多像 AI都会在工程落地时暴露问题。所以做 AI 应用开发时不要因为模型能力强大就放松对系统工程质量的要求。对开发者来说最重要的能力不是“把大模型的回答直接转发给用户”而是“如何设计好模型与业务系统之间的边界”。这个边界包括数据结构、错误处理、权限控制、成本管理、模型替换策略。当你把这些问题想清楚之后大模型就真正从“玄学”变成了工程组件。9. 小结与下一步学习方向这篇文章从 Ken Thompson 对 AI 的观点切入结合 Unix 时代沉淀下来的工程哲学聊了传统软件工程与 AI 工程的本质区别并用一个完整示例演示了“模型负责生成、系统负责校验”的落地方式。如果你正在学习 AI 开发下一步建议按这个顺序深入先把基础工程能力打牢HTTP 调用、JSON 解析、pydantic 校验、异常处理。理解 RAG检索增强生成的原理和实现这是目前企业应用中最实用的方向之一。学习模型本地部署比如 Ollama、vLLM至少知道量化、显存、并发这些概念。再去看 AI Agent但要把重点放在工具调用、权限边界、多步任务状态管理上。如果你的目标是做企业级 AI 应用优先关注的不是“哪个模型更强”而是“模型不可靠时系统怎么办”。把这一步想明白比再换一个新模型有价值得多。Ken Thompson 那一代工程师用“简简单单的确定性系统”构建了整个计算机时代的基础。今天的 AI 应用开发也应该在拥抱大模型能力的同时保留这种对确定性、可验证性、可维护性的坚持。毕竟工程的核心不是写出“看起来智能”的代码而是写出“长期可维护”的系统。