从Prompt到Harness:三年间,我们驾驭大模型的方式经历了怎样的进化?

引言:驾驭方式的变迁三年前,当大语言模型(LLM)如GPT-3刚刚进入公众视野时,我们与它们交互的方式几乎完全依赖“Prompt”——一段精心设计的文本提示。那时,Prompt engineering(提示工程)被奉为魔法般的技术,人们绞尽脑汁设计模板、优化措辞、添加示例,只为从模型中获得一个满意的回答。然而,随着模型能力的爆发式增长和应用场景的复杂化,单纯依赖Prompt的局限性日益凸显:输出不可控、上下文长度受限、无法与外部系统交互、难以实现多步骤推理……于是,从Prompt到Harness(束具/框架)的进化悄然发生。Harness在这里代表一种更结构化的驾驭方式——通过程序化框架、工具调用、记忆管理、错误处理等机制,将LLM从“回答者”转变为“执行者”。本文将深入剖析这一进化背后的原理,并通过可运行的代码示例,展示从原始Prompt到现代Harness的实践演进。## 第一阶段:Prompt时代的艺术与局限### 原理剖析Prompt engineering的核心是“通过自然语言指令引导模型生成期望输出”。其原理基于模型的预训练知识:模型在大量文本中学习到了模式、语法和常识,因此精心设计的提示可以激活特定区域的“知识”。例如,加入“请用JSON格式输出”可以触发模型的结构化生成能力。但Prompt依赖于模型的“零样本”或“少样本”能力,缺乏对输出格式的硬性约束,也无法保证逻辑一致性。下面是一个典型的Prompt示例:python# 示例1:纯Prompt方式,依赖模型理解指令import openai# 假设已配置openai.api_keyresponse = openai.ChatCompletion.create( model="gpt-3.5-turbo", messages=[ {"role": "system", "content": "你是一个天气助手。请用JSON格式返回结果。"}, {"role": "user", "content": "北京今天天气怎么样?"} ])# 解析模型输出(可能失败,因为模型可能不遵循格式)raw_output = response['choices'][0]['message']['content']try: import json weather_data = json.loads(raw_output) print(f"温度: {weather_data.get('temperature')}")except json.JSONDecodeError: print("模型输出不是有效JSON,需要手动处理:", raw_output)局限性:模型可能返回“北京今天晴,温度25°C”这样的自然语言,导致JSON解析失败。没有错误处理机制,输出不可靠。## 第二阶段:结构化生成的萌芽——Function Calling### 原理剖析2023年中,OpenAI推出了Function Calling功能,这是从Prompt到Harness的关键转折点。Function Calling允许开发者定义一组可调用的函数(包含参数和描述),模型在推理时会自主选择是否需要调用函数,并生成符合函数签名的结构化参数。这本质上是将“输出格式控制”从Prompt中剥离,交给模型内置的推理引擎。其原理是:模型在生成过程中,除了输出文本,还会输出一个“函数调用”的特殊token序列,指示它想要调用哪个函数以及参数。开发者可以捕获这个信号,执行实际函数,并将结果返回给模型继续对话。python# 示例2:使用Function Calling实现可靠的天气查询import openaiimport json# 定义可调用的函数functions = [ { "name": "get_weather", "description": "获取指定城市的天气信息", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名称,如'北京'" } }, "required": ["city"] } }]# 模拟真实天气APIdef get_weather(city): # 实际中应调用API weather_db = {"北京": {"temperature": 25, "condition": "晴"}} return weather_db.get(city, {"temperature": "未知", "condition": "未知"})response = openai.ChatCompletion.create( model="gpt-3.5-turbo", messages=[ {"role": "user", "content": "北京今天天气怎么样?"} ], functions=functions, function_call="auto" # 让模型自主决定是否调用)message = response['choices'][0]['message']# 检查是否触发了函数调用if message.get("function_call"): func_name = message["function_call"]["name"] arguments = json.loads(message["function_call"]["arguments"]) if func_name == "get_weather": result = get_weather(arguments["city"]) # 将函数结果返回给模型,让模型生成自然语言回答 second_response = openai.ChatCompletion.create( model="gpt-3.5-turbo", messages=[ {"role": "user", "content": "北京今天天气怎么样?"}, message, {"role": "function", "name": "get_weather", "content": json.dumps(result)} ] ) print(second_response['choices'][0]['message']['content'])else: print("模型未调用函数,直接回答:", message['content'])进化点:模型不再直接输出天气数据,而是通过函数调用获取可靠信息。输出变得可控,且能与外部系统交互。## 第三阶段:Harness框架的诞生——LangChain与工具链### 原理剖析Function Calling解决了“单次调用”问题,但真实应用需要多步推理、记忆、错误重试、工具组合等复杂场景。于是,Harness框架(如LangChain)应运而生。Harness的核心思想是:将LLM封装在程序化执行环境中,通过链(Chain)、代理(Agent)、工具(Tool)等抽象,实现可组合、可调试的AI工作流。其底层原理包括:- ReAct模式:让模型交替进行“思考”(Reasoning)和“行动”(Action),通过循环实现多步推理。- 记忆管理:将对话历史、中间结果存储在外部(如向量数据库),突破上下文长度限制。- 工具抽象:将API、数据库、计算器等统一为工具接口,模型通过选择工具来完成任务。下面是一个使用LangChain实现的多步推理示例:python# 示例3:使用LangChain Harness实现多步推理与工具调用from langchain.agents import initialize_agent, Toolfrom langchain.llms import OpenAIfrom langchain.memory import ConversationBufferMemoryfrom langchain.tools import BaseToolfrom typing import Optional, Typefrom pydantic import BaseModel, Field# 定义自定义工具:计算器class CalculatorInput(BaseModel): expression: str = Field(description="需要计算的数学表达式,如'3+5'")class CalculatorTool(BaseTool): name = "calculator" description = "用于执行数学计算,输入应为数学表达式" args_schema: Type[BaseModel] = CalculatorInput def _run(self, expression: str) -> str: try: result = eval(expression) return f"计算结果: {result}" except Exception as e: return f"计算错误: {str(e)}"# 定义知识查询工具(模拟)class KnowledgeTool(BaseTool): name = "knowledge_base" description = "查询常识知识,输入为问题" def _run(self, query: str) -> str: knowledge = { "光速": "光速约为3×10^8米/秒", "地球质量": "地球质量约为5.97×10^24千克" } return knowledge.get(query, "未找到相关信息")# 初始化LLM和工具llm = OpenAI(model_name="gpt-3.5-turbo", temperature=0)tools = [CalculatorTool(), KnowledgeTool()]memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)# 创建Agent(Harness的核心)agent = initialize_agent( tools, llm, agent="conversational-react-description", # 使用对话式ReAct模式 verbose=True, # 显示推理过程 memory=memory, max_iterations=3 # 限制推理步数)# 执行多步任务query = "光速的三次方是多少?先查光速,然后计算"response = agent.run(query)print(f"最终回答: {response}")输出示例(verbose模式下):> Entering new AgentExecutor chain...Thought: 我需要先查询光速,然后计算其三次方。Action: knowledge_baseAction Input: 光速Observation: 光速约为3×10^8米/秒Thought: 现在我需要计算(3×10^8)^3Action: calculatorAction Input: (3*10**8)**3Observation: 计算结果: 2.7e+25Thought: 我得到了计算结果Final Answer: 光速的三次方约为2.7×10^25米/秒的三次方。> Finished chain.进化点:模型不再直接回答,而是按照“查询知识→计算→整合”的流程执行。Harness提供了记忆、错误处理(如计算失败可重试)、多工具组合等能力,使LLM真正成为“AI代理”。## 第四阶段:未来展望——从Harness到Orchestrator当前,Harness框架仍在快速进化。下一代趋势是将Harness升级为Orchestrator(编排器),实现更复杂的任务分解、并行执行、动态规划。例如,AutoGPT和BabyAGI这类项目让模型自主生成子任务并递归执行,本质上是一个自我驱动的Harness。核心挑战在于:如何平衡模型自主性与可控性?如何在长链推理中避免错误累积?这需要更精细的监控、回溯和验证机制。未来,我们可能会看到专门用于Harness的编程语言(如DSPy),将模型调用视为一种“函数”,由编译器自动优化。## 总结从Prompt到Harness的进化,本质是**从“让模型理解指令”到“让模型执行程序”**的范式转移。Prompt时代,我们把模型当作黑盒,用自然语言小心翼翼地试探;Harness时代,我们为模型穿上了“外骨骼”——工具、记忆、流程控制,让它能系统地解决问题。这一进化路径揭示了AI工程化的重要规律:不要试图让模型完美,而是设计系统来弥补模型的不足。通过结构化的Harness,我们可以容忍模型偶尔的“幻觉”,通过错误重试、多轮验证来保证可靠性。三年间,我们驾驭大模型的方式从“祈祷它理解”变成了“设计它执行”,而这正是AI从实验走向工程的关键一步。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