Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

学习下用Google的agent开发工具Agent Development Kit简称 ADK,看着名字就不一般,冲着JDK的地位去的哈哈
首先我们安装下pom包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<dependencies>
<!-- The ADK Core dependency -->
<dependency>
<groupId>com.google.adk</groupId>
<artifactId>google-adk</artifactId>
<version>0.2.0</version>
</dependency>

<!-- The ADK Dev Web UI to debug your agent (Optional) -->
<dependency>
<groupId>com.google.adk</groupId>
<artifactId>google-adk-dev</artifactId>
<version>0.2.0</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.0</version>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>

这个版本也说明了它还不是个完全体
接下去可以生成一个简单的项目结构

1
2
3
4
5
6
7
8
project_folder/
├── pom.xml (or build.gradle)
├── src/
├── └── main/
│ └── java/
│ └── agents/
│ └── multitool/
└── test/

然后再multitool中创建个java文件,可以叫 MultiToolAgent.java
注意这里需要使用jdk 17及以上版本
示例代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package agents.multitool;

import com.google.adk.agents.BaseAgent;
import com.google.adk.agents.LlmAgent;
import com.google.adk.events.Event;
import com.google.adk.runner.InMemoryRunner;
import com.google.adk.sessions.Session;
import com.google.adk.tools.Annotations.Schema;
import com.google.adk.tools.FunctionTool;
import com.google.genai.types.Content;
import com.google.genai.types.Part;
import io.reactivex.rxjava3.core.Flowable;
import java.nio.charset.StandardCharsets;
import java.text.Normalizer;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.Scanner;

public class MultiToolAgent {

private static String USER_ID = "student";
private static String NAME = "multi_tool_agent";

// The run your agent with Dev UI, the ROOT_AGENT should be a global public static final variable.
public static final BaseAgent ROOT_AGENT = initAgent();

public static BaseAgent initAgent() {
return LlmAgent.builder()
.name(NAME)
.model("gemini-2.0-flash")
.description("Agent to answer questions about the time and weather in a city.")
.instruction(
"You are a helpful agent who can answer user questions about the time and weather"
+ " in a city.")
.tools(
FunctionTool.create(MultiToolAgent.class, "getCurrentTime"),
FunctionTool.create(MultiToolAgent.class, "getWeather"))
.build();
}

public static Map<String, String> getCurrentTime(
@Schema(name = "city",
description = "The name of the city for which to retrieve the current time")
String city) {
String normalizedCity =
Normalizer.normalize(city, Normalizer.Form.NFD)
.trim()
.toLowerCase()
.replaceAll("(\\p{IsM}+|\\p{IsP}+)", "")
.replaceAll("\\s+", "_");

return ZoneId.getAvailableZoneIds().stream()
.filter(zid -> zid.toLowerCase().endsWith("/" + normalizedCity))
.findFirst()
.map(
zid ->
Map.of(
"status",
"success",
"report",
"The current time in "
+ city
+ " is "
+ ZonedDateTime.now(ZoneId.of(zid))
.format(DateTimeFormatter.ofPattern("HH:mm"))
+ "."))
.orElse(
Map.of(
"status",
"error",
"report",
"Sorry, I don't have timezone information for " + city + "."));
}

public static Map<String, String> getWeather(
@Schema(name = "city",
description = "The name of the city for which to retrieve the weather report")
String city) {
if (city.toLowerCase().equals("new york")) {
return Map.of(
"status",
"success",
"report",
"The weather in New York is sunny with a temperature of 25 degrees Celsius (77 degrees"
+ " Fahrenheit).");

} else {
return Map.of(
"status", "error", "report", "Weather information for " + city + " is not available.");
}
}

public static void main(String[] args) throws Exception {
InMemoryRunner runner = new InMemoryRunner(ROOT_AGENT);

Session session =
runner
.sessionService()
.createSession(NAME, USER_ID)
.blockingGet();

try (Scanner scanner = new Scanner(System.in, StandardCharsets.UTF_8)) {
while (true) {
System.out.print("\nYou > ");
String userInput = scanner.nextLine();

if ("quit".equalsIgnoreCase(userInput)) {
break;
}

Content userMsg = Content.fromParts(Part.fromText(userInput));
Flowable<Event> events = runner.runAsync(USER_ID, session.id(), userMsg);

System.out.print("\nAgent > ");
events.blockingForEach(event -> System.out.println(event.stringifyContent()));
}
}
}
}

这个示例非常简单,就是初始化模型,提供基本的提示词,接下去就是两个简单的工具可供使用,提供城市的时间和纽约的天气
要运行起来需要申请api key,然后写到环境变量里

