惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
V2EX
C
Check Point Blog
博客园_首页
B
Blog
D
Docker
U
Unit 42
量子位
I
InfoQ
有赞技术团队
有赞技术团队
Martin Fowler
Martin Fowler
GbyAI
GbyAI
L
LangChain Blog
云风的 BLOG
云风的 BLOG
博客园 - Franky
美团技术团队
T
The Blog of Author Tim Ferriss
阮一峰的网络日志
阮一峰的网络日志
月光博客
月光博客
Vercel News
Vercel News
Recent Announcements
Recent Announcements
雷峰网
雷峰网
大猫的无限游戏
大猫的无限游戏
小众软件
小众软件
Google DeepMind News
Google DeepMind News

博客园_首页

Linux实操--组管理、权限管理和定时任务 Java + EasyExcel 实现单个接口导出多个Excel Mem0 源码解析系列(二):提示词工程的深度剖析 Openclaw TaskFlow究竟是什么?和普通Skill技能有什么区别 博文阅读密码验证 - 博客园 嘉立创开源:应该是全网MicroPython教程最多的开发板 Hermes Agent 集成实践:从协议到生产 2026年AI编程工具横评:Cursor、Codex、Claude Code、Zed、Windsurf Java程序员必看的RAG入门教程 2026 AI效率神器:Superpowers + Claude Code 保姆级教程 本地大模型部署全攻略:从 0 到 1 玩转 Ollama 【从0到1构建一个ClaudeAgent】内存管理-上下文压缩 .NET 高级开发 | 设计、实现一个事件总线框架 电子小白入门之NE555 3. WorkBuddy:隐藏玩法,一键召唤专家,让 AI 以"专家身份"给你干活 和AI一起搞事情#3:Claude Teammate 游戏开发翻车实录 【OpenClaw】通过 Nanobot 源码学习架构---(7)Memory C# .NET 周刊|2026年3月3期 我在 Debian 11 上把 K8s 单机搭起来了,过程没你想的那么顺(/opt 目录版) 深度学习进阶(七)Data-efficient Image Transformer CLI+Skill搭建浏览器AI自动化框架,告别一切重复枯燥任务 告别Token账单无底洞:OpenClaw本地部署,重塑企业数据主权的唯一解 FastAPI+Vue:文件分片上传+秒传+断点续传,这坑我帮你踩平了! SBTI 爆火后,我做了个程序员版的 CBTI。。已开源 + 附开发过程 多模态检索开始进入工程期:用 Sentence Transformers 搭建可落地的 Multimodal RAG 100多行代码实现一个最简单的Agent(用ReAct) Claude Code 通关手册(八):推荐 5 个 Hooks,代码质量提升 3 倍 老板:“有人截图了!”。安全部门:“收到,马上查暗水印!” - why技术 技术之外,皆是人间 C#/.NET/.NET Core技术前沿周刊 | 第 69 期(2026年4.01-4.12)
别再写 if/else 了:让 LLM 自己决定调用哪个函数
it排球君 · 2026-04-21 · via 博客园_首页

前言

书接上文,今天我们来讨论上一篇文章遗留的问题:当一次性给大模型多个函数时,它到底怎么判断该调用哪一个?这个问题,在 AIOps、智能排障、客服问答、数据分析这类场景里特别常见。本文继续用理论结合时间,拿一个线上故障排查的例子,掰开揉碎了讲清楚:当有多个工具同时可用时,LLM 是怎么做决策的,以及我们代码里到底该怎么写

实战例子

先上代码:多函数function call

函数作用

假设现在手里有 3 个函数

  • 1)获取 Pod 状态,主要通过describe 查看 k8s pod,get_pod_status

    • Pod 是否 Running
    • 有没有 CrashLoopBackOff
    • 重启了多少次
    • 最近事件里有没有探针失败、OOM、镜像拉取失败
  • 2)获取业务日志,通过调用日志平台接口获取对应日志,get_business_log

    • 接口为什么返回 500
    • 有没有空指针
    • 有没有数据库超时
    • 有没有调用下游失败
  • 3)获取操作系统监控数据,通过调用prometheus获取底层监控,get_system_metrics

    • CPU 是否打满
    • 内存是否不足
    • 磁盘是否写满
    • 网络流量是否异常

函数描述

  • 获取 Kubernetes Pod 的状态、重启次数和事件信息,用于排查 Pod 启动失败、异常重启、探针失败等问题
  • 获取业务服务日志,用于排查接口报错、代码异常、数据库超时等问题
  • 获取节点操作系统监控数据,用于排查 CPU、内存、磁盘、网络资源瓶颈问题

函数描述非常重要,LLM主要靠它来指导模型决策以及函数选取

定义三个函数

import json
from datetime import datetime, timedelta


