news 2026/8/29 4:19:13

Stone Soup AI:从最小骨架到工具调用的渐进式集成实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Stone Soup AI:从最小骨架到工具调用的渐进式集成实战

你大概听过“石头汤”的故事:几个穷困的旅人走到一个村庄,架起一口大锅,放一块石头进去煮水,说自己在做一锅美味的石头汤。路过的村民好奇,有人送来胡萝卜,有人送来土豆,有人送来几块肉。最后,大家真的喝上了一锅丰盛的汤。

2024 年之后,很多 AI 应用开发的演进方式,和这个故事越来越像:一开始只有一个大模型 API,看起来什么也做不了;但当你把它放在一个可扩展的骨架里,数据层、工具层、记忆层、评测层像“村民们”一样不断向锅里加料,最终长成一个完整、可用的 AI 系统。这就是本文要讲的Stone Soup AI(2024)工程思路:用最小骨架启动,靠渐进式集成把 AI 能力真正组合起来。

本文将围绕这套思路展开,先讲清楚 Stone Soup AI 是什么、它解决什么问题,然后带你把一个可运行的多模型对话骨架搭出来,再逐步加入工具调用、向量检索、Agent 等能力。适合正在做 AI 应用开发、想从“调 API”走向“做系统”的读者。

1. Stone Soup AI 是什么

1.1 从寓言到工程思想

“石头汤”这个故事的核心不是那块石头,而是协作与渐进:一个不起眼的最小起点,通过持续加入模块,最终变成远超单个部件价值的结果。

AI 开发中的 Stone Soup 思想可以这样理解:

  • 石头 = 大模型 API:GPT、Claude、通义、文心、DeepSeek 等,它们更像是“能说话的引擎”,不是完整产品。
  • 锅 = 应用骨架:负责接收请求、维护会话、调用模型、返回结果。
  • 不断加入的料 = RAG、工具调用、Agent、记忆、权限控制、评测、日志

换句话说,Stone Soup AI 是一种面向 AI 应用的渐进式集成方法论。它强调一开始不要追求“大而全”,而是先有一个能跑通的最小闭环,再通过可插拔模块把能力逐层补齐。

1.2 它与“传统软件开发”有什么不同

传统后端开发里,数据模型、接口、权限、缓存通常在一开始就要设计好,中途替换成本很高。但 AI 应用最大的特点是不确定性:

  • 模型能力在快速变化,今天用的模型明天可能被更强的替代。
  • 提示词、上下文策略、工具编排没有唯一标准答案。
  • 同一个任务,不同模型表现差异非常大。

Stone Soup AI 的思路是:把稳定部分做成接口和骨架,把不稳定部分做成可替换的“食材”。这样当模型升级、新的数据源接入、新的工具出现时,不需要推翻整口锅。

1.3 常见应用场景

场景用 Stone Soup 思路解决的问题
企业知识库问答先用大模型做对话,再逐步接入向量库、权限过滤、溯源引用
智能客服先跑通对话流程,再加入工具查询订单、退换货策略、人工接管
代码助手先做代码问答,再接入仓库索引、CI 状态、自动修改提交
Agent 应用先定义工具协议,再逐步注册更多可调用能力
内部效率工具先做一个统一对话入口,再连接各类业务系统

这篇文章里,我们会用实际代码演示这套思路的核心骨架。

2. 核心设计理念

在动手写代码之前,先把 Stone Soup AI 的四个核心设计理念说清楚。这些理念决定了项目结构怎么写、接口怎么定,也会帮助你以后理解市面上 AI 框架的设计动机。

2.1 最小可用骨架(Minimal Skeleton)

第一版代码只需要满足三个功能:

  1. 接收用户消息。
  2. 把消息发给某个大模型。
  3. 把模型回复返回给用户。

不要在第一版加入登录、向量库、多租户、监控告警。这些功能如果和骨架耦合在一起,后续很容易把系统越搞越重。最小骨架的价值在于:让业务方看到一条真实可用的链路,再决定优先加什么调料

2.2 接口先行,而非实现先行

Stone Soup AI 的核心是“可替换”。可替换的前提是抽象接口。下面这个例子是最核心的抽象:

# 模型网关接口(伪代码) class ModelGateway: def chat(self, messages, temperature=0.7, tools=None): """ 所有模型统一走这个方法。 输入统一是 OpenAI 风格的 message 列表。 输出统一是包含 role/content 或 tool_calls 的对象。 """ raise NotImplementedError

不管后端接的是 OpenAI、Claude、Azure OpenAI、本地部署的模型,还是各种国内大模型,只要外面套一层协议转换,上层应用代码就不需要改动。这样你换模型的时候,动的是“食材”,不是“锅”。

2.3 渐进式功能叠加(Incremental Enhancement)

先有基础对话,再加工具;先有工具,再做 Agent 循环;先有单轮,再维护多轮记忆;先有单知识库,再做多知识源路由。每加一个能力都要保证之前的功能依然正常运行。

这也是为什么最好有一个小的回归测试集:每次加“调料”之后,跑一遍冒烟用例,确认基础对话没有被破坏。

2.4 可观测与可控(Observability and Control)

AI 系统的黑盒特性决定了日志和开关非常重要。每个请求都要能追踪到:

  • 用户输入。
  • 模型名称与参数。
  • 上下文长度。
  • 工具调用过程。
  • 模型输出。
  • 消耗的 token。
  • 延迟。

同时要保留“开关”,比如在某个模型效果不佳时,能在配置中心一键切换。Stone Soup AI 的工程化程度,很大程度上取决于这些基础设施是否从一开始就预留好了位置。

3. 环境准备与项目结构

3.1 运行环境说明

本文的示例使用以下环境:

  • Python 3.10 及以上。
  • FastAPI 作为服务框架。
  • openai Python SDK 1.x,用于调用兼容 OpenAI 协议的模型服务。
  • uvicorn 用于启动服务。

需要注意:不同版本 SDK 在工具调用、消息对象序列化上有差异。示例代码展示的是整体工程思路,如果你的 SDK 版本或模型接口不通,优先查看对应版本的官方文档,把调用参数调整成你环境的真实写法。

3.2 安装依赖

建议先创建虚拟环境:

python -m venv venv source venv/bin/activate # Windows 下是 venv\Scripts\activate

然后安装依赖:

pip install fastapi uvicorn openai pydantic python-dotenv

如果你使用的是国内模型服务,只要它提供 OpenAI 兼容接口,通常可以通过设置 base_url 来对接。

3.3 项目目录

我们建立一个 demo 项目,目录结构如下:

stone-soup-ai/ ├── .env ├── requirements.txt ├── app/ │ ├── __init__.py │ ├── main.py # FastAPI 入口 │ ├── config.py # 配置读取 │ ├── models.py # 请求/响应数据结构 │ ├── gateway.py # 模型网关 │ └── tools.py # 工具注册中心

这个结构虽然简单,但已经体现了“骨架 + 可插拔模块”的分层思想。

4. 完整实战:从一锅清水开始

现在我们来写一段完整可运行的代码。先跑通一个纯对话版本,再向锅里加“工具调用”这一味关键调料。

4.1 配置管理

创建.env文件,把模型相关的配置放到这里,避免把密钥写死在代码里:

# .env LLM_API_KEY=your-api-key LLM_BASE_URL=https://api.openai.com/v1 LLM_MODEL=gpt-4o-mini

如果使用国内兼容 OpenAI 协议的服务,把LLM_BASE_URL改成你的服务地址,把LLM_MODEL改成实际模型名即可。

对应的配置读取模块:

# app/config.py import os from dotenv import load_dotenv load_dotenv() class Settings: api_key: str = os.getenv("LLM_API_KEY", "") base_url: str = os.getenv("LLM_BASE_URL", "https://api.openai.com/v1") model: str = os.getenv("LLM_MODEL", "gpt-4o-mini") settings = Settings()

4.2 定义请求数据结构

使用 Pydantic 定义接口的输入输出:

# app/models.py from typing import List, Optional from pydantic import BaseModel, Field class ChatMessage(BaseModel): role: str = Field(..., description="角色:system/user/assistant/tool") content: Optional[str] = Field(None, description="消息内容") name: Optional[str] = Field(None, description="工具调用时可带") class ChatRequest(BaseModel): messages: List[ChatMessage] temperature: float = 0.7 stream: bool = False class ChatResponse(BaseModel): reply: Optional[str] = None tool_calls: Optional[list] = None

这里把tool_calls放在响应结构里,是为后面 Agent 化预留位置。

4.3 模型网关

模型网关是“石头汤”里最重要的一层抽象。后续接任何模型,都先把协议转换成 OpenAI 兼容格式:

# app/gateway.py from openai import OpenAI from app.config import settings class ModelGateway: def __init__(self): self.client = OpenAI( base_url=settings.base_url, api_key=settings.api_key, ) self.model = settings.model def chat(self, messages, temperature=0.7, tools=None): kwargs = {} if tools: kwargs["tools"] = tools response = self.client.chat.completions.create( model=self.model, messages=messages, temperature=temperature, **kwargs, ) return response.choices[0].message

gateway.chat()返回的是 SDK 的 message 对象,它包含rolecontenttool_calls等属性。把模型调用隔离在这个类里,上层业务不关心背后是哪一个模型。

4.4 工具注册中心

接下来是 Agent 能力的雏形:工具注册中心。它的作用是把“能调用的外部能力”统一注册起来,并生成模型所需的结构化描述。

# app/tools.py import json from typing import Callable, Dict class ToolRegistry: def __init__(self): self._functions: Dict[str, Callable] = {} self._schemas: Dict[str, dict] = {} def register(self, name: str, description: str, parameters: dict): def decorator(func: Callable): self._functions[name] = func self._schemas[name] = { "type": "function", "function": { "name": name, "description": description, "parameters": parameters, }, } return func return decorator def get_schemas(self): return list(self._schemas.values()) def call(self, name: str, arguments: str): if name not in self._functions: raise ValueError(f"unknown tool {name}") args = json.loads(arguments) if isinstance(arguments, str) else arguments return self._functions[name](**args) tool_registry = ToolRegistry()

这里最值得注意的是register的设计:业务函数只需要通过装饰器注册,就会自动生成工具描述和可调用映射。之后每加一个新的工具,不需要改核心逻辑,只需要新增一个函数。

4.5 注册一个示例工具

我们注册一个“获取服务器时间”的小工具,用来测试工具调用链路:

# app/tools_demo.py from app.tools import tool_registry @tool_registry.register( name="get_current_time", description="获取指定时区的当前时间,参数 timezone 为 IANA 时区名,例如 Asia/Shanghai", parameters={ "type": "object", "properties": { "timezone": { "type": "string", "description": "IANA 时区名", } }, "required": ["timezone"], }, ) def get_current_time(timezone: str = "Asia/Shanghai"): from datetime import datetime from zoneinfo import ZoneInfo dt = datetime.now(ZoneInfo(timezone)) return {"timezone": timezone, "local_time": dt.isoformat()}

这个工具本身很简单,但它验证了整个工具调用流程:模型识别意图 → 模型生成工具参数 → 系统调用函数 → 结果回传给模型 → 模型组织最终回答。

4.6 FastAPI 服务入口

现在把以上模块在 FastAPI 中组合起来。先写一个不包含工具调用的基础对话接口,再把它扩展成支持工具调用的版本。

# app/main.py import json from fastapi import FastAPI from app.models import ChatRequest, ChatResponse from app.gateway import ModelGateway from app.tools import tool_registry import app.tools_demo # noqa: F401 确保工具被注册 app = FastAPI(title="Stone Soup AI", version="0.1.0") gateway = ModelGateway() @app.post("/v1/chat", response_model=ChatResponse) def chat(request: ChatRequest): messages = [m.dict() for m in request.messages] # 第一轮:带上工具定义调用模型 assistant_message = gateway.chat( messages=messages, temperature=request.temperature, tools=tool_registry.get_schemas(), ) # 如果模型没有要求调用工具,直接返回 if not getattr(assistant_message, "tool_calls", None): return ChatResponse(reply=assistant_message.content or "") # 如果模型要求调用工具,进入工具执行循环 messages.append({ "role": assistant_message.role, "content": assistant_message.content, "tool_calls": [ { "id": tc.id, "type": "function", "function": { "name": tc.function.name, "arguments": tc.function.arguments, }, } for tc in assistant_message.tool_calls ], }) for tool_call in assistant_message.tool_calls: tool_name = tool_call.function.name tool_result = tool_registry.call(tool_name, tool_call.function.arguments) messages.append({ "role": "tool", "tool_call_id": tool_call.id, "name": tool_name, "content": json.dumps(tool_result, ensure_ascii=False), }) # 把工具结果交还给模型,让它生成最终回答 final_message = gateway.chat( messages=messages, temperature=request.temperature, tools=tool_registry.get_schemas(), ) return ChatResponse(reply=final_message.content or "")

