红石中继站
    • 资源
    • 新帖
    • 版块
    • 热门
    • 登录

    BukkitGPT 开发日志: 添加 AI 编辑现有 Bukkit 插件的功能

    编程开发
    ai deepseek bukkit 插件
    1
    1
    130
    正在加载更多帖子
    • 从旧到新
    • 从新到旧
    • 最多赞同
    回复
    登录后回复
    此主题已被删除。只有拥有主题管理权限的用户可以查看。
    • BaimoQilinB
      BaimoQilin
      最后由 BaimoQilin 编辑 · 江苏省

      作者 / AUTHOR 白墨麒麟 BaimoQilin

      授权协议 / LICENSE CC-BY-NC-SA 署名-非商业性使用-相同方式共享

      撰稿日期 / DATE OF WRITING 2/12/2025 19:00

      原文链接 / LINK https://cynia.baimoqilin.com/logs/bukkitgpt-1-0-0-dev/

      明天就正式开学了 这这我还没玩够的怎么就初二下半学期了
      趁着上午报道回来、下午空闲的一小段时间更新了一下搁置了很久的 BukkitGPT-v3 项目~

      这代码写的我已经快看不懂了

      在站内也有资源帖~ https://www.mczwlt.net/resource/52c7tw1m

      目标

      1. 完成插件编辑功能
      2. 对 DeepSeek R1 等有思维链的 LLM 添加支持
      3. 修复各种 Bug

      插件编辑

      af0ec5a0-8562-49cc-bdb4-2ce8ecef8a00-image.png
      这个功能很早就想做了,前几天还被用户提出来了
      本来想着最近一段时间开学了没时间搞了
      好好我们先来看这个

      大致思路

      13a3c70c-0f3c-41bb-9139-7c1ed48d5d84-original.jpg

      反编译获取代码

      be47dc04-0166-487f-b7fd-258efb5c83e4-image.png
      (本项目所有的 docs 都是 copilot 生成的 问就是懒)
      直接调用 CFR,够轻量
      这边这个 cfr 我直接放到 libs/ 文件夹了()
      (最佳实践:自动下载库文件而不是放在 libs )

      扔给 LLM

      因为这个 BukkitGPT 还有来自 2/12/2024 (是的整整1年前)的 v2 版本的代码,当时没有 structured output 这种东西,也没有 json mode,甚至都没听说过 agent 是什么东西,导致我原来的解析方式是
      fbeed494-8f3c-4290-897a-8058dbe0bd79-image.png
      年幼无知的我不知道 cot 的重要性
      现在这个方法是不能用了,尤其是我还想做 r1 的支持呢
      这里我参考了 gpt-engineer 的一点思路,让 LLM 自由输出其他内容,程序只识别```diff``` tag内的内容,并解析 diff 应用更改。

      这里是 prompt ~

        You're a minecraft bukkit plugin coder AI.
        You're given the codes of a minecraft bukkit plugin and a request to edit the plugin.
        You should edit the codes to meet the request.
        You should use git diff (without index line) to show the changes you made.
      
        For example, if the original code of codes/ExamplePlugin4/src/main/java/org/cubegpt/188eba63/Main.java is:
        ```java
        1 package org.cubegpt._188eba63;
        2 
        3 import org.bukkit.Bukkit;
        4 import org.bukkit.event.EventHandler;
        5 import org.bukkit.event.Listener;
        6 import org.bukkit.event.player.PlayerJoinEvent;
        7 import org.bukkit.plugin.java.JavaPlugin;
        8 
        9 public class Main extends JavaPlugin implements Listener {
        10 
        11     @Override
        12     public void onEnable() {
        13         Bukkit.getServer().getPluginManager().registerEvents(this, this);
        14     }
        15
        16     @EventHandler
        17     public void onPlayerJoin(PlayerJoinEvent event) {
        18         event.getPlayer().sendMessage("hello");
        19     }
        20 }
        ```
        And the request is "Change the join message to 'hi'", then the response should be:
        ```diff
        diff --git a/codes/ExamplePlugin4/src/main/java/org/cubegpt/188eba63/Main.java b/codes/ExamplePlugin4/src/main/java/org/cubegpt/188eba63/Main.java
        --- a/codes/ExamplePlugin4/src/main/java/org/cubegpt/188eba63/Main.java
        +++ b/codes/ExamplePlugin4/src/main/java/org/cubegpt/188eba63/Main.java
        @@ -1,17 +1,17 @@
        -event.getPlayer().sendMessage("hello");
        +event.getPlayer().sendMessage("hi");
        @@ -19,20 +19,20 @@
        ```
        There could be multiple diffs, put each diff inside a markdown ```diff``` tag.
        You can response other stuffs like your plan, steps and explainations outside the ```diff``` tag. Only the diffs inside the ```diff``` tag will be used to apply the edit and text outside will be ignored.
        Make sure the diffs are valid and can be applied to the original code.
        Do not forget to add ";" in the java codes.
      
        There should be a empty pom.xml in the original code, and you should fill the pom.xml with things needed for the plugin to work. Always add this in pom.xml:
          <repositories>
            <repository>
                <id>spigot-repo</id>
                <url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
            </repository>
        </repositories>
      
        <dependencies>
            <dependency>
                   <groupId>org.spigotmc</groupId>
                   <artifactId>spigot-api</artifactId>
                   <version>1.13.2-R0.1-SNAPSHOT</version>
                   <scope>provided</scope>
            </dependency>
        </dependencies>
      

      9403c105-5852-421b-8a16-fd84b9e62716-image.png
      用正则表达式匹配所有diff就可以啦

      如何向 LLM 展示代码

      这个本来以为是最简单的步骤,遍历一遍文件就完了,但是还是有一些坑的

      1. LLM 数不清行数,得给它额外加上行号
      2. 原 JAR 一旦里面有图片之类的,就会炸出一大段无意义内容导致爆 tokens,所以需要识别仅文本文件才扔给 LLM。本来想用 magic number 识别,但是还是有些编码识别不出来,最后还是用了最 basic 的方法 识别后缀

      上代码:

      def code_to_text(directory: str) -> str:
          """
          Converts the code in a directory to text.
      
          Args:
              directory (str): The directory containing the code files.
      
          Returns:
              str: The text representation of the code.
          
          Return Structure:
              file1_path:
              ```
              1  code
              2  code
              ...
              ```
              file2_path:
              Cannot load non-text file
              ...
          """
          def is_text_file(file_path):
              txt_extensions = [
                  ".txt",
                  ".java",
                  ".py",
                  ".md",
                  ".json",
                  ".yaml",
                  ".yml",
                  ".xml",
                  ".toml",
                  ".ini",
                  ".js",
                  ".groovy",
                  ".log",
                  ".properties",
                  ".cfg",
                  ".conf",
                  ".bat",
                  ".sh",
                  "README",
              ]
              return any(file_path.endswith(ext) for ext in txt_extensions)
      
          text = ""
          for root, dirs, files in os.walk(directory):
              for file in files:
                  file_path = os.path.join(root, file)
                  relative_path = os.path.relpath(file_path, directory)
                  
                  if is_text_file(file_path):
                      try:
                          with open(file_path, 'r', encoding='utf-8') as f:
                              content = f.read()
                              # Add line numbers to content
                              numbered_lines = [f"{i+1:<3} {line}" for i, line in enumerate(content.splitlines())]
                              numbered_content = '\n'.join(numbered_lines)
                              text += f"{relative_path}:\n```\n{numbered_content}\n```\n"
                      except Exception as e:
                          text += f"{relative_path}: Cannot load non-text file\n"
                  else:
                      text += f"{relative_path}: Cannot load non-text file\n"
          
          return text
      

      如何应用更改

      Python 的 difflib 只支持比较文件,不支持应用 diff……

      所以,我一开始居然想的是自己实现一个

      写了我半个多小时

      然后就放弃了…………………………
      (新文件对比原文件可能行数不对称,太复杂了原来以为很简单的)

      最后在这个 stackoverflow 的帖子找到了 Isaac Turner 2016 年写的陈年旧码
      58a7e349-7db2-422b-a9b3-e5308be298ed-image.png
      这一看就不是我能写出来的 为什么一开始我要逞强自己重复造轮子而不去搜搜可能的已有解决方案呢

      但是这里它不会获取原文件和新文件(即---和+++行)
      所以我这边稍微改了改

      这个问题算是解决了

      构建

      正经做插件开发强烈建议用 gradle
      这边为了 LLM 写起来方便直接用 maven 了 ( 真实原因:我懒得写 gradle 模板 )

      但是问题是反编译过来的插件相当于只有 `src/main/java·文件夹里的几个代码文件,其他 pom.xml 都木有

      5add6806-856c-4a30-a31e-656770afb7f8-image.png

      然后 LLM 就会自己写上了(prompt里面提醒一句即可)

      为思维链提供支持

      前面新写的编辑插件功能就原生支持思维链了,这里把原来的生成插件的部分也加上;当然 prompt 也得改。
      ba074ec1-1416-4b58-8e4f-094f1df66bca-image.png

      细碎的修改

      • 现在支持在 OpenRouter 上标记调用 app 了;
      • 修正了 README 和部分代码 Docs 中历史遗留的不正确的措辞,停止使用 “ChatGPT” / “GPT” 等名称代指所有模型,改为使用 “LLM”;
      • 在 config.yaml中默认的模型提供商改为了 OpenRouter.ai ,默认模型改为 deepseek/deepseek-r1:free;
      • 删除了gpt-4-turbo等已经被弃用的模型的“推荐”提示和强制切换(建议使用 DeepSeek R1, OpenAI o1 / o3-mini 和 Gemini 2.0 Flash Thinking Exp 0121)
      • catch 了 APIConnectionError、AuthenticationError 和 OpenRouter 的Rate Limit,提供了更完善的指引提示
      • 为 o1-preview 添加了特殊适配( preview 版本不支持 system prompt ) (强烈建议使用 o1 正式版)
      • 弃用 core.askgpt 的 disable_json_mode 参数
      • logger 现在会同时在日志文件和控制台输出了

      最后的话

      欸这一下午就过去了
      这代码真得再找个时间优化优化了 现在真是一大坨

      然后欢迎各位如果不嫌弃我的屎山的话可以提提 PR ……

      最后说一下 BukkitGPT WebApp 的进展 原来的 WebApp 是部署在 某国外著名游戏服务商送我的 VPS 上的 —— 但是因为我更新太不活跃人家把 partnership 给我 remove 了
      90c478b2-c773-4550-9a0f-53179fca0c00-image.png
      没了美国的这台 4c8g (Disk 160G; Bandwith 8192GB) 的 VPS,我不得不找阿里云租了台轻量应用服务器,然后地址选错成上海了 要备案
      所以最近这个 WebApp 的进程要延缓了。现在打算前端部署到 Vercel 上,然后后端放在阿里云上。但是不知道要等到什么时候了

      初二在读 | LLM/AI 爱好者
      GitHub CyniaAI 系列开源 LLM Agents 项目维护者
      Hypiworld 机械筑梦腐竹 现已停运,另见 #492

      个人主页 | GitHub主页 | 微信公众号 | 日志随笔 | 知乎专栏

      ✨ 用冰冷的理性温暖世界

      我的最新文章: 评“煤炭压力版”事件:观研报告网们敲响的 LLM 信息污染警钟 | 评“姚北实验学校食堂惊现活蛆”事件 | BukkitGPT 开发日志: 添加 AI 编辑现有 Bukkit 插件的功能 | ...

      1 条回复 最后回复 回复 引用
      • 第一个帖子
        最后一个帖子
        "Minecraft" 以及 "我的世界" 为 Mojang Synergies AB 的商标,本站与 Mojang 以及 Microsoft 没有从属关系
        © 2024-2025 红石中继站 版权所有 本站原创图文内容版权属于原创作者,未经许可不得转载
        侵权投诉邮箱:[email protected]
        由 长亭雷池WAF 提供安全检测与防护 由 WAFPRO 提供 SCDN 安全加速
        苏公网安备32050902102328号 苏ICP备2023043601号-8