“我”:Java/Go后端开发者、有点时间想自己琢磨,想入门Agent但不想堆砌框架、希望理解底层原理的研发
上一篇链接:Java/Go后端手撸原生Agent(第三篇)

前言

在这里插入图片描述

前三篇文章我们从零搭建了ReAct智能体,完成了Pydantic结构化JSON输出、工具Schema自动生成、三态状态机驱动循环、文件读取工具扩展、重复调用代码层拦截等能力。但跑多工具多问题场景时暴露了三个架构级问题:

  1. 工具调用靠JSON Mode+Prompt约束:我们让LLM输出JSON字符串,自己解析action/params字段,工具描述塞在System Prompt里用自然语言教模型格式——这本质是用文本约束模拟工具调用协议,模型经常输出格式错误、参数名编造,稳定性差;
  2. 工具结果污染System消息add_observationrole="system"存储工具返回值,system消息既承载全局规则又承载动态数据,随着轮次增长原始规则被"推远",LLM注意力稀释忘记规则;
  3. FINISH转移无门禁:模型输出无tool_calls的content就直接FINISH,多问题场景下经常只回答一半就"交卷",靠Prompt写"请完整回答"完全不可靠。

本文完成三大架构升级,是本系列至今认知密度最高的一篇:

  1. 切换到OpenAI原生Function Calling协议:API层面传tools Schema,响应解析message.tool_calls数组,告别Prompt里教格式、告别脆弱的JSON字符串解析;
  2. Memory Role语义修正:新增add_tool_result(tool_call_id, content)用role=tool存工具结果,tool_call_id精确配对调用和结果,assistant消息携带tool_calls数组,system消息不再存入memory;
  3. LLM-as-Judge FINISH Guard:最终回答经一次不带tools的LLM裁判校验完整性,不通过则注入[Judge反馈]纠偏消息回退重答,校验通过后才正式落盘到memory。

前置说明

  1. 改造文件:llm_client.py(适配原生Function Calling请求/响应)、agent/memory.py(role修正+tool_call_id支持)、main.py(四态状态机+Judge门禁);
  2. 删除文件:agent/schema.py中的ToolAction/FinishResponse/StructuredParser——原生FC下不再需要自定义JSON解析,模型直接返回结构化tool_calls;
  3. 工具层(tools/base_tool.pytools/calculator.pytools/file_reader.py)零改动,Phase 2预留的to_openai_tool_schema()正好被原生FC用上。

一、问题复现与根因分析

1.1 痛点1:JSON Mode模拟工具调用,稳定性差

上一版的做法:System Prompt里用一大段自然语言教模型"两种输出格式严格二选一",模型返回JSON字符串,StructuredParserjson.loads+字典键判断解析。

问题:

  • 格式漂移:模型偶尔在JSON外加markdown代码块、加注释文字,解析直接失败;
  • 参数幻觉:模型编造不在schema里的参数名(比如给calculator加个method字段);
  • 工具描述靠Prompt:工具列表、参数说明全写在System Prompt里自然语言描述,模型"看懂了但不一定按格式来"。

根因:我们在"请求"LLM按指定格式输出,但OpenAI API提供了原生Function Calling能力——你把Schema传给tools参数,模型是在微调阶段就学会了怎么输出结构化的tool_call,这是协议层约束,不是文本层约束。

1.2 痛点2:工具结果role=system,消息语义混乱

上一版:

def add_observation(self, obs: str):
    self.history.append({"role": "system", "content": f"[{tool_name}] 返回结果:{obs}"})

问题:system role在OpenAI协议中的语义是"不可变的全局指令",放在messages最前面权重最高。把工具执行结果塞进去会导致:

  • system消息越来越长,原始规则被工具数据稀释,长上下文下LLM"看不到"最前面的规则;
  • 没有tool_call_id,一次对话多个工具调用时结果和调用对不上;
  • role语义混乱——system是规则,tool是数据,混在一起模型分不清哪些是必须遵守的、哪些是参考数据。
1.3 痛点3:FINISH无门禁,回答完整性靠模型自觉

上一版状态机:模型输出无tool_calls的content → 直接FINISH返回。多问题场景(“读文件+解释方法+计算行数”)模型经常解释完方法忘了计算,或者算出数忘了解释——Prompt写"请完整回答所有问题"是规劝不是保证。

根因:和参数校验、重复调用检测一样的问题——把正确性托付给LLM自觉,没有代码层门禁。


