用 Kotlin 构建你的第一个 Agent — 开篇

0 阅读6分钟

背景

短短的三四年时光,AI 的发展速度超出了所有人的预期。

从 ChatGPT 的横空出世到各种 Agent 的涌现,AI 已经不再是实验室里的概念,而是我们日常工作中不可分割的一部分。

作为一名 Android 开发者,我深刻感受到了这种变化。曾经需要我们手动编写的代码,现在 AI 可以自动生成;曾经需要我们设计的架构,现在 AI 也可以自主完成。

工作正在被慢慢取代,但我们对于技术的追求却从未停止。

我最近发现,大部分开发者(包括我在内)对 Agent 技术了解的并不多。我们知道 AI 很强大,知道 Agent 很热门,但真正动手构建过一个 Agent 应用的人却寥寥无几。

可能很多 Android 开发者只是停留在了写 Skill 的层面,更深入的层次就是使用一些开源工具,对 AI 工具进行私人定制。

所以,我想借此机会,用我们熟悉的技术栈来深入学习 Agent 的整个构建流程。不是为了追赶潮流,而是为了真正理解这项技术,掌握它的核心原理。

目标

blog_1.png

我想要构建一个桌面端英语类 Agent——一个专门帮助程序员提升英语水平的 AI 助手。它能理解你的问题,提供语法纠正,解释技术术语,并陪你进行日常英语对话。

选择这个项目有几个原因:

  1. 实用性强:程序员确实需要提升英语水平,无论是阅读文档还是参与国际社区;
  2. 复杂度适中:既不会太简单以至于学不到东西,也不会太复杂以至于难以完成;
  3. 可扩展性好:后续可以添加更多功能,如天气查询、地点查询,我尽量找一些免费的 API 用,后续随着项目向后更近,可以往里面添加更多的功能。

而且,我发现使用英语确实可以极大的节省 token,所以学一手英语还是很有必要的。

技术选型

2026 年了,个人项目,那我一切以最新的技术为优先:

  • Kotlin:现代、简洁、安全,适合构建复杂应用。作为 Android 开发者,这是我们最熟悉的语言;
  • Amper:JetBrains 的新一代构建工具,配置更简洁;
  • Koog:专为 Kotlin 设计的 Agent 开发 SDK,提供完整的 Agent 框架,让我们可以专注于业务逻辑而非底层实现;
  • Compose Desktop:声明式 UI,快速构建桌面应用界面。我们可以复用 Android 端的 Compose 经验。

选择它们不仅是为了学习 Agent,也是为了跟上技术发展的步伐。

这个 Amper 比较有意思,JetBrains 受不了 Gradle 构建工具的复杂度,自己弄了一个新的 Amper,后续基本上 Kotlin 开发相关的,都打算做原生支持,而且配置非常简单,有点像 Rust 的 Cargo

但目前从功能上看距离 GradleCargo 还是有巨大差距的,期待后续的持续迭代。

Koog 也是 JetBrains 搞的一款面向 JVM 生态、用于构建 Agent 的开源框架。它为 Kotlin 与 Java 开发者提供更好的开发体验,配备符合语言习惯、类型安全的 Kotlin 领域特定语言(DSL),以及流式构建器风格的 Java 应用程序接口(API)。

类比的话,可以当做 Python 中的 LangChain + LangGraph

这篇文章是系列的第一篇,我们将从零开始搭建项目,使用 Kotlin、AmperKoog 和 Compose 构建一个简单的应用。

开工

首先,我们需要创建一个新的 Kotlin 项目。

使用最新版本的 IntelliJ IDEA,先安装一个插件 Kotlin ToolChain

plugin_tool_chain.png

安装完毕后,开始创建新项目,选择 New Project,然后选择 Kotlin

start_project.png

额,对,我这个项目就叫 CookCompose + Koog + Kotlin)。

注意在项目配置中,选择 JVM GUI application 作为项目类型,我的目标就是运行在桌面端的,因为 JVM 的加持,这样跑在 Mac 和 Linux 上也不是梦。

当你点击 Create 之后,IDEA 就会帮你创建好这个工程。

Amper 的第一体验,非常快!!!没有什么 sync,没有什么构建,一下就好,几乎是你点击 Create 之后,整个工程迅速的呈现了。

project.png

看下 Amper 的配置文件 module.yaml

yaml.png

嚯,干净!