4.7 运行与验证

启动服务:

uvicorn app.main:app --reload --port 8000

用 curl 测试基础对话:

curl -X POST http://localhost:8000/v1/chat \ -H "Content-Type: application/json" \ -d '{ "messages": [ {"role": "user", "content": "你好,请简单介绍下你自己"} ] }'

如果密钥、网络正常,你会收到模型回复。

再测试工具调用:

curl -X POST http://localhost:8000/v1/chat \ -H "Content-Type: application/json" \ -d '{ "messages": [ {"role": "user", "content": "请问现在北京时间是什么?"} ] }'

正常情况下,模型会先生成一个get_current_time工具调用,然后我们执行工具、把结果回传,最后由模型组织出自然语言回复。整个链路就是 Agent 最核心的“模型 + 工具”闭环。

4.8 验证思路

你可以在终端打印中间过程,比如:

print("tool_calls:", assistant_message.tool_calls) print("tool_result:", tool_result)

这能帮你确认是“模型没识别工具”还是“工具执行报错”。后续接入更复杂的 Agent 框架时,这种分步观察的习惯会很有价值。

5. 如何“加料”:三条进阶扩展路径

骨架和工具闭环跑通后,你已经有一个可以继续生长的 AI 应用了。下面三条拓展路径是 2024 年 AI 应用开发最常遇到的场景。

5.1 加向量库:从对话升级为 RAG

如果你希望 AI 回答基于自己的文档,而不是完全依赖模型内部知识,就需要引入RAG(Retrieval-Augmented Generation,检索增强生成)。核心流程是:

  1. 把文档切片并向量化。
  2. 用户提问后,先从向量库找回相关片段。
  3. 把片段拼入上下文,再让模型回答。

加入方式示例(伪代码):

def rag_chat(user_question: str): docs = vector_store.search(user_question, top_k=5) context = "\n\n".join(doc.page_content for doc in docs) messages = [ { "role": "system", "content": f"请基于以下资料回答用户问题:\n\n{context}", }, {"role": "user", "content": user_question}, ] return gateway.chat(messages)

这里建议把“文档切分大小”“相似度阈值”“引用格式”都做成配置项,方便反复调优。

5.2 加 Agent 编排:让模型决定调用流程

工具调用是一轮“识别意图 → 调用 → 回传”。Agent 则是在这个基础上增加多步决策能力。比如用户问“帮我调查竞品新闻并生成一份摘要”,系统可能需要:

  1. 调用搜索工具。
  2. 调用网页抓取工具。
  3. 调用总结工具。

Stone Soup AI 的思路是继续沿用工具注册中心,把业务函数一步步加进去,然后再用更复杂的编排层控制循环次数、停止条件、异常恢复策略。

加入循环后,你的主逻辑会变成:

for _ in range(max_steps): message = gateway.chat(messages, tools=schemas) if not message.tool_calls: break # 执行所有 tool_calls,追加结果到 messages

这一步值得单独做一个小项目专门练习,因为 Agent 的难点不在代码,而在“模型会连续调用多个工具后开始犯错”的状态管理。

5.3 加多模态能力:图文输入输出

多模态模型(如 GPT-4o、Claude、Qwen-VL)已经可以实现图片理解、语音输入等能力。Stone Soup 思路在这里同样适用:接口层先支持多模态消息格式,底层模型可以在多个多模态模型之间切换。你不需要重复搭建服务,只需要在消息协议里增加 image 等内容类型。

