新闻详情

新闻详情

首页 / 资讯中心 / 详情

三种语言实现同一LLM Agent:工具调用主循环的跨语言建模

发布时间:2026/9/4 17:48:44来源:尧图网络
三种语言实现同一LLM Agent:工具调用主循环的跨语言建模
最近和几个团队聊 LLM Agent 的架构选型发现一个很典型的分歧有人觉得 Python 加一个 Agent 框架就够了有人坚持把 Agent 当作独立的领域模型来做还有人已经在 Elixir 的进程模型里折腾“每个 Agent 一个进程”的方案。我的建议是在选定框架之前先做一个更小、也更诚实的小练习把同一个带工具调用的 LLM Agent分别用 Python、Clojure、Elixir 写一遍只做最核心的工具调用主循环。这个练习看起来有点“炫技”但实际做下来价值很高。你会发现跨语言实现 Agent 得到的不是语法比较而是同一时间看到 Agent 的三个切面Python 所代表的命令式状态管理、Clojure 所代表的数据驱动递归、Elixir 所代表的消息并发模型。这层心智模型换到任何 Agent 框架后都不会过时。这篇文章把三份可运行的代码、环境准备、运行方法和工程建议完整拆开。无论你最后用不用 Clojure 或 Elixir这套“工具调用、消息回填、循环终止”的核心逻辑都是绕不开的。1. 为什么要用三种语言写同一个 LLM Agent先明确一个容易混淆的问题标题里的“Modeling”不是让模型给自己建模而是开发者如何用代码描述一个 LLM Agent 的运作方式。很多刚接触 Agent 的开发者会把大量时间花在提示词上觉得只要把 prompt 写得足够好模型就能自动完成任务。但当你进入真实项目会立刻发现瓶颈不在“模型会不会理解”而在“你的代码能不能正确完成一次模型和工具之间的往返”。例如用户问“北京今天需要带伞吗”一次完整的 Agent 流程可能是模型先决定调用get_current_weather。模型返回一个结构化的工具调用而不是直接输出“我不知道”。你的代码执行天气查询拿到结果。代码把天气结果作为tool角色消息回填给模型。模型阅读结果后给出最终回答“北京今天晴天不需要带伞。”这套流程无论用什么语言实现本质都相同。但不同语言会让开发者以不同的方式思考状态、数据和并发Python 更适合快速实现“面向对象 可变状态”的直观流程Clojure 强制你把 Agent 当作不可变数据和递归函数来组织Elixir 则把 Agent 放到 BEAM 的进程模型里让你天然关注隔离和并发。所以这篇文章真正解决的问题是当你不依赖任何重量级 Agent 框架时如何用最朴素的 HTTP 请求实现一个最小 Agent并判断哪种语言的工程风格更适合你的团队。如果你正准备从 Python 转向函数式语言或者需要在现有 Clojure/Elixir 系统中接入 LLM这篇文章可以直接给出一个最低可行实现。2. LLM Agent 的基础共识角色、消息、工具与主循环在看代码之前先把 Agent 的基础概念压缩到最小。2.1 Agent 的核心组成一个带工具调用能力的 Agent至少包含四部分模型承担意图理解和工具选择通常通过 Chat Completions 一类的接口调用。工具模型可以调用的外部函数例如天气查询、数据库查询、订单查询。消息历史系统消息、用户消息、助手消息、工具消息按顺序组成的数组。主循环判断模型返回的是最终文本还是工具调用并循环执行。把 Agent 简化成“模型 工具 循环 状态”是跨语言建模最有效的思路。它避免了一上来就纠缠于复杂的多智能体框架。2.2 工具调用协议function calling模型本身没有“执行函数”的能力。模型能做的是输出一个结构化 JSON告诉你的代码“应该调用哪个函数、参数是什么”。这套机制通常称为 function calling 或 tool calling。请求时你把工具描述放在tools参数里[ { type: function, function: { name: get_current_weather, description: 查询某个城市的实时天气, parameters: { type: object, properties: { city: { type: string, description: 城市名例如北京 } }, required: [city] } } } ]模型返回的内容里可能包含tool_calls例如{ id: call_123, type: function, function: { name: get_current_weather, arguments: {\city\: \北京\} } }注意arguments是一个字符串不是对象。落地时第一步是解析这个字符串否则很容易出现 JSON 解析失败。2.3 为什么消息顺序不能乱工具调用要求消息顺序非常严谨。一次成功的历史应该长这样角色内容system设定助手身份和约束user北京今天需要带伞吗assistant返回 tool_calls而不是最终回答tool对应上面的 tool_call_id返回天气查询结果assistant北京今天晴天不需要带伞容易出现的问题是两个一是客户端收到tool_calls后没有把 assistant 消息放回历史二是执行完工具后没有把tool消息放回历史。这两种错都会导致模型无法“看到”自己刚才调用过什么工具。2.4 主循环的等价结构如果用伪代码描述 Agent 主循环几乎所有语言都是同一张图用户输入 → 请求模型 → 有 tool_calls → 是执行工具把结果作为 tool 消息追加 → 否返回最终文本 → 继续循环直到达到最大轮数后面的三份实现本质上是同一张结构图在三种语言里的翻译。3. 环境准备一套 OpenAI 兼容协议跑三个运行时为了减少环境差异三份示例统一使用兼容POST /v1/chat/completions的接口。目前很多模型服务商和本地推理工具都支持这个协议你只需要把地址、模型名、密钥替换成自己的。示例中使用以下环境变量export LLM_BASE_URLhttp://localhost:11434/v1 export LLM_MODELyour-model-name export LLM_API_KEYyour-api-key如果你的服务不需要密钥也可以填任意值但建议保留变量避免代码里硬编码密钥。三个语言的基础环境要求如下语言运行环境主要依赖PythonPython 3.9 以上requestsClojureJDK 11 以上、Clojure CLIclj-http、cheshireElixirErlang/OTP 25 以上Req、Jason如果你只是想把流程先跑通我建议先用本地模型或公司内部已部署的模型服务不要在公共网络环境中随意泄露 API Key。密钥只能通过环境变量提供不要提交到 Git 仓库。本文重点关注通用思路所以模型名保持为占位符实际使用时请替换成你的模型服务商可用的值。4. 用 Python 建模最小依赖的命令式 AgentPython 是 Agent 生态最丰富的语言但这并不意味着必须依赖 LangChain 之类的框架。先用自己的代码实现一遍主循环反而能避免“被框架遮住核心逻辑”。下面是一个只依赖requests的最小实现。# 文件python_agent.py import json import os import requests MODEL os.getenv(LLM_MODEL, your-model-name) API_KEY os.getenv(LLM_API_KEY, ) BASE_URL os.getenv(LLM_BASE_URL, http://localhost:11434/v1) MAX_STEPS 5 TOOLS [ { type: function, function: { name: get_current_weather, description: 查询某个城市的实时天气, parameters: { type: object, properties: { city: { type: string, description: 城市名例如北京 } }, required: [city], }, }, }, { type: function, function: { name: get_product_price, description: 查询某个商品的当前价格, parameters: { type: object, properties: { product_id: { type: string, description: 商品编号 } }, required: [product_id], }, }, }, ] def get_current_weather(city: str) - str: # 实际项目中这里应该调用真实的天气服务 return json.dumps( {city: city, weather: 晴, temperature: 22}, ensure_asciiFalse, ) def get_product_price(product_id: str) - str: # 实际项目中这里应该查询商品系统 return json.dumps( {product_id: product_id, price: 499, currency: CNY}, ensure_asciiFalse, ) def execute_tool(tool_name: str, arguments_text: str) - str: 把模型输出的 arguments 字符串解析成真正的函数参数。 try: args json.loads(arguments_text) if tool_name get_current_weather: return get_current_weather(**args) if tool_name get_product_price: return get_product_price(**args) return json.dumps({error: funknown tool
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