没有 setting.gradle,没有 project 级别的 build.gradle,也没有module 级别的 build.gradle,就一个 module.yaml,五行有效代码。

yaml 大家怎么发音的?我是发的“呀馍”。

我们打开 main.kt,直接运行。

初次运行的时候,控制台会打印这些东西:

build.png

然后,如果运气好的话,你会看到这个画面:

first_desktop.png

为什么说运气好呢?因为我在运行的时候,就没有显示任何画面,进程是运行了,但是没有任何窗口弹出。

于是我在命令行运行了一下 .\kotlin.bat run(我是 Windows,所以使用 bat 命令),等待了一会儿,好了。

如果你也碰到类似的问题,那么你可以参考这个做法!

配置 Koog

接下来,我们正式引入 Koog,在配置中添加 Koog 的依赖。

打开 module.yaml,添加 ai.koog:koog-agents:1.0.0。这个库包含了 Agent 框架的核心组件。

dependencies:
  - $compose.desktop.currentOs
  - ai.koog:koog-agents:1.0.0 // add koog
  - ai.koog:prompt-executor-openrouter-client:1.0.0 // add openrouter

构建第一个 Agent

现在,让我们创建一个简单的英语学习 Agent。

cookkt.png

我创建了一个新的文件 agent/Cook.ktCook 我打算作为一个 object 类,主要负责Agent管理相关。


// current api key on openrouter
private const val ENV_KEY = "OPENROUTER_API_KEY"

// system prompt
private const val SYSTEM_PROPERTIES_KEY = "You are a friendly chat partner. Chat with me naturally in English. Whenever I make grammar, word choice or expression mistakes, point them out clearly, explain the errors briefly and offer corrected versions. Keep conversations relaxed and concise. Do not be overly formal. Focus on helping me improve my English while chatting."

object Cook {

    private val agent: GraphAIAgent<String, String>

    init {

        // Describe the OpenRouter-hosted model and the Koog capabilities this agent expects to use.
        val glm4Flash = LLModel(
            provider = LLMProvider.OpenRouter,
            id = "deepseek/deepseek-v4-flash",   // free-tier, fast
            capabilities = listOf(
                LLMCapability.Temperature,
                LLMCapability.Tools,
                LLMCapability.Completion,
            )
        )

        // Build a Koog prompt executor around the OpenRouter client selected by the API key env var.
        val client = OpenRouterLLMClient(apiKey = System.getenv(ENV_KEY))
        val executor = PromptExecutor.builder()
            .addClient(client)
            .build()

        // Keep one configured agent instance so calls to ask() reuse the same model and system prompt.
        agent = AIAgent(
            promptExecutor = executor,
            llmModel = glm4Flash,
            systemPrompt = SYSTEM_PROPERTIES_KEY,
            toolRegistry = ToolRegistry.EMPTY,
        )
    }

    suspend fun ask(question: String) {
        // AIAgent.run is suspend; callers decide which coroutine scope owns the request.
        val response = agent.run(question)
        println(response)
    }
}

当然,此时这段代码还不能使用。

我之前在 Github 上问了一个问题 —— 如何才能在 Koog 中使用 Glm 的模型。

如果你在智谱的平台上注册了,然后想使用它免费的模型,在 Koog 中实现是非常复杂的,你需要自己处理网络协议。好在下面的老哥给出了解决方案:

1.png

总结就是去 OpenRouter 注册账号,然后使用 OpenRouter 上的免费模型,所以上面的代码就是使用 OpenRouter 的相关支持,注意,你需要注册一个 API Key,这一步比较简单,不再赘述:

2.png

注册完 API Key 之后,回到 IDEA,添加 OPENROUTER_API_KEY 的环境变量。

3.png

在这里编辑配置。

4.png

5.png

添加环境变量。

哎,对了,我把模型换成了 deepseek/deepseek-v4-flash,因为 OpenRouter 有很多免费模型可以用,所以我就手一抖,换成了 deepseek/deepseek-v4-flash 试试看。

此时,我们在 main 函数中,添加一段询问的代码,就可以运行应用了,让我们试一下。

6.png

这段话有几个语病,我们看看我们的 Agent 能帮我们找到不。

运行后,查看控制台:

You said "introduce your self" �� a small correction: it should be "yourself" (one word). Also, "word" should be "words" here. So: "Introduce yourself in 100 words."

Here's my introduction:

Hi! I'm an AI assistant created to help you practice and improve your English. I can chat naturally, correct your mistakes, and explain grammar or word choice in a simple way. I'm friendly, casual, and love keeping conversations relaxed. Whether you want to talk about daily life, hobbies, or just have a chat, I'm here for you. I'll point out errors clearly but gently, so you learn as we talk. Think of me as a patient language buddy who's always ready to help. Let's have fun improving your English together!

That's about 100 words. Let me know if you'd like to shorten or adjust it!

非常棒!

它不仅帮我找到了我的语病,同时也完成了相关的介绍任务!

乱码

不过,这里有个乱码问题,我们需要解决。

通过配置输出的编码格式,可以防止输出的乱码问题,这里我们配置了输出为 UTF-8 格式。

fun main() {
    configureUtf8ConsoleOutput()
    //...
}

private fun configureUtf8ConsoleOutput() {
    System.setOut(PrintStream(FileOutputStream(FileDescriptor.out), true, StandardCharsets.UTF_8)) // config FileDescriptor.out in UTF_8
    System.setErr(PrintStream(FileOutputStream(FileDescriptor.err), true, StandardCharsets.UTF_8)) // config FileDescriptor.err in UTF_8
}

再次运行:

//....

You wrote: *"introduce your self in 100 word"*

✅ Corrected to: *"Introduce yourself in 100 words."*

- "Your self" should be one word: **yourself**

- "100 word" needs to be plural: **100 words**

- Remember to capitalize the first letter of your sentence. 😊

原来如此,是 emoji 的乱码,emoji —— 这 AI 味儿一下上头了!

SLF4J

当然,你还会碰到一个编译的警告:

7.png

我最讨厌警告了!

dependencies:
  //...
  - org.slf4j:slf4j-simple:2.0.17

补充交互

目前我们的 App 还非常简单,启动页面后,给 Agent 发送一个消息,然后在控制台打印了回复。

现在,让我们做的稍微复杂点,让其有个简单的聊天窗口。

当然了,我是肯定不会亲自写了,交给 AI 吧!

大约两小时后...

我们得到了一个简单 Agent App。

PixPin_2026-05-29_10-13-46.gif

实际上这里做了大量的更改,我把主要的修改列举出来:

引入 MVVM

我把 Android 的那一套 MVVM 架构引入进来了:

8.png

现在的依赖也比较多了:

dependencies:
  - $compose.desktop.currentOs
  - $compose.material3
  - org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose:2.10.0 // viewmodel
  - org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.10.2 // 协程
  - ai.koog:koog-agents:1.0.0
  - ai.koog:prompt-executor-openrouter-client:1.0.0
  - org.slf4j:slf4j-simple:2.0.17

目前我依然认为,如果是开发前端页面,MVVM 几乎到了完美的架构上!

修改 Agent

关于 Cook 的 Agent 代码,也做了如下的修改:

.functionalStrategy(
    functionalStrategy<String, String>("direct_chat") { question ->
        llm.writeSession {
            appendPrompt {
                user(question)
            }
            requestLLMWithoutTools().textContent()
        }
    }
)

简单来说,这段代码的主要目的就是:把 Cook 简化成一个直接聊天代理。每次调用 Cook.ask(question) 时,它把 question 加进对话,然后调用 OpenRouter 上的模型,最后返回模型的纯文本回答。

也就是我们截图中所呈现的,聊天内容。

好,这篇文章我们就先干到这里。

总结

我们已经成功搭建了一个基于 Kotlin、Amper、Koog 和 Compose 的英语学习 Agent 应用。虽然功能还很简单,但这个框架为我们后续的开发打下了坚实的基础,也作为一个我们后续开发的原型。

关键点回顾:

  • 使用 Amper 简化构建配置;
  • 使用 Koog 快速构建 Agent 逻辑;
  • 使用 Compose Desktop 创建现代化的桌面界面。

说实话,我自己对 Agent 的理解也还在摸索阶段,文章中难免有不够严谨甚至错误的地方。如果你在其他语言或框架(比如 Python 的 LangChain、Go 的 Eino 等)中有更好的实现思路,或者发现文中的方案有不合理、可以改进之处,欢迎在评论区直接指出来,我一定会认真调研和学习。

毕竟,写这个系列的过程,本身也是我自己的学习过程。

后续,我们将为 Agent 添加更多实用工具,让它成为一个真正的英语助手。

Happy coding, and happy learning!

源码
github.com/kapaseker/c… [1_aster]