1
2
export GOOGLE_GENAI_USE_VERTEXAI=FALSE
export GOOGLE_API_KEY=PASTE_YOUR_ACTUAL_API_KEY_HERE

在这里替换成自己的api key就行
然后运行

1
2
3
4
mvn exec:java \
-Dexec.mainClass="com.google.adk.web.AdkWebServer" \
-Dexec.args="--adk.agents.source-dir=src/main/java" \
-Dexec.classpathScope="compile"

就可以看到一个简单的运行界面

初使用后发现还是比较容易上手,只是后续更深入的还需要好好研究学习

wsl出来这些年感觉在windows里使用linux,或者ubuntu真的是方便了很多,不用搞虚拟机这些
但是也有一些明显的问题,首先就是默认它是在C盘中安装,长久使用占用的空间就会越来越大,而且对于一些临时使用占用空间后,即使删除了文件,windows并不会对已经扩容的虚拟磁盘文件进行缩容
所以就介绍下怎么手动来给它释放空间
首先可以看下安装的系统有哪些

1
wsl -l -v

比如我这就是一个ubuntu 20.04

1
2
  NAME            STATE           VERSION
* Ubuntu-20.04 Running 2

然后我们需要寻找下虚拟磁盘的所在位置
我的是在
C:\Users\Administrator\AppData\Local\Packages\CanonicalGroupLimited.Ubuntu20.04onWindows_79rhkp1fndgsc\LocalState\ext4.vhdx
可以按文件名 ext4.vhdx 进行查找
当然在操作之前我们还是要做好备份工作,并且还有特殊功效

1
2
3
4
# 首先进行关机
wsl --shutdown
# 然后做打包备份到制定路径,放心这里打包的大小就是实际大小
wsl --export Ubuntu-20.04 X:\Ubuntu-20.04.tar

然后我们运行 diskpart 命令,这是windows用来管理磁盘文件的工具
运行后会进入到 diskpart 工具的命令状态下

1
select vdisk file="C:\Users\Administrator\AppData\Local\Packages\CanonicalGroupLimited.Ubuntu20.04onWindows_79rhkp1fndgsc\LocalState\ext4.vhdx"

接着我们再运行select vdisk命令来选中这个文件
选中以后会提示
DiskPart 已成功选择虚拟磁盘文件。
接着运行压缩命令

1
compact vdisk

运行时会有个压缩进度展示,压缩完成后会显示以下内容

1
2
3
  100 百分比已完成

DiskPart 已成功压缩虚拟磁盘文件。

最终我们运行

1
detach vdisk

分离命令就可以了
这样子我们的wsl因为扩容后无法自动收回的空间就被我们压缩出来了

之前在我的MacBookPro里使用gpt-oss,因为总的内存只有18g,加上系统本身和各种后台程序的占用,剩下的一般就一半左右,然后这个模型大小就有13g左右,运行起来是很吃力的
正好我这有个服役比较久的windows笔记本,带有3060显卡,只是显存是6g的,想试下看能不能通过显卡来给它加加速,这里我使用了lm studio来运行gpt-oss 20b模型
这个lm studio可以配置有多少层transformer运行在gpu上,

我们可以大致除一下
这个gpt-oss 20b是有24层,官标的显存需求是16g,我们有6g显存,大约可以加载8~9层的样子,并且lm studio也自动算出来是加载8层
通过这个配置我们运行的gpt-oss能够得到勉强可用的程度

能达到4.x个token的生成速度,还是比mac上要把所有程序全关了,清掉很多token来得方便
实测将gpu卸载层数调整到9是勉强还可以运行,再往上就加载不了了,显存小没办法
当然我们还可以继续用 unsloth 进行微调
例如参考 https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/gpt-oss-(20B)-Fine-tuning.ipynb
同时可以通过nvidia-smi来查看显存占用

过程中倒是没看到显存完全占满,可以通过nvitop再进行观察
能够在gpu中加载部分层的确能把大模型的生成速度提升一些,但是还是受限于显存大小,最好是能有显存大于16g的显卡可以尝试测一下

之前体验过gemini code这个工具,谷歌还算比较友好,有token,有网络就可以用,claude code目前是个很严格,而且这家公司也对国内用户很不友好
所以就研究了下怎么把它对接上国内的模型
幸好之前deepseek发布v3.1的时候天然给了支持

1
2
3
4
5
6
7
8
## 由deepseek提供的兼容claude code的base url
export ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic
## deepseek的token
export ANTHROPIC_AUTH_TOKEN={token}
## 对应的deepseek-chat模型,这个是非推理的
export ANTHROPIC_MODEL=deepseek-chat
## 快速小模型(同样可设为 deepseek-chat)
export ANTHROPIC_SMALL_FAST_MODEL=deepseek-chat