二、改造1:LLM客户端适配原生Function Calling

2.1 设计思路

OpenAI Chat Completions API的原生工具调用流程:

  1. 请求时传tools参数(工具Schema数组,格式由API定义),不传response_format: {"type":"json_object"}
  2. 模型如果要调用工具,响应里message.tool_calls是一个数组,每个元素有id(唯一调用ID)、function.name(工具名)、function.arguments(JSON字符串格式的参数);
  3. 你执行工具后,必须回传两条消息:
    • 原assistant消息(携带tool_calls数组)
    • 一条或多条role="tool"消息,每条带tool_call_id对应一个调用
  4. 模型收到tool结果后继续推理,直到输出无tool_calls的content表示完成。

关键认知:tool_call_id是配对的锚点,模型靠它精确知道"这条工具结果是回应我哪个调用",多工具并行时靠id区分。

2.2 重写llm_client.py
"""
LLM调用客户端(Phase 4:适配原生Function Calling协议)

核心变化:
1. 返回值从str变为LLMResponse对象,同时承载content文本和tool_calls数组;
2. 支持tools参数传入OpenAI Function Schema;
3. 响应解析从只取message.content扩展到同时解析message.tool_calls;
4. ToolCall模型封装id/name/arguments,提供to_openai_dict()用于回传messages。
"""
from __future__ import annotations

import json
import os
from typing import Any, Optional

from dotenv import load_dotenv
from openai import OpenAI
from pydantic import BaseModel, Field

load_dotenv()


class ToolCall(BaseModel):
    """一个工具调用,对应OpenAI message.tool_calls[i]"""
    id: str = Field(description="工具调用唯一ID,由API生成,tool结果必须带这个id回传")
    name: str = Field(description="要调用的工具名")
    arguments: dict[str, Any] = Field(description="工具参数字典(已从JSON字符串解析为dict)")

    def to_openai_dict(self) -> dict:
        """序列化为OpenAI messages数组中的tool_calls元素格式"""
        return {
            "id": self.id,
            "type": "function",
            "function": {
                "name": self.name,
                "arguments": json.dumps(self.arguments, ensure_ascii=False),
            },
        }


class LLMResponse(BaseModel):
    """LLM响应统一封装"""
    content: Optional[str] = Field(default=None, description="文本内容(直接回答或推理过程)")
    tool_calls: Optional[list[ToolCall]] = Field(default=None, description="工具调用列表,None表示无工具调用")

    @property
    def has_tool_calls(self) -> bool:
        return self.tool_calls is not None and len(self.tool_calls) > 0


def chat_completion(
    messages: list[dict],
    tools: Optional[list[dict]] = None,
    json_mode: bool = False,
) -> LLMResponse:
    """
    调用LLM,返回LLMResponse。
    - messages: OpenAI格式消息数组
    - tools: OpenAI Function Schema数组,传了就走工具调用模式
    - json_mode: True时强制JSON输出(Judge校验用),tools模式下不要设为True
    """
    client = OpenAI(
        api_key=os.getenv("LLM_API_KEY"),
        base_url=os.getenv("LLM_BASE_URL"),
    )

    kwargs: dict[str, Any] = {
        "model": os.getenv("LLM_MODEL_NAME"),
        "messages": messages,
        "temperature": 0.2,
    }

    if tools is not None:
        kwargs["tools"] = tools
        kwargs["tool_choice"] = "auto"

    if json_mode:
        kwargs["response_format"] = {"type": "json_object"}

    resp = client.chat.completions.create(**kwargs)
    msg = resp.choices[0].message

    result = LLMResponse(content=msg.content)

    if msg.tool_calls:
        parsed_calls = []
        for tc in msg.tool_calls:
            try:
                args_dict = json.loads(tc.function.arguments)
            except json.JSONDecodeError:
                args_dict = {}
            parsed_calls.append(ToolCall(
                id=tc.id,
                name=tc.function.name,
                arguments=args_dict,
            ))
        result.tool_calls = parsed_calls

    return result

关键设计点:

  • ToolCall.id:API生成的唯一标识,后续存tool结果时必须带回这个id,这是配对的关键;
  • to_openai_dict():assistant消息存入memory时需要序列化回OpenAI格式,tool_calls数组里的arguments是字符串不是dict(API协议要求);
  • has_tool_calls属性:主循环判断分支用,替代之前的isinstance(parse_res, ToolAction)
  • toolsjson_mode不同时使用:Function Calling模式下不传response_format,Judge校验时不传tools但传json_mode,两种模式互斥。