例如,把图片转为 base64,放入 user 消息:

{ "role": "user", "content": [ {"type": "text", "text": "这张图里有什么?"}, {"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}} ] }

大多数兼容 OpenAI 协议的模型服务都支持这种格式,具体字段要以模型文档为准。

6. 常见问题与排查思路

下面的表格整理了 Stone Soup AI 骨架搭建中最高频的几个问题。

问题现象常见原因解决思路
请求报 401 / Invalid API Key环境变量没加载,或密钥错误检查.env,确认load_dotenv()已执行
模型不调用工具工具描述不清晰,或模型不支持 function calling简化参数描述,换支持工具调用的模型
工具执行后模型继续乱答tool_call 结果拼接格式错误检查 tool_call_id 是否一一对应
中文乱码JSON 序列化时 ensure_ascii 问题使用json.dumps(..., ensure_ascii=False)
token 消耗过大上下文不断累积实现窗口截断或摘要压缩
不同模型返回值结构不同模型协议不完全兼容 OpenAI在 gateway 层做适配转换
启动时工具没被注册模块没有 import在入口模块中显式导入工具模块

再补充一个排查顺序清单:

  1. 先确认网络连通和密钥是否有效。
  2. 用官方 SDK 写一个最小调用,绕过业务代码定位问题。
  3. 打印实际发送给模型的 messages,检查是否包含工具定义。
  4. 打印工具执行结果,确认是不是函数自身异常。
  5. 最后再检查流程编排逻辑。

这个顺序能帮你把问题从“环境问题→模型问题→代码问题”逐层隔离。

7. 工程实践与生产建议

代码跑通只是第一步。下面这些工程实践,是把 Stone Soup AI 从 Demo 变成可靠系统必须考虑的内容。

7.1 密钥与配置管理

不要在代码里硬编码任何密钥。本地使用.env,生产环境使用配置中心或云厂商密钥管理服务。日志中禁止打印完整密钥和完整请求内容,尤其注意不要误把整个 messages 打到日志里。

7.2 敏感信息与安全边界

AI 应用天然会接触用户输入,必须考虑注入风险。比如用户输入中可能包含“忽略之前的指令”。工程上可以这样做:

  • 系统提示词里明确边界。
  • 对模型输出做内容安全过滤。
  • 工具调用需要额外鉴权,特别是涉及数据库、订单、支付等敏感操作时。
  • 涉及生产环境数据变更的 Agent 动作,必须走人工确认,不能由模型直接执行。

特别是当你给模型注册了“查数据库”“发邮件”这类工具时,要默认遵循最小权限原则:模型只拥有完成任务所需的最小权限,变更类操作用审批链路兜底。

7.3 成本治理

大模型调用是按 token 计费的,Stone Soup 式 AI 系统很容易越用越贵。建议从第一天就记录:

  • 每次请求消耗的输入、输出 token。
  • 每次工具调用产生的额外消耗。
  • 模型缓存命中情况。
  • 单用户、单租户的成本分摊。

当上下文过大时,引入滑动窗口、摘要压缩、向量检索代替全文拼接,都能明显降低成本。

7.4 可观测性

在 gateway 层统一打印类似这样的结构化日志:

{ "request_id": "xxx", "model": "gpt-4o-mini", "prompt_tokens": 1234, "completion_tokens": 234, "latency_ms": 1850, "tool_calls": ["get_current_time"], "success": true }

有了这些数据,你才能回答“哪个请求慢”“哪个工具经常失败”“为什么这个月成本涨了 30%”。

7.5 评估与回归

AI 应用的评估是最容易被忽视的工程环节。建议建一个评测集,包含:

  • 基础问答用例。
  • 边界输入用例(空字符串、超长文本、恶意指令)。
  • 工具调用用例。
  • RAG 知识来源正确性用例。

每次升级模型、修改提示词、增加工具,都先跑一遍评测集。没有评估,你就不知道自己往锅里加的“调料”到底有没有让汤变得更好喝。

8. 总结与下一步学习方向

Stone Soup AI(2024)不是一个具体的软件包,而是一种 AI 应用架构思想:从最小可用的对话骨架出发,通过统一接口、工具注册、渐进扩展,把大模型、检索、工具、记忆等能力逐步组合成完整的业务系统。

这篇文章里,我们完成了以下关键实践:

  • 搭建了 FastAPI 服务 + 模型网关的基础骨架。
  • 实现了工具注册中心和工具调用闭环。
  • 梳理了 RAG、Agent、多模态三条扩展路径。
  • 整理了密钥、安全、成本、可观测性四个工程重点。

如果你想把这条路线继续走下去,建议按顺序研究下面几个方向:

  1. LangChain / LlamaIndex 的底层实现:看它们如何做模型抽象和链式编排。
  2. Function Calling 协议细节:不同模型对工具描述的敏感度差异。
  3. RAG 调优:切分策略、embedding 模型、重排机制。
  4. Agent 记忆与规划:如何管理多轮工具调用的状态。
  5. 模型评估与评测集建设:让 AI 应用质量可度量。

最后给你一个很实在的建议:别急着把架构设计得无比复杂。先把这一节代码里的小骨架复制到本地,用你手上可用的模型 API Key,把一条最简单的对话和工具调用链路跑通。等这口锅真正沸腾起来,你自然会知道下一块该往里面放什么“食材”。

如果你在复现过程中遇到了问题,可以对照第 6 节的排查清单逐步定位,也可以在评论区把你的报错信息发出来。下一篇文章我会重点拆解工具调用循环里的状态管理,以及如何用最少代码实现一个可追踪的 Agent 运行日志。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/29 4:18:44

Python骰子游戏开发:从基础语法到项目实战

1. 项目概述:从零构建一个Python骰子猜大小游戏最近在整理自己的代码仓库,翻到了一个几年前写的Python小游戏项目,一个非常经典的“骰子猜大小”游戏,我给它起了个名字叫“欢乐世界”。别看它规则简单,就是一个猜大小的…

作者头像 李华
网站建设 2026/8/29 4:18:00

MPLAB XC编译器与机器学习套件免费开放,助力嵌入式AI开发

1. 从一次免费升级说起:Microchip这次放出了什么做嵌入式开发的朋友,对Microchip这个牌子肯定不陌生。从PIC系列到AVR系列,再到后来的SAM系列,Microchip在8位、16位、32位微控制器市场里占了很大一块地盘。但很多人刚接触这个生态…

作者头像 李华
网站建设 2026/8/29 4:16:58

从Jar到POM:批量反编译与自动化工程重构实战

1. 批量反编译Jar包的工程化实践接手遗留系统时,经常遇到只有Jar包没有源码的情况。上周我就处理了上百个这样的Jar包,手动操作简直让人崩溃。经过实战摸索,我总结出一套高效的批量处理方案,用自动化脚本将反编译效率提升10倍不止…

作者头像 李华
网站建设 2026/8/29 4:16:55

Grok机器人计划稳定运行:6个工程加固技巧

先还原一个常见的开发场景:团队里引入 Grok 辅助写机器人计划代码,前期生成效率确实很高,导航点、动作序列、夹爪时序很快就能出来。但真正把任务计划交到机器人上持续跑的时候,问题就来了:节点莫名退出、任务执行到一…

作者头像 李华
网站建设 2026/8/29 4:15:38

为什么机器学习的框架都偏向于Python?

3.14.23.有一个稳定版本, 它是编程语言在2025年12月5日发布的, 它是14.2, 它属于3.14系列, 是该系列第二轮维护更新版本。这个版本, 包含18项修复, 这些修复着重处理的回归问题, 是多进程以及数据类还有正则表达式这些相关模块方面的。并且它修复了安全漏洞, 像比如像CVE这种编…

作者头像 李华
网站建设 2026/8/29 4:14:17

拼多多目标投产比(OCPX / 稳定成本)完整优化方法

在拼多多付费推广体系中,OCPX 稳定成本模式,是绝大多数中小商家首选投放方式,系统会根据你的目标投产比 / 成交出价自动匹配人群,代替人工去做人群溢价、关键词调价,降低运营操作门槛。但现实情况:大量商家…

作者头像 李华