def get_pod_status(namespace: str = "default", pod_name: str = ""):
    return {
        "namespace": namespace,
        "pod_name": pod_name,
        "status": "CrashLoopBackOff",
        "restart_count": 5,
        "node": "10.10.1.23",
        "events": [
            "2026-04-20 11:42:11 Readiness probe failed: connection refused",
            "2026-04-20 11:42:25 Container restarted",
            "2026-04-20 11:42:25 Last State: Terminated, Reason: OOMKilled"
        ]
    }


def get_business_log(service_name: str = "", start_time: str = "", end_time: str = ""):
    if not start_time:
        start_time = (datetime.now() - timedelta(minutes=30)).strftime("%Y-%m-%d %H:%M:%S")
    if not end_time:
        end_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

    return {
        "service_name": service_name,
        "time_range": {
            "start_time": start_time,
            "end_time": end_time
        },
        "logs": [
            "2026-04-20 11:41:02 [ERROR] order-service create order failed: java.lang.NullPointerException",
            "2026-04-20 11:41:03 [ERROR] order-service db timeout when inserting order record",
            "2026-04-20 11:41:04 [WARN] order-service retry failed, return HTTP 500"
        ]
    }


def get_system_metrics(node_ip: str = "", start_time: str = "", end_time: str = ""):
    if not start_time:
        start_time = (datetime.now() - timedelta(minutes=30)).strftime("%Y-%m-%d %H:%M:%S")
    if not end_time:
        end_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

    return {
        "node_ip": node_ip,
        "time_range": {
            "start_time": start_time,
            "end_time": end_time
        },
        "metrics": {
            "cpu_usage": "91%",
            "memory_usage": "93%",
            "disk_usage": "58%",
            "network_rx": "110MB/s",
            "network_tx": "76MB/s"
        }
    }


TOOL_MAP = {
    "get_pod_status": get_pod_status,
    "get_business_log": get_business_log,
    "get_system_metrics": get_system_metrics,
}

