Java已死?别慌,看我如何用Java手写一个Qwen Code Agent,拯救Java

从通义灵码、CodeBuddy等编程助手,到Cursor、Trae等AI编程IDE,再到Claude Code、Gemini Cli、Qwen Code等命令行工具,AI编程Agent这个赛道一直很热闹,那么这些AI Agent是如何实现的呢?这篇文章我来介绍一下我用Java实现出来的一个简单版的AI编程Agent,你可以理解为是一个简单版本的Qwen Code。

为什么用Java?其实我也用Python写了一版,也没别的原因,就想试试Java到底行不行...

ReAct

首先介绍一下什么是ReAct,ReAct拆开就是Reason+Action,也就是思考+行动。为什么需要ReAct? 第一个原因是LLM只能输出文字,没有行动能力,比如不能创建文件、写文件、读文件,因此我们需要给LLM提供工具,让它有行动能力。 第二个原因是LLM不管是解决简单问题还是复杂问题,都是一次性给出答案,但实际情况是,对于复杂问题往往需要分阶段思考、分阶段行动会更好。

ReAct核心就是一个循环:

  1. 思考:根据问题,给出一个解决问题的计划,然后执行计划中的第一步
  2. 行动:针对当前步骤,分析出要执行哪些工具,工具入参是什么
  3. 观察:观察工具执行结果,将结果信息返回给大模型,模型继续思考和行动。

这个"思考-行动-观察"的循环不断重复,直到任务完成,有点抽象,我们来看具体代码。

第一步:配置

首先看看配置,配置DASHSCOPE平台的API_KEY、BASE_URL和我们要使用的LLM。

arduino 复制代码
/**
 * 需要完整代码可以联系我,微信号:it_zhouyu
 */
public class ModelConfig {
    public static final String API_KEY = System.getenv("DASHSCOPE_API_KEY");
    public static final String BASE_URL = "<https://dashscope.aliyuncs.com/compatible-mode/v1>";
    public static final String LLM_NAME = "qwen-plus";
}

第二步:定义工具

然后定义Agent需要的工具,在AgentTools中可以定义多个工具,@Tool和@ToolParam都是我自己定义的,并没有用Spring AI。

typescript 复制代码
/**
 * 需要完整代码可以联系我,微信号:it_zhouyu
 */
public class AgentTools {

    @Tool(description = "将指定内容写入本地文件。")
    public String writeFile(@ToolParam(description = "包含 'file_path' 和 'content' 的 JSON 字符串。") String jsonInput) {
        try {
            JsonNode rootNode = objectMapper.readTree(jsonInput);
            String filePath = rootNode.get("file_path").asText();
            String content = rootNode.get("content").asText();
            try (FileWriter writer = new FileWriter(filePath)) {
                writer.write(content);
                return String.format("成功将内容写入文件 '%s'。", filePath);
            } catch (IOException e) {...}
        } catch (Exception e) {...}
    }
}

这里的关键是注解@Tool@ToolParam

  • @Tool(description = "..."): 用来描述工具的作用,当有多个工具时,LLM能根据每个工具的描述来选择该用哪个工具来完成当前任务。
  • @ToolParam(description = "..."): 用来描述参数的用途,当调用工具时,LLM需要根据参数描述来构造输入数据。

第三步:构建核心ReAct提示词

ReAct中最核心的就是提示词,因为我们需要LLM能够按照我们需要的格式返回,然后我们再通过Java代码来按格式进行解析,比如解析出该调用哪个工具,也就是该调用AgentTools中的哪个方法,传什么参数,以下是我的提示词:

arduino 复制代码
/**
 * 需要完整代码可以联系我,微信号:it_zhouyu
 */
private static final String REACT_PROMPT_TEMPLATE = """
        你是一个强大的 AI 助手,通过思考和使用工具来解决用户的问题。

        你的任务是尽你所能回答以下问题。你可以使用以下工具:

        {tools}

        请严格遵循以下规则和格式:
        1. 你的行动必须基于一个清晰的"Thought"过程。
        2. 你必须按顺序使用 "Thought:", "Action:", "Action Input:"。
        3. 在每次回复中,你只能生成 **一个** Thought/Action/Action Input 组合。
        4. **绝对不要** 自己编造 "Observation:"。系统会在你执行动作后,将真实的结果作为 Observation 提供给你。
        5. 当你拥有足够的信息来直接回答用户的问题时,请使用 "Final Answer:" 来输出最终答案。
        6. 在每次回复中,"Thought:", "Action:", "Action Input:"和"Final Answer:"不能同时出现。

        下面是你的思考和行动格式:
        Thought: 我需要做什么来解决问题?下一步是什么?
        Action: 我应该使用哪个工具?必须是 [{tool_names}] 中的一个。
        Action Input: 我应该给这个工具提供什么输入?这必须是一个 JSON 对象。

        --- 开始 ---

        Question: {input}
        {agent_scratchpad}
        """;

