标签: AI编程 2025-12-14 次
开源的魅力在于它能催生创新,所以北京心玥软件公司发现了Goose,一个我能参与贡献的开源AI开发代理。在此之前,我用过的很多AI开发工具都是闭源的,限制用户只能用产品提供的那些功能。
看其他工程师和Goose互动时,我惊喜地发现任何人都能扩展它的功能。这意味着我可以拿Goose的基础功能,根据自己的用例定制它,不管这些用例多不寻常。我见过工程师用它干实事,比如总结代码仓库、和GitHub命令行交互,也见过实验性用法,比如让Goose上网找毛绒玩具,或者开启语音交互。
被这些可能性鼓舞着,我开始琢磨自己做个工具包。我野心不小,想让Goose帮我:
• 协助训练解读手语的AI模型
• 生成表情包
• 为终端实时编程音乐创建Sonic Pi集成
• 帮忙规划感恩节晚餐
可惜这些尝试全失败了。一开始我以为是我不是Python高手,但很快意识到——我没完全搞懂怎么创建工具包,要是连我都这样,别人可能也跟我一样摸不着头脑。这篇博客里,我就用一个简单实用的例子——待办事项管理器,分享怎么创建你的第一个工具包。
什么是工具包?🤔
官方定义
根据Goose官方文档,工具包是插件,能“给Goose提供可调用的工具(函数),还能选把额外上下文加载到系统提示里(比如‘GitHub命令行工具叫gh,你应该用它跑git命令’)。工具包几乎无所不能,从调用外部API、截屏到总结当前项目都行。”
我的理解
把工具包想象成你手机上的应用。手机基础能打电话,但你还能下载应用扩展功能——玩游戏、拍照、听歌都行。同理,Goose的基础功能是管理会话、控制版本,但工具包能给它加这些本事:
• 截屏调试
• 和GitHub命令行交互
• 管理Jira项目
• 总结代码仓库
想看看工具包实际怎么用?翻我上一篇博客,里面用屏幕工具包修UI bug。
怎么创建自己的工具包 📝
第一步:安装Goose
在终端运行这些命令装Goose:
brew install pipx pipx ensurepath pipx install goose-ai
第二步:复刻Goose插件仓库
Goose插件仓库是你工具包的“家”。按README里的说明操作,先把仓库复刻下来。
第三步:启动开发会话
现在可以创建一个Goose开发会话。这和普通启动会话不一样——你要启动一个用Goose来构建的会话:
uv run goose session start
注意:可能需要先装uv。
第四步:创建工具包文件
在目录goose-plugins/src/goose_plugins/toolkits里,创建一个叫mytodo.py的文件。
第五步:设置样板代码
往mytodo.py里加这些代码:
from goose.toolkit.base import tool, Toolkit class MyTodoToolkit(Toolkit): """一个简单的待办事项工具包,用来管理任务。""" def __init__(self, *args: tuple, **kwargs: dict) -> None: super().__init__(*args, **kwargs) # 初始化任务列表,每个任务是带'description'和'completed'字段的字典 self.tasks = []
到现在,这个文件干了这些事:
• 导入了基础工具包库
• 创建了MyTodoToolkit类,继承自Toolkit类
• 初始化了一个空列表,用来存任务清单
第六步:添加工具包的第一个动作
先写个添加任务的方法,往mytodo.py里加这段代码:
@tool
def add_task(self, task: str) -> str:
"""往待办清单里加个新任务。
Args:
task (str): 要添加到清单的任务描述。
"""
self.tasks.append({"description": task, "completed": False})
self.notifier.log(f"Added task: '{task}'")
return f"Added task: '{task}'"拆解一下这个方法:
• @tool装饰器:把函数标记为工具,让Goose能自动识别和管理它。
• 方法定义:创建add_task方法。工具包里写方法,就是定义它能干的特定动作——这里我们要让任务管理工具包能加任务。
• 文档字符串:Goose强制要求这个。它不只是文档,Goose用它来:
• 验证方法功能
• 检查参数是否匹配
Goose的创造者Bradley Axen说,文档字符串常帮Goose在处理命令时给出更精准的结果。
第七步:完善工具包
现在可以加更多方法,处理删除任务、更新任务、列出所有任务、列出已完成任务。下面是完整的mytodo.py文件:
from goose.toolkit.base import tool, Toolkit
class MyTodoToolkit(Toolkit):
"""一个简单的待办事项工具包,用来管理任务。"""
def __init__(self, *args: tuple, **kwargs: dict) -> None:
super().__init__(*args, **kwargs)
# 初始化任务列表,每个任务是带'description'和'completed'字段的字典
self.tasks = []
@tool
def add_task(self, task: str) -> str:
"""往待办清单里加个新任务。
Args:
task (str): 要添加到清单的任务描述。
"""
# 把任务存为带描述和完成状态的字典
self.tasks.append({"description": task, "completed": False})
self.notifier.log(f"Added task: '{task}'")
return f"Added task: '{task}'"
@tool
def list_tasks(self) -> str:
"""列出待办清单里的所有任务。"""
if not self.tasks:
self.notifier.log("No tasks in the to-do list.")
return "No tasks in the to-do list. Give user instructions on how to add tasks."
task_list = []
for index, task in enumerate(self.tasks, start=1):
status = "✓" if task["completed"] else " "
task_list.append(f"{index}. [{status}] {task['description']}")
self.notifier.log("\n".join(task_list))
return f"Tasks listed successfully: {task_list}"
@tool
def remove_task(self, task_number: int) -> str:
"""按编号从待办清单里删任务。
Args:
task_number (int): 要删的任务编号(从1开始)。
"""
try:
removed_task = self.tasks.pop(task_number - 1)
self.notifier.log(f"Removed task: '{removed_task['description']}'")
return f"Removed task: '{removed_task['description']}'"
except IndexError:
self.notifier.log("Invalid task number. Please try again.")
return "User input invalid task number and needs to try again."
@tool
def mark_as_complete(self, task_number: int) -> str:
"""按编号把任务标为已完成。
Args:
task_number (int): 要标记的任务编号(从1开始)。
Raises:
IndexError: 如果任务编号无效。
"""
try:
self.tasks[task_number - 1]["completed"] = True
self.notifier.log(f"Marked task {task_number} as complete: '{self.tasks[task_number - 1]['description']}'")
return f"Marked task {task_number} as complete: '{self.tasks[task_number - 1]['description']}'"
except IndexError:
self.notifier.log("Invalid task number. Please try again.")
return "User input invalid task number and needs to try again."
@tool
def list_completed_tasks(self) -> str:
"""列出所有已完成任务。"""
completed_tasks = [task for task in self.tasks if task["completed"]]
if not completed_tasks:
self.notifier.log("No completed tasks.")
return "No completed tasks. Provide instructions for marking tasks as complete."
task_list = []
for index, task in enumerate(completed_tasks, start=1):
task_list.append(f"{index}. [✓] {task['description']}")
self.notifier.log("\n".join(task_list))
return f"Tasks listed successfully: {task_list}"
@tool
def update_task(self, task_number: int, new_description: str) -> str:
"""按编号更新任务描述。
Args:
task_number (int): 要更新的任务编号(从1开始)。
new_description (str): 任务的新描述。
Raises:
IndexError: 如果任务编号无效。
"""
try:
old_description = self.tasks[task_number - 1]["description"]
self.tasks[task_number - 1]["description"] = new_description
self.notifier.log(f"Updated task {task_number} from '{old_description}' to '{new_description}'")
return f"Updated task {task_number} successfully."
except IndexError:
self.notifier.log("Invalid task number. Unable to update.")
return "Invalid task number. Unable to update."第八步:让你的工具包对他人可用
现在要把工具包加到pyproject.toml里,让别人也能用:
[project.entry-points."goose.toolkit"] developer = "goose.toolkit.developer:Developer" github = "goose.toolkit.github:Github" # 加这么一行——键会成为配置里用的名称 mytodo = "goose_plugins.toolkits.mytodo:MyTodoToolkit"
格式是模块.子模块:类名:
• goose_plugins是基础模块
• toolkits是goose_plugins下的子模块
• mytodo是toolkits下更深的子模块
• MyTodoToolkit是类名
第九步:启用你的工具包
把工具包加到~/.config/goose/profiles.yaml里就能用了:
default:
provider: openai
processor: gpt-4o
accelerator: gpt-4o-mini
moderator: truncate
toolkits:
- name: developer
requires: {}
- name: mytodo
requires: {}现在用自然语言提示它就行,比如加任务、标完成、更新任务。下面是个使用示例:
参考我的拉取请求 👀
你可以看看我的PR,里面有完整实现。

工具包的未来 🚀
工具包的创建方式还在发展。看看产品路线图计划,了解接下来会有什么新东西。