#AI

03-LLM的工程化能力


03-LLM的工程化能力

LLM的工程化能力指的是,LLM本身是大语言模型,只能输出文字,但它具有一定智能,如果我让他通过语言去描述动作,我再执行它的动作,那么GPT就具有了工程能力。

最简案例

假设说我编写了一个Python脚本,可以控制灯的开关,当我向模型提问:

你是一个根据时间来判断是否应该打开客厅灯的机器人,我会给你当前的时间,如果是处于晚上8点到10点,则需要你输出“Open”这个单词,如果不是,则输出“Close”这个单词,其余什么都不输出。
当前时间是晚上7点。

此时模型必然输出

“Close”

这个单词,那么我编写一个Python脚本,检测模型输出是否等于“Open”,如是,则通过脚本把灯打开。

那么LLM便有了工程能力。

Tool Calls

Tool Calls是模型最原生的工具调用能力,以Deepseek提供的API为参考,这里面进行了一些拓展

# sk-23f27280f8d04d8c857f5b14672e14ea
from openai import OpenAI
import json
import random

def send_messages(messages):
    response = client.chat.completions.create(
        model="deepseek-chat",
        messages=messages,
        tools=tools
    )
    return response.choices[0].message

client = OpenAI(
    api_key="sk-23f27280f8d04d8c857f5b14672e14ea",
    base_url="https://api.deepseek.com",
)


tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get weather of a location, the user should supply a location first.",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "The city and state, e.g. San Francisco, CA",
                    }
                },
                "required": ["location"]
            },
        }
    },
]

mock = False

messages = [{"role": "user", "content": "How's the weather in Hangzhou, Zhejiang?"}]
message = send_messages(messages)
# 此时响应的信息中包含了模型希望调用的工具
print(f"User>\t {messages[0]['content']}")
tool = message.tool_calls[0]

# 打印模型希望调用的工具参数
print(f"Tools>\t {tool.function.name}")         # Tools>   get_weather
print(f"Tools>\t {tool.id}")                    # Tools>   call_00_tnoHD8lWGMiMSsnQVcfiZpyv
print(f"Tools>\t {tool.function}")              # Tools>   Function(arguments='{"location": "Hangzhou, Zhejiang"}', name='get_weather')
print(f"Tools>\t {tool.function.arguments}")    # Tools>   {"location": "Hangzhou, Zhejiang"}
messages.append(message)
# 可以根据模型希望的内容去调用实际工具

if mock:
    messages.append(
        {
            "role": "tool", 
            "tool_call_id": tool.id, 
            "content": "24℃"
        }
    )
else:
    # 解析工具参数
    arguments = json.loads(tool.function.arguments)
    location = arguments["location"]
    # 随机编一个天气
    weather = f"{random.randint(18, 39)}℃"
    print(f"Tools>\t 温度是 {weather}")
    messages.append(
        {
            "role": "tool", 
            "tool_call_id": tool.id, 
            "content": weather
        }
    )
    # 调用实际工具
message = send_messages(messages)
print(f"Model>\t {message.content}")

它的输出是

PS D:\DreamReflexTech\devops-blog\playgrounds> python .\main.py
User>    How's the weather in Hangzhou, Zhejiang?
Tools>   get_weather
Tools>   call_00_TFDN6pHzQnfWLRz7slT87VIE
Tools>   Function(arguments='{"location": "Hangzhou, Zhejiang"}', name='get_weather')
Tools>   {"location": "Hangzhou, Zhejiang"}
Tools>   温度是 33℃
Model>   The current weather in Hangzhou, Zhejiang is 33°C (approximately 91°F). It's quite warm there!
PS D:\DreamReflexTech\devops-blog\playgrounds> 

能够看得出来,此时脚本作为一个客户端,在模型返回的内容中解析到了希望调用获取天气的工具,并且通过一些手段得到了温度信息,返回给了模型,模型就具有了与现实世界交互的能力。

现在我们就可以编写各类工具供大模型使用。

目前存在的问题是:大模型可以通过调用工具像调用函数一样获取信息,但仍需要我们手动编辑执行器来解析它的命令。比如说让他开灯这个指令还是需要我们手动约束完成。或者编写一个工具让他调用,为了进一步封装Tool Calls,出现了MCP

MCP

对于上文的Tool Calls来说,我们还需要进一步封装工具和执行层,并且争取让一个执行器自动完成任务。对于Tool Calls来说,如何定义工具,如何暴露接口是模型厂商内部的一种接口形式,代码无法应用到所有模型,但所有LLM都有预测能力,所以可以直接用自然语言描述能力,让能力通用。

下面以一个非常简单的场景案例

我希望一个大模型能够自动扫描的文章并且修改错别字。那么我需要让大模型读取到我的文章,并且把大模型修改后的文章写回文件。

对于操作文件来说,我可以使用Python的read和write功能,伪代码大概是