开关电源纹波电流公式推导与降纹波系统方案 2026/9/4 18:43:03

开关电源纹波电流公式推导与降纹波系统方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
国产AI工具实战指南:从选型到集成,构建个人高效工作流 2026/9/4 18:43:03

国产AI工具实战指南:从选型到集成,构建个人高效工作流

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
Ollama+AnythingLLM+DeepSeek构建企业级RAG知识库实战指南 2026/9/4 18:43:03

Ollama+AnythingLLM+DeepSeek构建企业级RAG知识库实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
仪器仪表读数 自动化读数 opencb石油化工工业仪器仪表自动化识别 结合OpenCV进行图像处理和读数识别,Django作为后端框架,MySQL作为数据库,并使用前端技术构建用户界面 2026/9/4 18:43:03

仪器仪表读数 自动化读数 opencb石油化工工业仪器仪表自动化识别 结合OpenCV进行图像处理和读数识别,Django作为后端框架,MySQL作为数据库,并使用前端技术构建用户界面

使用YOLOv5进行目标检测建立石油化工工业仪器仪表自动化识别 结合OpenCV进行图像处理和读数识别,Django作为后端框架,MySQL作为数据库,并使用前端技术构建用户界面 文章目录使用YOLOv5进行目标检测建立石油化工工业仪器仪表自动化识别 结合Op…

阅读更多 →
DrugBank 5.1.7 数据库解析与应用:从数据架构到药物研发实战 2026/9/4 18:43:03

DrugBank 5.1.7 数据库解析与应用:从数据架构到药物研发实战

简介:DrugBank 5.17 是面向药物信息学、生物信息学及计算药理学研究者的权威结构化数据资源,适用于开展靶点预测、药物重定位、ADMET建模、知识图谱构建等科研任务。资源为单文件ZIP压缩包(140.19MB),内含1个核心XML文…

阅读更多 →
第一款Mac app就获97%媒体评分?关键在于发布前的工程细节 2026/9/4 18:39:57

第一款Mac app就获97%媒体评分?关键在于发布前的工程细节

那天刷新 Show HN 的时候,我注意到一条标题:My first app got 97% on MacSources。发帖人没有写长篇功能清单,没有讲解技术栈,也没有放几十张截图,只是把一个结果放在那里。评论区里有人恭喜,有人询问这款具…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