如果需要设置乘推理型的
可以把模型改成

1
export ANTHROPIC_MODEL=deepseek-reasoner

当然这个如果要长久有效得放在 .zshrc ,根据所用的shell工具来设置
然后安装claude需要node版本在18及以上

1
2
nvm use v18
npm install -g @anthropic-ai/claude-code

安装以后到想要开发的项目
运行

1
claude

接下来我们同样以todo应用作为测试应用

1
帮我写一个todo应用,使用react,ui需要尽可能美观

只是加了个美观的要求

1
2
3
4
5
6
7
8
⏺ Update Todos
⎿  ☐ Set up React project structure and dependencies
☐ Create main Todo component with state management
☐ Design and implement beautiful UI with CSS/styling
☐ Add todo functionality (add, delete, toggle complete)
☐ Test the application

⏺ Let me start by checking the current directory structure and then create the React todo application.

首先它分了五步
然后逐步运行对应的动作,生成文件等

生成的还是比较美观可用的,说明国内模型在简单的编码任务上也具有比较不错的能力了

上次是文字型的,这次我们来写点代码试试,看看效果怎么样
首先肯定是比较简单的尝试开始
写一个todo应用,主要是看能不能零改动就跑起来
用最简单的提示词
1
帮我写一个todo应用,基于react

然后就给出了代码

下面给你一个 最小可运行 的 Todo 应用示例,基于 React + Vite(如果你习惯 create-react-app,思路几乎完全一样,只是文件夹结构略有差异)。
应用包含:

组件作用
TodoInput新增条目输入框
TodoItem单个待办项(支持完成/取消、删除)
TodoList渲染 Todo 列表
TodoApp/App主容器,维护全局 todos 状态

安装依赖

1
2
3
4
npm create vite@latest todo-app --template react
cd todo-app
npm i
npm run dev

下面按文件展示完整代码(把对应文件替换或新建即可)。


1️⃣ src/main.jsx

1
2
3
4
5
6
import React from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
import "./index.css";

createRoot(document.getElementById("root")).render(<App />);

2️⃣ src/App.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import React, { useState, useEffect } from "react";
import TodoInput from "./components/TodoInput";
import TodoList from "./components/TodoList";
import "./App.css";

/**
* 关键点:
* 1. 用 useState 存放 todos
* 2. 用 useEffect 做本地存储同步,仅在组件挂载时读取一次
* 3. 处理增删改的通用方法
*/
function App() {
const [todos, setTodos] = useState([]);

// 从 localStorage 初始化
useEffect(() => {
const stored = localStorage.getItem("todos");
if (stored) {
setTodos(JSON.parse(stored));
}
}, []);

// 每次 todos 变更时同步到 localStorage
useEffect(() => {
localStorage.setItem("todos", JSON.stringify(todos));
}, [todos]);

const addTodo = (title) => {
const newTodo = {
id: Date.now(),
title,
completed: false,
};
setTodos([newTodo, ...todos]);
};

const toggleComplete = (id) => {
setTodos((prev) =>
prev.map((t) =>
t.id === id ? { ...t, completed: !t.completed } : t
)
);
};

const deleteTodo = (id) => {
setTodos((prev) => prev.filter((t) => t.id !== id));
};

return (
<div className="app-container">
<h1>Todo List</h1>
<TodoInput addTodo={addTodo} />
<TodoList
todos={todos}
toggleComplete={toggleComplete}
deleteTodo={deleteTodo}
/>
</div>
);
}

export default App;

3️⃣ src/components/TodoInput.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import React, { useState } from "react";
import "./TodoInput.css";

const TodoInput = ({ addTodo }) => {
const [value, setValue] = useState("");

const handleSubmit = (e) => {
e.preventDefault();
const trimmed = value.trim();
if (trimmed) {
addTodo(trimmed);
setValue("");
}
};

return (
<form className="todo-input" onSubmit={handleSubmit}>
<input
type="text"
placeholder="Add a new todo..."
value={value}
onChange={(e) => setValue(e.target.value)}
/>
<button type="submit">Add</button>
</form>
);
};

export default TodoInput;

4️⃣ src/components/TodoItem.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React from "react";
import "./TodoItem.css";

const TodoItem = ({ todo, toggleComplete, deleteTodo }) => {
return (
<div className="todo-item">
<label>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleComplete(todo.id)}
/>
<span className={todo.completed ? "completed" : ""}>
{todo.title}
</span>
</label>
<button className="delete-btn" onClick={() => deleteTodo(todo.id)}>
×
</button>
</div>
);
};