prompt="你是一个作家,你需要分析文章的问题,然后写出修改后的版本,如果你输出read(文章名称),你就会得到文章,如果你输出write(文章名,文章内容),就可以修改文章"
msg = send_to_llm(prompt)
if msg == read
	article_content = read(msg.article_name)
	send_to_llm(article_content)
if msg == write
	write(msg.article_name, msg.article_content)

再将上述伪代码循环,便制作出了一个智能文章修改机器人,也成为智能体。

其中read和write函数是我们提供给智能体的,read和write函数本身是执行器,read/write 是 MCP Server 暴露的 tools(能力函数),read和write的细节由他们自己实现维护,我们并不关系实现细节和底层原理。

而判断大模型需要干什么的分支判断和循环便是MCP Host,我们构建了一个MCP Host,它通过read和write函数去执行大模型的要求。

从上述的简单情景中,我们就可以制作出一个智能体

MCP Host

一个Agent代码是

# host.py

import json

class MCPHost:
    def __init__(self, llm, client):
        self.llm = llm
        self.client = client

    def run(self, article_path):
        tools = self.client.get_tools_schema()

        system_prompt = f"""
你是文章校对助手。
你可以调用如下工具(用JSON格式返回调用):

{json.dumps(tools, ensure_ascii=False, indent=2)}

目标:
1. 读取文章
2. 修正错别字和病句
3. 写回文件
"""

        conversation = [
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": f"请处理文件:{article_path}"}
        ]

        while True:
            response = self.llm(conversation)

            # 如果模型输出工具调用
            if response.get("tool_call"):
                name = response["tool_call"]["name"]
                args = response["tool_call"]["arguments"]

                result = self.client.call_tool(name, args)

                conversation.append({
                    "role": "tool",
                    "name": name,
                    "content": result if isinstance(result, str) else "ok"
                })
            else:
                print("模型完成任务:")
                print(response["content"])
                break

MCP Client

由于MCP Server是分布于四面八方的,可能在公网,可能是本地,可能是第三方,也可以是其他人的,所以我们还需要设立一个专门的MCP Client去调用Server,这个Client结构如下

# mcp_client.py

class MCPClient:
    def __init__(self, server):
        self.server = server

    def call_tool(self, name, args):
        tool = getattr(self.server, name)
        return tool(**args)

    def get_tools_schema(self):
        return self.server.list_tools()

MCP Server

此时我们就可以实现一个自己的文件系统工具

# file_server.py

class FileSystemServer:
    def read_file(self, path: str) -> str:
        with open(path, "r", encoding="utf-8") as f:
            return f.read()

    def write_file(self, path: str, content: str):
        with open(path, "w", encoding="utf-8") as f:
            f.write(content)

    def list_tools(self):
        return {
            "read_file": {
                "description": "Read a text file",
                "args": {"path": "string"}
            },
            "write_file": {
                "description": "Write text to a file",
                "args": {
                    "path": "string",
                    "content": "string"
                }
            }
        }

我们再在这个基础上模拟一个大模型

import json

class FakeLLM:
    def __call__(self, messages):
        last = messages[-1]["content"]

        if "请处理文件" in last:
            return {
                "tool_call": {
                    "name": "read_file",
                    "arguments": {"path": last.split(":")[1]}
                }
            }

        if "这是文章内容" in last or len(last) > 50:
            fixed = last.replace("错别字", "正确文字")
            return {
                "tool_call": {
                    "name": "write_file",
                    "arguments": {
                        "path": "article.txt",
                        "content": fixed
                    }
                }
            }

        return {"content": "文章已修改完成"}

那么此时这个Agent就可以真正跑起来

from file_server import FileSystemServer
from mcp_client import MCPClient
from host import MCPHost
from fake_llm import FakeLLM

server = FileSystemServer()
client = MCPClient(server)
llm = FakeLLM()

host = MCPHost(llm, client)
host.run("article.txt")

Agent智能体

由于MCP的存在,MCP赋予了大模型操作其他内容的能力,MCP是连接大模型和物理世界的接口,那么大模型就可以在编码,创作,执行等方面大放异彩,让LLM具有人类的能力。

人类在编码时候本质上是在GUI操作文本字符串,而大模型也可以通过POSIX接口操作OS,人类在电脑上的办公,本质上都是在调用各类接口,例如窗体接口,HTTP的API等等,如果把这些能力都封装成MCP Server,那么Agent就可以利用这些MCP Server执行真正的行为,进而干预物理世界。

举一个简单的例子,假设说我在监控一个服务器的运行状态,常规情况下,我需要监控CPU,延迟,IO等数据,如果我把bash接口封装为MCP Server,那么大模型就可以通过调用 bash 和附加命令来获取系统情况。例如通过htop数据进行分析,如果出现问题,通过HTTP接口发起WebHook调用告知我风险。那么他便替代了运维工程师的某个工作。

× Preview