定义工具描述

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_pod_status",
            "description": "获取 Kubernetes Pod 的运行状态、重启次数和事件信息,用于排查 Pod 异常重启、启动失败、探针失败、CrashLoopBackOff 等问题。",
            "parameters": {
                "type": "object",
                "properties": {
                    "namespace": {
                        "type": "string",
                        "description": "Pod 所在命名空间,默认 default"
                    },
                    "pod_name": {
                        "type": "string",
                        "description": "Pod 名称或者工作负载名称"
                    }
                },
                "required": ["pod_name"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_business_log",
            "description": "获取业务服务日志,用于排查接口报错、HTTP 500、代码异常、数据库超时、调用下游失败等问题。",
            "parameters": {
                "type": "object",
                "properties": {
                    "service_name": {
                        "type": "string",
                        "description": "服务名称,比如 order-service"
                    },
                    "start_time": {
                        "type": "string",
                        "description": "查询开始时间,格式 YYYY-MM-DD HH:MM:SS"
                    },
                    "end_time": {
                        "type": "string",
                        "description": "查询结束时间,格式 YYYY-MM-DD HH:MM:SS"
                    }
                },
                "required": ["service_name"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_system_metrics",
            "description": "获取节点操作系统监控数据,包括 CPU、内存、磁盘和网络使用情况,用于排查节点负载高、资源瓶颈、系统卡顿等问题。",
            "parameters": {
                "type": "object",
                "properties": {
                    "node_ip": {
                        "type": "string",
                        "description": "节点 IP 地址"
                    },
                    "start_time": {
                        "type": "string",
                        "description": "查询开始时间,格式 YYYY-MM-DD HH:MM:SS"
                    },
                    "end_time": {
                        "type": "string",
                        "description": "查询结束时间,格式 YYYY-MM-DD HH:MM:SS"
                    }
                },
                "required": ["node_ip"]
            }
        }
    }
]

模型决定调用

下面这段代码,是整篇文章最核心的部分。

  • 把用户问题和工具列表发给模型
  • 如果模型决定调用函数,就执行对应函数
  • 把函数执行结果再喂给模型,让它输出最终结论
import json
from litellm import completion


def run_agent(user_query: str):
    messages = [
        {
            "role": "system",
            "content": (
                "你是一个线上故障排查助手。"
                "请根据用户问题自主选择最合适的工具。"
                "如果一个工具不足以定位问题,可以继续调用其他工具。"
                "回答时要基于工具返回的事实,不要编造。"
            )
        },
        {
            "role": "user",
            "content": user_query
        }
    ]

    while True:
        response = completion(
            model="doubao-seed-2.0-pro",
            messages=messages,
            tools=tools,
            tool_choice="auto"
        )

        message = response.choices[0].message
        tool_calls = message.tool_calls

        if not tool_calls:
            return message.content

        messages.append(message)

        for tool_call in tool_calls:
            tool_name = tool_call.function.name
            tool_args = json.loads(tool_call.function.arguments or "{}")

            print(f"[TOOL] {tool_name} args={tool_args}")

            tool_func = TOOL_MAP[tool_name]
            tool_result = tool_func(**tool_args)

            messages.append(
                {
                    "role": "tool",
                    "tool_call_id": tool_call.id,
                    "name": tool_name,
                    "content": json.dumps(tool_result, ensure_ascii=False)
                }
            )

验证结果

选取了4个不同的问题,查看llm调用函数的情况

if __name__ == "__main__":
    test_queries = [
        "我的 order-service Pod 一直重启,帮我看看怎么回事",
        "下单接口一直返回 500,帮我查一下日志",
        "节点 10.10.1.23 最近特别卡,帮我看下是不是资源打满了",
        "order-service 返回 500,而且 Pod 也反复重启,帮我整体分析一下"
    ]

    for idx, query in enumerate(test_queries, start=1):
        print(f"\n===== 测试 {idx} =====")
        print(f"[USER] {query}")
        answer = run_agent(query)
        print(f"[FINAL] {answer}")

参考输出

并不是每一次运行都是同样的内容,这里只展现本次运行llm的输出

===== 测试 1 =====
[USER] 我的 order-service Pod 一直重启,帮我看看怎么回事
[TOOL] get_pod_status args={'pod_name': 'order-service'}
[TOOL] get_business_log args={'service_name': 'order-service', 'start_time': '2026-04-20 11:30:00', 'end_time': '2026-04-20 11:45:00'}
[TOOL] get_system_metrics args={'node_ip': '10.10.1.23', 'start_time': '2026-04-20 11:30:00', 'end_time': '2026-04-20 11:45:00'}
[FINAL] 经过排查,order-service Pod 持续重启的原因如下:
### 根因分析
1. **Pod 进程被强制终止**:Pod 状态为 CrashLoopBackOff,累计重启5次,进程终止原因是 `OOMKilled`(内存溢出被系统Kill),进程退出后导致就 绪探针探测失败报 connection refused,触发自动重启。
2. **业务代码异常**:服务运行时存在业务报错,包括空指针异常、订单数据插入数据库超时、重试失败返回HTTP 500,这类异常会导致服务资源占用异 常升高,是引发内存溢出的诱因之一。
3. **节点资源瓶颈**:Pod 所在节点10.10.1.23在故障时间段CPU使用率达91%、内存使用率达93%,节点本身负载极高,进一步加剧了Pod的资源紧张问题 ,加速触发OOMKill。

### 修复建议
1. 优先修复业务代码中的空指针异常、数据库超时问题,优化资源占用逻辑;
2. 调整 order-service Pod 的内存limit配置,适当调高内存上限;
3. 可将该Pod临时调度到其他低负载节点运行,同时对10.10.1.23节点进行资源扩容或者清理节点上多余的负载。

===== 测试 2 =====
[USER] 下单接口一直返回 500,帮我查一下日志
[TOOL] get_business_log args={'service_name': 'order-service'}
[FINAL] 根据查询order-service服务的业务日志,下单接口返回500的原因已经定位到:
1. 订单创建逻辑触发了Java空指针异常(NullPointerException)
2. 插入订单记录到数据库时出现数据库超时问题
3. 超时重试多次失败,最终接口返回HTTP 500错误

如果需要进一步排查数据库超时的根因,可以提供数据库所在节点的IP查询系统资源负载,也可以查询order-service服务的Pod运行状态确认是否存在服务本身资源不足、异常重启等问题。

===== 测试 3 =====
[USER] 节点 10.10.1.23 最近特别卡,帮我看下是不是资源打满了
[TOOL] get_system_metrics args={'node_ip': '10.10.1.23'}
[FINAL] 根据查询到的节点10.10.1.23最近30分钟的系统监控数据:
1. CPU使用率达91%,内存使用率达93%,二者均处于极高负载状态,这是导致节点卡顿的核心原因
2. 磁盘使用率为58%,处于正常水平
3. 网络入流量110MB/s、出流量76MB/s,也处于较高负载水平

当前该节点确实是CPU和内存资源几乎被打满导致运行卡顿,建议尽快排查占用资源过高的进程或Pod,进行资源扩容或者异常进程查杀。

===== 测试 4 =====
[USER] order-service 返回 500,而且 Pod 也反复重启,帮我整体分析一下
[TOOL] get_pod_status args={'pod_name': 'order-service'}
[TOOL] get_business_log args={'service_name': 'order-service', 'start_time': '2026-04-20 11:30:00', 'end_time': '2026-04-20 11:45:00'}
[TOOL] get_system_metrics args={'node_ip': '10.10.1.23', 'start_time': '2026-04-20 11:30:00', 'end_time': '2026-04-20 11:45:00'}
[FINAL] ### 故障整体分析结论
#### 1. Pod反复重启原因
order-service的Pod当前处于`CrashLoopBackOff`状态,累计重启5次,直接原因是**内存溢出被系统杀死(OOMKilled)**,同时Pod就绪探针检测失败( 连接被拒绝),进一步触发了Pod反复重启。
所在节点`10.10.1.23`的资源水位极高:CPU使用率91%、内存使用率93%,节点资源瓶颈是触发Pod OOM的重要诱因。

#### 2. 接口返回500原因
业务日志显示故障时间段内存在两类错误:
- 代码层面存在空指针异常(`java.lang.NullPointerException`)
- 订单插入操作触发数据库超时,重试失败后直接返回HTTP 500错误。

#### 临时恢复建议
1. 优先调整order-service的容器内存配额,同时调度到资源充足的节点
2. 修复代码空指针问题,优化数据库插入逻辑(增加超时时间/分库分表/异步插入)
3. 对`10.10.1.23`节点进行负载清退,降低节点资源使用率。

4组问题,llm选择了不同的、它自主决策的函数来解决对应的问题。在多函数场景里,模型并不是瞎选,而是很像运维工程师排查问题一样:

  • 先判断问题在哪一层
  • 再决定调哪个工具
  • 如果证据不够,就继续补证据
  • 最后基于事实做总结

illustration-02

原理解析

LLM 语义匹配

illustration-03

LLM 在多函数场景下的选择逻辑,本质上是拿用户问题的语义去匹配函数描述的语义,LLM会关注用户当前到底在问什么,每个函数的作用、输入参数、适 用场景分别是什么

然后模型会判断:

  • 哪个函数最像解决这个问题的工具
  • 需不需要调用函数
  • 调用时参数该怎么补齐
  • 如果一个函数不够,后面还要不要继续调别的函数

function call 的关键不只是有没有函数,更关键的是怎么描述函数,函数写得像说明书,模型就选得准,函数写得像谜语人,模型就容易整活

到这里,有为老哥可能还会继续追问:所谓的语义匹配,到底匹配的是啥?

模型在决策时,通常会综合下面几个维度:

  • 问题里的关键词和上下文:

    • “Pod 重启” 更容易命中 get_pod_status
    • “接口 500” 更容易命中 get_business_log
    • “节点很卡” 更容易命中 get_system_metrics
  • 函数 description 的场景描述,如果你把“用于排查什么问题”写得清楚,模型就更容易做出准确判断

  • 参数是否容易从当前上下文中提取,比如用户明确说了 10.10.1.23等操作系统类的资源,那模型就更容易触发监控工具

  • 系统提示词里的行为约束,如果你在 system prompt 里明确要求:先查事实,再下结论;必要时可以多次调用工具,模型一般会严谨许多

function call 的效果,不是只取决于模型本身,也取决于你的工具定义和提示词设计

让模型更聪明

如果你发现模型老是选错函数,先排查下面几个点

illustration-04

  • 1)description 写得太短,这是最常见的问题

    • 不要只写:

      • 获取日志
      • 获取监控
    • 而要写成:

      • 获取业务服务日志,用于排查接口报错、代码异常、数据库超时等问题
      • 获取节点操作系统监控数据,用于排查 CPU、内存、磁盘、网络资源瓶颈问题
  • 2)函数边界不清晰,如果两个函数看起来都像什么都能查,模型就容易纠结,所以工具设计最好做到:

    • 每个函数负责一类证据
    • 不同函数之间边界清楚
    • 尽量不要让多个函数 description 大面积重叠
  • 3)参数名太抽象,像这种参数:id name target,看着万能,实际上很坑,应该改为具体指某一类资源,实在有重名,那就疯狂叠下划线pod_name service_name node_ip,参数名越具体,模型越不容易理解歪。

  • 4)提示词没告诉模型:可以连续调用多个工具,如果 system prompt 没写这句话,模型有时会保守,只调一个函数就停,所以建议明确告诉它:

    • 如果一个工具不足以定位问题,可以继续调用其他工具
    • 回答必须基于工具返回的事实
    • 不要在没有证据时直接下结论
  • 5)最后就是选择llm,应该选择那种更擅长推理的,参数更多的llm

总结

多个函数 同时存在时,LLM 不是随机选函数,而是在做语义匹配和工具决策

在排障场景里,这个能力尤其有价值,因为问题往往横跨 Pod、应用、系统多个层面。你不需要再维护一大堆僵硬的 if/else 规则,只需要把工具边界、description、参数定义写清楚,模型就能像一个会查证据的助手一样,自己决定先调哪个函数,必要时再继续补充调用

当然,真要上生产,审计、超时、权限、限流这些工程能力,一样都不能省

联系我

  • 联系我,做深入的交流

至此,本文结束

在下才疏学浅,有撒汤漏水的,请各位不吝赐教...