export default TodoItem;

5️⃣ src/components/TodoList.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React from "react";
import TodoItem from "./TodoItem";
import "./TodoList.css";

const TodoList = ({ todos, toggleComplete, deleteTodo }) => {
if (todos.length === 0) {
return <p className="empty-msg">No todos yet. Add something!</p>;
}

return (
<div className="todo-list">
{todos.map((todo) => (
<TodoItem
key={todo.id}
todo={todo}
toggleComplete={toggleComplete}
deleteTodo={deleteTodo}
/>
))}
</div>
);
};

export default TodoList;

6️⃣ src/index.css(可选,简单全局样式)

1
2
3
4
5
6
7
8
9
10
11
12
/* 让页面充满屏幕并居中 */
html, body, #root {
height: 100%;
margin: 0;
font-family: system-ui, sans-serif;
}
body {
display: flex;
justify-content: center;
align-items: flex-start;
background: #f5f5f5;
}

7️⃣ src/App.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.app-container {
width: 90%;
max-width: 480px;
background: #fff;
padding: 20px;
margin-top: 40px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
border-radius: 8px;
}

.app-container h1 {
text-align: center;
margin-bottom: 20px;
}

8️⃣ src/components/TodoInput.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.todo-input {
display: flex;
margin-bottom: 20px;
}

.todo-input input {
flex: 1;
padding: 8px 12px;
font-size: 16px;
}

.todo-input button {
padding: 8px 12px;
margin-left: 8px;
font-size: 16px;
background: #4caf50;
color: #fff;
border: none;
cursor: pointer;
}

.todo-input button:hover {
background: #45a049;
}

9️⃣ src/components/TodoList.css

1
2
3
4
5
6
7
8
9
10
.todo-list {
display: flex;
flex-direction: column;
gap: 8px;
}

.empty-msg {
text-align: center;
color: #777;
}

🔟 src/components/TodoItem.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
.todo-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 12px;
background: #fafafa;
border-radius: 4px;
}

.todo-item label {
flex: 1;
display: flex;
align-items: center;
}

.todo-item input[type="checkbox"] {
margin-right: 12px;
}

.todo-item .completed {
text-decoration: line-through;
color: #777;
}

.delete-btn {
background: transparent;
border: none;
color: #ff4444;
font-size: 20px;
cursor: pointer;
}

.delete-btn:hover {
color: #ff0000;
}

🎉 运行、打包

1
2
3
4
5
# 开发模式
npm run dev

# 生产构建
npm run build

🚀 小贴士

  • 本地存储:示例中使用 localStorage 让 Todo 在刷新后保留。生产环境可以接
    入后端或使用 IndexedDB 之类的更强大存储方案。
  • 状态管理:若项目日后变大,可考虑引入
    Redux/zustand/@tanstack/react-store 等。
  • 类型安全:使用 TypeScript 可以防止类型错误;把文件后缀改成 .tsx 并在
    package.json dependencies 增加 typescript@types/react
  • 单位测试jestreact-testing-library 能帮你保障组件逻辑正确。
  • 样式优化:考虑使用 styled-components/emotion/tailwindcss 等,让样
    式变得更易维护。

现在你就拥有一个完整、可直接跑起的 Todo React 应用,随时可以继续扩展功能(如过
滤、拖拽排序、用户登录等)。祝编码愉快 🚀!


在本地先建个应用

1
npm create vite@latest todo-app --template react

这里要注意下,要用vite的话要node版本在20.19+或者22.12+
还有是需要选择js版本,而不是ts
运行下

1
npm install

这里比较慢的话可以上镜像

1
npm config set registry https://registry.npmmirror.com

接着把生成的代码都保存成文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
├── src
│   ├── App.css
│   ├── App.jsx
│   ├── assets
│   │   └── react.svg
│   ├── components
│   │   ├── TodoInput.css
│   │   ├── TodoInput.jsx
│   │   ├── TodoItem.css
│   │   ├── TodoItem.jsx
│   │   ├── TodoList.css
│   │   └── TodoList.jsx
│   ├── index.css
│   └── main.jsx

运行命令

1
npm run dev

看到效果

结果是真的直接可用的,为什么要测这个呢,主要原因是一个模型本地可用的探讨,我认为如果是要本地可用,至少在这些相对简单的任务上能够完成得比较好,前面那个只是个提问,并没有什么可以验证正确性的,内容还是比较多的,对于做开发的来说能辅助编程还是比较重要的,并且能够完成一些相对独立完整的任务才是比较可用的初级门槛,只是对内存压力还是比较大的

0%