三、改造2:Memory Role语义修正

3.1 设计思路

对齐OpenAI消息协议,四种role严格分离:

Role 用途 存入Memory?
system 全局指令(SYSTEM_PROMPT) ❌ 每轮THINKING单独拼接,始终在messages最前端
user 用户输入+框架注入的前缀标记反馈([Judge反馈]/[系统提示]
assistant LLM输出,可携带tool_calls数组
tool 工具执行结果,带tool_call_id

核心认知:system不存入memory。之前把system连同工具结果一起append导致规则被稀释,现在system每轮独立拼在第一条,位置权重最高。

3.2 重写agent/memory.py
"""
短期对话记忆模块(Phase 5:role语义修正)

消息role严格对齐OpenAI Chat Completions API协议:
- system: 不存入memory(每轮THINKING单独拼接在最前)
- user: 用户输入,以及带前缀标记的框架反馈([Judge反馈]、[系统提示])
- assistant: LLM输出,可携带tool_calls数组
- tool: 工具执行结果,带tool_call_id精确关联调用
"""
from __future__ import annotations

from typing import Any, Optional


class ShortMemory:
    """短期对话记忆,消息格式直接对齐OpenAI API"""

    def __init__(self):
        self.history: list[dict[str, Any]] = []

    def add_user(self, content: str):
        """添加user角色消息"""
        self.history.append({"role": "user", "content": content})

    def add_assistant(self, content: Optional[str], tool_calls: Optional[list[dict]] = None):
        """
        添加assistant消息。
        - 直接回答:content=回答文本, tool_calls=None
        - 工具调用:content=思考文本(可为None), tool_calls=OpenAI格式工具调用数组
        """
        msg: dict[str, Any] = {"role": "assistant", "content": content}
        if tool_calls:
            msg["tool_calls"] = tool_calls
        self.history.append(msg)

    def add_tool_result(self, tool_call_id: str, content: str):
        """
        添加工具执行结果(role=tool + tool_call_id)。
        tool_call_id必须与assistant消息中某条tool_call的id完全一致,
        LLM靠id精确配对"哪个结果对应哪个调用"。
        """
        self.history.append({
            "role": "tool",
            "tool_call_id": tool_call_id,
            "content": content,
        })

    def get_messages(self) -> list[dict]:
        """返回全部历史消息(浅拷贝),可直接拼入API请求"""
        return [m.copy() for m in self.history]

    def remove_last(self) -> Optional[dict]:
        """移除最后一条消息并返回,用于撤销未通过校验的草稿"""
        if self.history:
            return self.history.pop()
        return None

和上一版的对比:

  • 删除了add_observation(role=system存工具结果)→ 新增add_tool_result(tool_call_id, content)用role=tool;
  • add_assistant新增可选tool_calls参数,工具调用场景下把OpenAI格式的tool_calls数组一并存入;
  • system消息不再存入history,get_messages()只返回user/assistant/tool三种消息;
  • remove_last()提供撤销能力,FINISH Guard校验不通过时可以撤销未通过的草稿(虽然最终版本没用这个,改为"校验通过后才落盘"的策略)。

四、改造3:四态状态机+LLM-as-Judge FINISH Guard

4.1 为什么是四态而不是三态

上一版状态机:THINKING / TOOL_EXECUTING / FINISHED。问题是"模型输出final answer"直接转移到FINISHED,中间没有校验环节。新增VALIDATING状态:

THINKING ──(has tool_calls)──→ TOOL_EXECUTING ──(执行完)──→ THINKING
    │                                                       ↑
    │(no tool_calls, 草稿回答)                                │
    ↓                                                       │
VALIDATING ──(Judge不通过, 注入纠偏)────────────────────────→┘
    │
    │(Judge通过)
    ↓
 FINISHED

VALIDATING的本质:在THINKING→FINISHED的转移路径上加一个门禁状态,不通过就回退。这和参数校验在工具调用前做校验、重复调用检测在工具执行前做拦截是同一套设计哲学。

4.2 LLM-as-Judge为什么比规则门禁好

最初我用硬编码规则做门禁:

if "calculator" in invoked_tools and not has_number(answer):  # 拦截
if "read_file" in invoked_tools and len(answer) < 40:       # 拦截

问题很明显:每加一个工具就要加规则,不可扩展。LLM-as-Judge用一次不带tools的LLM调用做裁判,输入(用户问题, AI回答),输出结构化判断{passed: bool, missing: str}

  • 通用性:不需要知道有哪些工具,直接理解语义差距;
  • 精确反馈:不只是"不通过",还指出具体缺什么(“缺少对execute方法的解释”),执行LLM拿到精确反馈补答;
  • 成本低:Judge用json_mode,输入短输出固定,通常<200 input tokens + <50 output tokens。
4.3 Final answer先校验后落盘

一个关键细节:之前assistant消息在THINKING分支里不管什么情况都立刻写入memory。但如果模型输出的是不合格的final answer(被Judge打回),这个草稿不应该留在对话历史里——否则模型"看到自己已经回答了"就不会认真补答。

策略:

  • tool_calls的assistant消息:立刻写入memory(因为tool结果必须紧跟在assistant消息后面配对);
  • final answer的assistant消息:不立即写入,存到pending_draft_answer,VALIDATING通过后才add_assistant正式落盘。
4.4 完整main.py
"""
Phase 4+5+6:原生Function Calling + Memory Role修正 + LLM-as-Judge FINISH Guard
"""
from __future__ import annotations

import json
from enum import Enum
from typing import Optional

from pydantic import BaseModel, Field

from agent.memory import ShortMemory
from llm_client import ToolCall, chat_completion
from tools.base_tool import BaseTool
from tools.calculator import CalcTool
from tools.file_reader import FileReadTool


class AgentTaskState(Enum):
    THINKING = "thinking"
    TOOL_EXECUTING = "tool_executing"
    VALIDATING = "validating"
    FINISHED = "finished"


tool_list: list[BaseTool] = [CalcTool(), FileReadTool()]
tool_map = {t.name: t for t in tool_list}


SYSTEM_PROMPT = """你是一个智能代码助手,可以使用提供的工具来帮助用户完成任务。

规则:
1. 需要获取外部信息(读取文件、计算数值等)时,调用相应工具;
2. 仔细阅读工具返回结果,基于结果进行推理;
3. 信息足够回答用户问题时,直接用清晰的自然语言回答,不要调用不必要的工具;
4. 如果用户要求数值计算,必须使用calculator工具,不要心算;
5. 如果工具返回错误信息,分析错误原因,可以换参数重试或告知用户;
6. 回答要结构清晰、条理分明、完整覆盖用户的所有问题点,使用中文。
"""


# ========== LLM-as-Judge 完整性校验 ==========

class JudgeResult(BaseModel):
    """Judge的结构化输出,json_mode强约束"""
    passed: bool = Field(description="回答是否完整覆盖了用户的所有问题点")
    missing: str = Field(description="不通过时具体缺少什么;通过时为空字符串")


JUDGE_SYSTEM_PROMPT = """你是回答质量检查员。你的唯一任务是判断"AI助手的回答"是否完整覆盖了"用户的问题"中的所有要求。

判断标准:
- 用户问了几个问题/任务点,回答里每个都要有对应内容;
- 如果用户要求"必须调用工具计算",回答必须包含工具算出的具体数值结果;
- 如果用户要求"读取并解释代码/文件",回答必须包含对代码的实质解释,不能只是说"已读取";
- 回答允许有推理过程,但必须最终给出答案。

输出严格JSON格式:
{"passed": true/false, "missing": "具体缺失内容描述,passed为true时为空字符串"}

注意:
- 你只做检查,不回答用户问题本身;
- missing字段要具体指出缺什么(例如"缺少对execute方法的解释"、"没有给出计算结果"),不要泛泛说"回答不完整";
- 如果回答覆盖了所有问题点,passed=true,missing为空。
"""


def judge_answer(user_query: str, answer: str) -> JudgeResult:
    """用LLM裁判判断回答完整性"""
    # 兜底:极短回答直接判不通过,省一次LLM调用
    if not answer or len(answer.strip()) < 10:
        return JudgeResult(passed=False, missing="回答过短,没有实质内容。")

    messages = [
        {"role": "system", "content": JUDGE_SYSTEM_PROMPT},
        {"role": "user", "content": f"【用户问题】{user_query}\n\n【AI回答】{answer}\n\n请判断AI回答是否完整覆盖了用户的所有问题点。"},
    ]
    resp = chat_completion(messages, json_mode=True)
    if not resp.content:
        return JudgeResult(passed=True, missing="")  # Judge异常时保守通过(fail-open)
    try:
        data = json.loads(resp.content)
        return JudgeResult(**data)
    except Exception:
        return JudgeResult(passed=True, missing="")


def run_agent(user_query: str):
    memory = ShortMemory()
    memory.add_user(user_query)
    max_loop = 15

    state = AgentTaskState.THINKING
    loop_count = 0
    judge_retry_count = 0
    max_judge_retries = 3
    final_answer = None
    pending_tool_call: Optional[ToolCall] = None
    pending_draft_answer: Optional[str] = None  # 待校验草稿,通过后才落盘
    executed_calls: set[tuple[str, str]] = set()

    tools_schema = [t.to_openai_tool_schema() for t in tool_list]

    while state != AgentTaskState.FINISHED and loop_count < max_loop:
        loop_count += 1

        if state == AgentTaskState.THINKING:
            # system每轮独立拼接在最前,不存入memory
            messages = [{"role": "system", "content": SYSTEM_PROMPT}]
            messages.extend(memory.get_messages())

            print(f"\n=== 第{loop_count}轮 THINKING ===")

            resp = chat_completion(messages, tools=tools_schema)

            if resp.has_tool_calls:
                # 工具调用:assistant消息立刻写入(tool结果必须紧跟其后配对)
                tool_calls_dicts = [tc.to_openai_dict() for tc in resp.tool_calls]
                memory.add_assistant(content=resp.content, tool_calls=tool_calls_dicts)

                if resp.content:
                    thought = resp.content[:200] + ("..." if len(resp.content) > 200 else "")
                    print(f"【推理思考】{thought}")

                pending_tool_call = resp.tool_calls[0]  # 简化:每次处理一个工具调用
                state = AgentTaskState.TOOL_EXECUTING
                continue
            else:
                # 无tool_calls:模型尝试直接回答,先存为草稿,校验通过后才落盘
                pending_draft_answer = resp.content
                state = AgentTaskState.VALIDATING
                continue

        if state == AgentTaskState.TOOL_EXECUTING:
            call_key = (
                pending_tool_call.name,
                json.dumps(pending_tool_call.arguments, sort_keys=True, ensure_ascii=False),
            )

            if call_key in executed_calls:
                obs = (
                    f"[系统提示] 你已经用完全相同的参数调用过{pending_tool_call.name},"
                    f"结果已在上方消息中。请直接基于已有信息回答,不要重复调用。"
                )
                print(f"【重复调用拦截】{pending_tool_call.name} {pending_tool_call.arguments}")
            else:
                print(f"【工具调用】{pending_tool_call.name}("
                      f"{json.dumps(pending_tool_call.arguments, ensure_ascii=False)})")
                tool = tool_map.get(pending_tool_call.name)
                if not tool:
                    obs = f"错误:不存在工具{pending_tool_call.name}"
                else:
                    obs = tool.execute(pending_tool_call.arguments)
                display_obs = obs[:300] + ("..." if len(obs) > 300 else "")
                print(f"【工具返回】{display_obs}")
                executed_calls.add(call_key)

            # 工具结果通过role=tool + tool_call_id存入memory
            memory.add_tool_result(pending_tool_call.id, obs)

            pending_tool_call = None
            state = AgentTaskState.THINKING
            continue

        if state == AgentTaskState.VALIDATING:
            print("【校验】正在Judge回答完整性...")
            judge_res = judge_answer(user_query, pending_draft_answer)

            if judge_res.passed or judge_retry_count >= max_judge_retries:
                if judge_retry_count >= max_judge_retries and not judge_res.passed:
                    print(f"【警告】Judge已连续{max_judge_retries}次打回,强制通过防止死循环")
                else:
                    print("【校验通过】回答完整")
                # 校验通过:此时才把final answer正式写入memory
                memory.add_assistant(content=pending_draft_answer)
                final_answer = pending_draft_answer
                state = AgentTaskState.FINISHED
                break
            else:
                judge_retry_count += 1
                print(f"【门禁拦截】缺失: {judge_res.missing}")
                # 纠偏消息用role=user,带[Judge反馈]前缀标记
                correction = (
                    f"你的回答不完整,缺少以下内容:{judge_res.missing}\n"
                    f"请基于已有工具结果补充回答,不要重复调用已经调用过的工具。"
                    f"如果信息不足,可以调用尚未使用过的工具。"
                )
                memory.add_user(f"[Judge反馈] {correction}")
                pending_draft_answer = None
                state = AgentTaskState.THINKING
                continue

    if final_answer is None:
        final_answer = f"达到最大循环次数{max_loop},任务未完成"

    return final_answer


if __name__ == "__main__":
    answer = run_agent(
        "帮我读一下 tools/base_tool.py 的内容,"
        "然后解释execute和to_openai_tool_schema方法做了什么,"
        "这两个方法的行数加起来乘以4再除以2等于多少(必须调用工具计算)"
    )
    print("\n最终回答:", answer)

五、运行效果

多工具多问题场景完整链路(读文件+解释+计算):

=== 第1轮 THINKING ===
【推理思考】用户要求读取文件、解释方法、计算数值。首先读取文件。
【工具调用】read_file({"path": "tools/base_tool.py"})
【工具返回】[文件: .../tools/base_tool.py] 共56行...
   1 | from abc import ABC...
  28 |     def execute(self, params: dict) -> str:
  ...

=== 第2轮 THINKING ===
【推理思考】文件已读取,execute是28-33行(6行),to_openai_tool_schema是35-43行(9行)。
需要调用计算器计算 (6+9)*4/2。
【工具调用】calculator({"expr": "(6+9)*4/2"})
【工具返回】计算结果: (6+9)*4/2 = 30.0

=== 第3轮 THINKING ===
【校验】正在Judge回答完整性...
【校验通过】回答完整
最终回答: 已读取文件内容,两个方法说明如下:
- **execute方法**(第28-33行,共6行):BaseTool的模板方法...
- **to_openai_tool_schema方法**(第35-43行,共9行):生成OpenAI标准Function Call Schema...
两个方法共15行,乘以4再除以2等于30。

共3轮,无重复调用、无死循环、无遗漏子问题。


六、核心改造总结

维度 Phase 3(改造前) Phase 4+5+6(改造后)
工具调用方式 JSON Mode+Prompt自然语言约束+手动JSON解析 原生Function Calling协议(tools参数+tool_calls响应)
工具结果role role=system(污染全局指令) role=tool + tool_call_id(精确配对)
system消息管理 存入memory随轮次增长 每轮独立拼接,始终在最前端
assistant消息格式 {"role":"assistant","content":"..."}纯文本 可携带tool_calls数组,对齐API协议
FINISH转移门禁 无(无tool_calls即结束) VALIDATING状态+LLM-as-Judge校验
失败重试机制 Judge不通过注入[Judge反馈]精确纠偏,最多3次
回答落盘时机 生成即写入memory Judge通过后才写入(草稿不落盘)
添加新工具 要在Prompt里手写工具描述 工具类定义→注册到tool_list,零侵入
重复调用防护 executed_calls set 保留,拦截提示通过tool_result content传递

七、后续拓展

  1. 多工具并行调用:当前简化为每次只处理resp.tool_calls[0]一个调用,可扩展为遍历tool_calls数组并发执行多个工具,结果分别用对应tool_call_id存入;
  2. BashTool/FileWriteTool:扩展工具集为真正可用的Coding Agent(注意危险命令白名单、输出截断、权限沙箱);
  3. Plan-then-Execute模式:复杂任务先让LLM生成子任务计划,执行中跟踪进度,完成全部子任务才允许FINISH;
  4. 长期记忆/RAG:接入Chroma向量库,解决上下文窗口限制和跨会话知识积累;
  5. Hook/Callback机制:抽离AgentEngine类,支持before_llm/after_tool/on_state_change等事件钩子,方便加日志、trace、指标。

八、Java/Go后端快速语法映射

  • ToolCall(BaseModel) = Java POJO / Go struct,带ID和参数字段
  • Optional[str] = Java Optional<String> / Go *string(指针可空)
  • @property 抽象属性 = 接口getter方法
  • 四态状态机while+continue = State Pattern / 状态机DSL
  • LLM-as-Judge = AOP切面校验 / 中间件模式(请求经过校验层才放行)
  • tool_call_id配对 = 分布式链路traceId / 消息队列correlation_id
  • 模板方法execute(基类校验→子类run) = Servlet Filter / Interceptor模式
  • fail-open保守策略 = 熔断器降级(校验器异常时放行而非阻塞主流程)

下一篇:Java/Go后端手撸原生Agent(第五篇)

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