其中:

  • {tools} : 占位符,用来填充所有的工具信息,也就是@Tool、@ToolParam中的内容
  • {tool_names} : 占位符,用来填充所有工具的名字
  • {input} : 占位符,用户的问题
  • {agent_scratchpad} : 占位符,用来记录之前所有步骤的"思考-行动-观察"历史,帮助LLM做出符合上下文的决策。

这里重点讲一下第4点和第6点:

  • 第4点,是因为我在测试时,发现模型有时候会自己模拟工具的执行,编造一个假的工具执行结果,导致没有真正的执行工具。
  • 第6点,是因为我在测试时,发现模型有时候会既给出行动指令又给出最终答案,这其实是矛盾的,所以得要求它不要既给出行动指令又给出最终答案。

第四步:实现ReAct核心循环

这块代码比较多,贴在文章里也不方便阅读,这里就只给出实现思路,大家想要完整代码可以加我微信领取,微信在文末。

  1. 外层是一个循环,设置一个maxIterations,因为不能让ReAct无限循环
  2. 填充提示词,解析工具信息,以及用户的问题需求,填充到REACT_PROMPT_TEMPLATE提示词模版中,得到完整的提示词
  3. 调用LLM,得到LLM原始输出
  4. 先判断原始输出中是否有Final Answer,如果有,则直接返回最终答案,结束循环
  5. 再判断原始输出中,是否有Thought、Action和Action Input,如果有,则提取出要执行的方法名和入参对象
  6. 执行工具,并得到工具结果Observation
  7. 然后将当前步骤的Thought、Action、Action Input、Observation等信息,保存到agentScratchpad中,用于下一次循环的输入
  8. 进行下一次循环,直到达到maxIterations次数或者LLM返回了Final Answer

第五步:组装Agent

最后将Agent组装起来就可以用了:

java 复制代码
/**
 * 需要完整代码可以联系我,微信号:it_zhouyu
 */
public class Main {
    public static void main(String[] args) {
        // 1. 初始化 API 客户端
        OpenAIClient apiClient = ...;

        // 2. 创建 AgentTools 实例
        AgentTools agentTools = new AgentTools();

        // 3. 创建 ReActAgent 实例
        ReActAgent agent = new ReActAgent(apiClient, agentTools);

        // 4. 定义问题并运行 Agent
        String question = "请帮我用HTML、CSS、JS创建一个简单的贪吃蛇游戏,分成三个文件,分别是snake.html、snake.css、snake.js";
        String finalResult = agent.run(question, 10);

        // 5. 打印最终结果
        System.out.println("AGENT 执行结束,最终结果为:");
        System.out.println(finalResult);
    }
}

总结

以上就是我手写的简单版的Qwen Code Agent的核心思路,虽然还不能投入生产使用,但对于大家理解AI Agent核心原理、什么是ReAct、如何开发一个Agent应该都有帮助,希望能得到大家的点赞、分享、关注,最后给出我的联系方式,需要完整代码的可以加我微信号:it_zhouyu。

相关推荐
lssjzmn4 小时前
性能飙升!Spring异步流式响应终极指南:ResponseBodyEmitter实战与架构思考
java·前端·架构
机器之心4 小时前
国内外AI大厂重押,初创梭哈,谁能凭「记忆」成为下一个「DeepSeek」?
人工智能·openai
LiuYaoheng5 小时前
【Android】View 的基础知识
android·java·笔记·学习
时序之心5 小时前
覆盖Transformer、GAN:掩码重建正在重塑时间序列领域!
人工智能·深度学习·生成对抗网络·transformer·时间序列
勇往直前plus5 小时前
Sentinel微服务保护
java·spring boot·微服务·sentinel
星辰大海的精灵5 小时前
SpringBoot与Quartz整合,实现订单自动取消功能
java·后端·算法
小鸡脚来咯5 小时前
一个Java的main方法在JVM中的执行流程
java·开发语言·jvm
江团1io05 小时前
深入解析三色标记算法
java·开发语言·jvm
机器之心5 小时前
OpenAI罕见发论文:我们找到了AI幻觉的罪魁祸首
人工智能·openai