系统提示技巧:用反重力SDK构建Anki导师

AI让我变得有点懒。

不是极度懒惰。不是"机器人会做一切"懒惰。更像是:一旦你习惯了让代理做无聊的工作,每一个小的人工工作流程都开始显得可疑。

Anki就是一个完美的例子。

安奇很棒。我用它来记住我学习的东西,我研究的主题,以及隐藏在代码库中的奇怪的小决定。间隔重复作品。问题不在Anki。

问题是我。

我已经可以看到腐败开始了。在复杂的卡片上,我的大脑开始和自己谈判。"是的,我基本上知道。""够近了。""我会在上下文中记住它。"然后我按好,继续前进。

那不是学习。这是自我认证的盛传。

我真正想要的是一个学习伙伴,坐在我真正的安奇收藏上面。有人问卡,等待我的答案,揭示真正的答案,诚实地比较,解释差距,只有这样才能帮助决定它是再次,难,好,还是容易。

人工智能在这方面做得非常好。

这在接手新项目时也很有用。当我输入一个回购协议时,我不仅想要一个摘要。我希望稍后能被问到关键决策、架构、陷阱以及"为什么会这样?"零件。Anki在这方面也很棒。

但是我还是很懒。

我不会手动写每张卡片。我不会手动更新每一副牌。如果我在手机上学习,我肯定不会在聊天中输入冗长的答案,这样代理就可以给我打分。声音也需要工作。

所以这个项目很快就不再是"连接双子座和安奇"

它变成了一个小型代理系统:

集中复习课的终极导师

从我的手机学习的电报导师,包括语音回答

从网络研究或本地代码库创建卡片的卡片组生成器

一种监视模式,可以在我工作时注意到代码变化并创建卡片

这是很多行为。

我的第一直觉是通常的:写一个更大的系统提示符。告诉代理如何开展学习课程。告诉它如何写出好的抽认卡。告诉它如何检查一个代码库,并把架构变成卡片。告诉它如何在电报中表现不同。告诉它除非我同意,否则不要碰日程表。

那工作了大约十分钟。

然后系统提示变成了垃圾抽屉。

难的是不给代理工具。

难的是让它养成习惯。

这就是Google Antigravity SDK非常适合的地方。它以Python库的形式为您提供代理运行时:定制工具、可重用技能、生命周期挂钩、安全策略、流、触发器,以及从不同表面运行相同代理逻辑的多种方式。

反重力SDK给你什么

反重力SDK不仅仅是一个聊天模型的包装。

它提供了对Google Antigravity 2.0和Antigravity CLI背后的相同代理运行时的编程访问,但来自Python。

这很重要,因为真正的经纪人不仅仅是一个模特。一个真正的特工需要:

工具

记忆穿越转弯

许可

钩住

技能

流动

扳机

副作用的安全性

SDK将这些放在一个主要抽象之后:Agent.

最小的有用版本真的很小:

import asyncio

from google.antigravity import Agent, LocalAgentConfig

async def main():

config = LocalAgentConfig()

async with Agent(config) as agent:

response = await agent.chat("What files are in the current directory?")

print(await response.text())

if name == "main":

asyncio.run(main())

安装时使用:

pip install google-antigravity

然后从Google AI Studio设置一个Gemini API键:

export GEMINI_API_KEY="your-key-here"

这是你好世界。

当您围绕一个真实的工作流构建运行时特性时,有用的版本就开始了。

在这个项目中,反重力SDK片段映射如下:

反重力SDK功能 我用它的地方

Agent / LocalAgentConfig 终端指导程序、电报指导程序和卡片组生成器都在同一个代理运行时上运行

自定义Python工具 图标连接操作,如get_due_cards, show_answer, rate_card,以及add_notes

skills_paths 共享的review-buddy, plain-cards,以及codebase-cards行为包

生命周期挂钩 会话开始/结束时同步,写入前甲板备份,计划更改后审核日志,工具错误恢复

安全政策 练习模式块rate_card所以临时抱佛脚不能改变真正的日程安排

流动 当代理研究和创建卡片时,卡片组构建者打印进度

扳机 观看模式对以下内容做出反应.py文件更改,并要求代理记录重要的更改

内置只读工具 代码库模式允许代理检查存储库,而无需对其进行编辑

这个列表是这个作为SDK项目比作为一个模型调用的巨大提示效果更好的原因。

现在,有用的第一步:给代理人双手。

给代理双手:作为Python工具的Anki

Anki已经通过AnkiConnect插件拥有了一个HTTP API。整个桥基本上是到本地主机的一个POST:

def invoke(action: str, **params):

response = requests.post(

"http://localhost:8765",

json={"action": action, "version": 6, "params": params},

timeout=30,

)

response.raise_for_status()

payload = response.json()

if payload"error":

raise RuntimeError(payload"error")

return payload"result"

从这里开始,代理工具就是普通的Python函数。

简化版:

def list_decks() -> str:

"""List all Anki decks with their due counts."""

decks = invoke("deckNames")

stats = invoke("getDeckStats", decks=decks)

return json.dumps(stats)

def get_due_cards(deck: str = "", limit: int = 5) -> str:

"""Return due cards without revealing the answer side."""

query = f'deck:"{deck}" is:due' if deck else "is:due"

card_ids = invoke("findCards", query=query):limit

cards = invoke("cardsInfo", cards=card_ids)

return json.dumps(cards)

def rate_card(card_id: int, rating: int) -> str:

"""Submit a user-confirmed Anki rating: 1 Again, 2 Hard, 3 Good, 4 Easy."""

invoke("answerCards", answers={"cardId": card_id, "ease": rating})

return json.dumps({"rated": card_id, "rating": rating})

然后将它们注册到SDK:

from google.antigravity import LocalAgentConfig

config = LocalAgentConfig(

tools=list_decks, get_due_cards, rate_card,

)

这是SDK最好的部分之一:定制工具不需要单独的服务器。对于这个版本,我不需要MCP、框架、模式生成器或第二个过程。

代理可以调用普通Python。

在真正的项目中,我使用了更多的工具:

list_decks

get_due_cards

show_answer

rate_card

find_notes

add_note

add_notes

update_note

suspend_card

unsuspend_card

undo

get_stats

sync

这足以使导师有用。

这是第一种模式:

将能力放在工具中。

工具是特工的手。但是手不是行为。

对于行为,我用了技巧。

巨型系统提示的问题是

起初,我试图在代理的系统指令中描述一切。

导师需要知道如何进行复习课:

展示问题

等我的回答

揭晓答案

对比我的答案

建议评级

等待确认

然后才更新Anki计划

它还需要知道如何写好卡片:

每张卡一个事实

答案-第一次回来

没有琐事填充

没有模糊的问题

没有巨大的论文卡

那么甲板建造者需要另一个工作流程:

研究一个课题

提取重要的事实

创建卡片

验证它们存在于Anki中

那么代码库平台构建器需要不同的工作流程:

先检查回购广度

找到关键的抽象

解释职责和数据流

避免为随机语法制作卡片

那么Telegram需要更短的回复,因为没人想要一整面降价墙。

您可以将所有这些放入一个系统提示符中。

但是你不应该。

巨型系统提示符有三个问题:

它污染了每个任务。当您复习西班牙语动词时,代理正在思考代码库探索。

很难再利用。同样的写卡规则需要出现在终端导师,电报导师,和甲板建设者。

它腐烂了。每个新行为都被粘贴到同一个blob中,直到没人知道哪个规则控制了什么。

这正是技能解决的问题。

形状从这个开始变了:

system prompt = tutor rules

  • card-writing rules

  • codebase-exploration rules

  • Telegram style rules

  • safety reminders

  • whatever I forgot last week

变成这样:

system prompt = identity + hard safety floor

review-buddy = study-session behavior

plain-cards = card-writing behavior

codebase-cards = repo-exploration behavior

hooks/policies = enforcement and receipts

这是标题背后的真正模式。

而不是"让提示更好。"

让提示变小。

系统提示技巧

技能是一个文件夹SKILL.md文件在里面。

我的项目有三个:

.agents/skills/

plain-cards/

SKILL.md

review-buddy/

SKILL.md

codebase-cards/

SKILL.md

每项技能都是从一点点前沿物质开始的。

比如复习技巧是这样开始的:


name: review-buddy

description: Playbook for running an interactive Anki review session --- quiz one card at a time, grade recall together, submit ratings, repair noisy or broken cards.


那description不仅仅是对人类的记录。它是轻量级发现层。代理可以查看存在哪些技能,然后仅在任务需要时加载完整的指令。

一项技能不是一种服务。它不是一个MCP服务器。这不是部署。它是一个位于磁盘上的行为包,可以在需要的时候放入代理中。

然后,SDK加载技能目录:

config = LocalAgentConfig(

system_instructions=SYSTEM_INSTRUCTIONS,

tools=ALL_TOOLS,

skills_paths=".agents/skills",

)

关键的想法很简单:

系统提示说代理是谁。技能说明它目前正在做什么工作。

对于这个项目,系统提示保持较小。它说,代理是一个友好的抽认卡导师与一个真正的安奇收集工作。

细节寓于技巧之中。

review-buddy:学习课程行动手册

该技能描述了如何运行考核期次。

它涵盖了节奏:

一次问一张牌

隐藏答案,直到用户尝试它

简单地揭示和教导

建议评级

等待确认

处理有噪音或破损的卡

以总结结束

这不是代码。这是行为规范。

这种区别很重要。审查流程与终端I/O、电报消息或AnkiConnect无关。这才是一个好导师应该有的行为方式。

plain-cards:卡片写作风格指南

这个技能处理卡片质量。

它告诉代理写下以下卡片:

原子的

回答第一

倾斜

已证实的

不含填料

几个月后容易回顾

一个坏的抽认卡比没有抽认卡更糟糕。它创造了虚假的进步。该模型可以在几秒钟内生成十张卡片,但如果没有风格指南,它会很高兴地生成十张模糊的卡片,未来的我会讨厌这些卡片。

于是写卡成了一种技能。

codebase-cards:回购探索协议

这个是用来把源代码变成Anki卡的。

代理被告知首先检查回购广度,确定架构、数据流、责任和陷阱,然后只将有用的发现转化为卡片。

该技能为甲板建造者的代码模式提供动力:

python deck_builder.py "overall architecture" --path ~/my/project --count 6

焦点提示改变了,但是探索协议保持不变。

这是第二种模式:

将可重用的行为放入技能中。

系统提示里没有。入口点之间不重复。没有隐藏在Python条件中。

一个技能只是一个文件,却改变了整个项目的形态。

一个行为层,三个表面

一旦行为生活在技能中,添加新的表面就变得容易多了。

建筑看起来是这样的:

.agents/skills/

┌──────────┼──────────┐

│ │ │

review-buddy plain-cards codebase-cards

│ │ │

└──────────┼──────────┘

LocalAgentConfig

┌─────────────────────┼─────────────────────┐

│ │ │

terminal tutor Telegram tutor deck builder

tutor.py telegram_tutor.py deck_builder.py

终端导师是最简单的表面:

async with Agent(config) as agent:

await run_interactive_loop(agent)

Telegram tutor以不同的方式使用同一代理:

async def chat_response(agent: Agent, prompt: str) -> str:

response = await agent.chat(prompt)

return "".join(token async for token in response)

deck builder在工作时流式输出:

response = await agent.chat(message)

async for token in response:

print(token, end="", flush=True)

不同的表面。相同的运行时间。同样的技能。

这是我最喜欢的部分。Telegram不需要复制的审查提示。甲板制造商不需要自己的卡片写作宣言。代码库模式不需要单独的特定于应用程序的原则。

他们都加载了相同的技能目录。

终极导师

终端版本是基线。

启动Anki,运行导师,自然问:

python tutor.py

然后:

quiz me on XYZ

导师列出到期卡,问一个问题,等我回答,揭示真实的Anki答案,对比,授课,建议评分。

Screenshot: Terminal tutor answering a flashcard

重要的是:它不会仅仅因为模型认为我得到了正确的答案就更新调度。

评审循环是人在循环中的设计:

Agent: I would rate this Good (3). You had the main idea but missed the date.

User: yes

Agent: rated 3. Next card...

或者我可以忽略它:

Agent: I would rate this Hard (2).

User: actually 1

Agent: rated Again (1). Let's reinforce it.

间隔重复是有状态的。差评影响未来日程。所以模型可以建议,但是我决定。

这不仅仅是一个即时的偏好。就是产品边界。

电报导师

第二面是电报。

不是因为电报很花哨。因为最好的学习app是我实际打开的那个。

电报机器人长期轮询机器人API,向同一个反重力代理发送消息,并返回响应。它还支持语音笔记:说出答案,转录它,并将转录本作为文本反馈给导师。

Screenshot: Telegram tutor answering a flashcard from phone

代理得到一个小的额外指令:

TELEGRAM_INSTRUCTIONS = """

You are chatting through Telegram on a phone. Keep replies short and plain

text only --- no markdown headers, tables, or code fences. One card per message.

"""

其他一切都是共享的。

同样的安奇工具。同样的钩子。同样的技能。

我还添加了到期卡轻推,无需花费模型代币。每30分钟,普通Python检查Anki甲板计数。如果纸牌正在等待,机器人会发出一个简短的提醒:

25 cards waiting (X 5, Y 8). Say 'quiz me' to start.

不需要LLM。不需要推理。只是确定性代码。

这成为一个有用的设计规则:

不要将该模型用于工作afor循环可以做到。

代理是做家教的。轻推只是一个计数器。

甲板建造者

第三个表面是一个甲板建设者。

它有两种模式。

网络模式:

python deck_builder.py "Ottoman Empire" --deck "History" --count 8

基本代码模式:

python deck_builder.py "error handling and edge cases" --path ~/my/project --count 6

Web模式为代理提供了一个小型的研究工具集:维基百科搜索、维基百科阅读和URL获取。然后,它要求代理使用plain-cards技巧。

代码库模式更有趣。SDK可以为代理提供工作空间范围内的内置文件工具。我启用了只读访问:

from google.antigravity.types import BuiltinTools, CapabilitiesConfig

config = LocalAgentConfig(

tools=add_notes, list_decks,

workspaces=code_path,

capabilities=CapabilitiesConfig(

enabled_tools=BuiltinTools.read_only()

),

skills_paths=".agents/skills",

)

这意味着代理可以检查目标回购,但不能编辑它。

对于甲板建造者来说,这是正确的许可边界。它需要读取代码和创建Anki笔记。它不需要修改项目。

这是哪里codebase-cards激活。代理研究回购,确定值得记住的概念,然后写卡片add_notes.

最后,我不相信模特的叙述。该脚本查询Anki来验证这些卡是否存在。

def cards_in_anki(deck: str) -> int:

result = json.loads(find_notes(f'deck:"{deck}" tag:auto-researched', 100))

return len(result) if isinstance(result, list) else 0

如果模型说它创建了卡片,但是Anki没有,脚本会轻推它再试一次。

这成为了另一条规则:

相信系统收据,不要相信模型叙述。

用触发器将它转变为环境

SDK还支持触发器:对外部事件做出反应并将消息推入代理的后台任务。

我使用文件更改触发器来生成代码库卡。

想法是:当我在一个项目中工作时,如果一个Python文件发生了变化,代理可以检查这个变化并决定它是否引入了值得记住的东西。

简化:

from google.antigravity.triggers import on_file_change

def make_watch_trigger(path, deck, tag):

async def on_change(ctx, changes):

paths = sorted({c.path for c in changes if c.path.endswith(".py")})

if not paths:

return

await ctx.send(

f"These files changed: {', '.join(paths)}. "

f"Create cards in deck {deck} if the change is worth remembering."

)

return on_file_change(path, on_change)

像这样运行:

python deck_builder.py "as I work" --path ~/my/project --watch

这是这个项目开始感觉不像聊天机器人,更像一个边车的地方。

我编辑代码。www.jpbara.com触发器唤醒代理。代码库技能告诉它如何检查变更。写卡技巧告诉它如何写好卡片。Anki工具创建音符。

没有新服务器。没有自定义计划程序。没有巨大的提示。

只是SDK触发器加技能。

我拒绝相信模型的那部分

技能是指导。

政策和挂钩就是执行。

这条线是一个有趣的演示和一个工具之间的区别,我可以把它连接到我真正的Anki系列。

反重力SDK有声明性的安全策略和生命周期挂钩。我两样都用了。

练习模式阻止计划写入

有时候想临时抱佛脚不碰Anki排班。

一个及时的指示是不够的。如果代理忘记并呼叫rate_card,时间表会发生变化。

因此练习模式在线束级别拒绝工具:

from google.antigravity.hooks import policy

policies = policy.confirm_run_command()

if practice_mode:

policies = policies + [

policy.deny("rate_card", name="practice_mode")

]

现在rate_card即使模型试图调用它,也会被阻止。

这就是我想要的安全感:不是共鸣,不是信任,不是"请不要"。运行时边界。

挂钩同步、备份、审核和恢复

SDK hook系统允许您在生命周期的各个点进行观察或干预。

我使用会话挂钩来同步Anki:

@hooks.on_session_start

async def sync_on_start():

sync_anki()

@hooks.on_session_end

async def sync_on_end():

sync_anki()

在note写下以下内容之前,我使用了一个预工具调用决策挂钩来备份一副牌:

@hooks.pre_tool_call_decide

async def backup_before_note_writes(tool_call):

if tool_call.name in ("add_note", "add_notes"):

backup_deck(tool_call.args"deck")

return hooks.HookResult(allow=True)

我使用了一个工具调用后检查挂钩来审计调度变更:

@hooks.post_tool_call

async def audit_scheduling_changes(result):

if result.name in {"rate_card", "undo", "suspend_card", "unsuspend_card"}:

append_jsonl("backups/scheduling_audit.jsonl", result)

我还使用了一个转换挂钩将难看的工具错误转换成模型可以操作的恢复提示:

@hooks.on_tool_error

async def recover_from_tool_error(error):

if isinstance(error, requests.Timeout):

return "AnkiConnect timed out. Ask the user to check Anki, then retry."

return None

这是SDK最强大的部分之一。

模型不需要记住审计自己。马具做到了。

该模型不需要在写入之前记住备份一副牌。是钩子做的。

该模型不会绕过实践模式。策略阻止了它。

模式变得清晰了:

工具赋予代理能力

技能赋予代理可重用的行为

政策定义决不能发生的事情

钩住围绕代理添加系统级保证

这种分离就是架构。

什么奏效了

一些事情比预期的要好。

普通的Python工具就足够了

我原本认为我可能需要立即建立一个主控制程序服务器。

我没有。

对于一个应用程序,定制Python函数更简单。SDK已经知道如何将它们作为工具公开。这使得第一个版本很小。

当您希望在多个客户机上使用相同的工具时,MCP仍然有用。但是对于一个SDK原生的app来说,Python函数是最短的路径。

技能使这个项目免于变成一碗汤

这是最大的胜利。

基本系统指令保持集中。详细的工作流程转移到了技能中。

当我改进写卡规则时,终端、电报和卡片组建造者都受益了。我不需要更新三个提示。

钩子使副作用不那么可怕

Anki不是一个玩具数据库。这是我真正的间隔重复时间表。

钩子给了我一个关于模型行为的确定性层:

在会话边界同步

写入前备份

计划更改后审计

从工具故障中恢复

这让代理感觉不像一个随机的聊天机器人,可以访问数据库。

触发器改变了应用程序的感觉

文件监视器很小,但是它改变了思维模式。

代理不再仅仅是我交谈的对象。它可以对周围发生的工作做出反应。

这就是SDK代理有趣的地方:不仅仅是聊天,还有事件驱动的工作。

什么不完美

一些警告。

技能不是硬性保证

技能就是指令。他们改善行为,但他们仍然是模型阅读指导。

如果某些事情是不可能的,使用一个策略或者移除这个工具。

这就是为什么实践模式否认rate_card而不是仅仅要求模型不要调用它。

AnkiConnect有锋利的边缘

AnkiConnect很简单,但也有怪癖。

举个例子,answerCards可以返回成功,即使是错误的卡id,除非你预先检查卡。如果便笺在Anki的浏览器窗口中打开,一些便笺更新会无声地失败。AnkiConnect也运行在Anki的Qt进程内部,所以你不应该把它当成一个高并发API。

修复是枯燥而重要的:验证内部工具。

代理环路之外的声音更简单

电报机器人支持语音应答,但我在代理循环外保留了转录。一个直接的双子座转录调用将语音笔记转换成文本,然后抄本进入导师。

这对于这个版本来说更简单也更可靠。

教训:使用SDK使架构更清晰。如果直接呼叫更简单,不要通过代理强制实现每个功能。

如何构建类似的东西

如果你想构建你自己的模式,我会按照这个顺序来做。

1.从一个真实的工作流程开始

不要从平台开始。

选择一个背后有真实状态的恼人的工作流:

抽认卡

GitHub问题

CRM更新

个人知识库

支持票

财务记录

国家很重要。当代理人能够对真实的东西采取行动时,他们就会变得有趣。

2.将系统包装成小型Python工具

保持工具无聊。

def search_items(query: str) -> str:

"""Search the user's records."""

...

def create_item(title: str, body: str) -> str:

"""Create a new record after user approval."""

...

注册它们:

config = LocalAgentConfig(

tools=search_items, create_item,

)

让工具验证输入。不要依赖模型来传递完美的id。

3.将任务行为转化为技能

创建技能文件夹:

.agents/skills/my-workflow/SKILL.md

最低限度的技能:


name: my-workflow

description: Use when helping the user process and update records in this system.


My Workflow

  1. Inspect the current record before changing it.

  2. Propose the change in plain language.

  3. Wait for user confirmation before writing.

  4. After writing, verify the record exists.

然后加载它:

config = LocalAgentConfig(

tools=TOOLS,

skills_paths=".agents/skills",

)

这是一步棋:不要让系统提示符永远增长。

4.添加不可协商的策略

如果一个工具不应该在某个模式下运行,就拒绝它。

policies = [

policy.deny("delete_record", name="no_deletes"),

]

如果shell执行需要确认,请保留默认保护:

policies = policy.confirm_run_command()

模型可能会误解某项技能。它不能忽视被拒绝的工具。

5.添加收据挂钩

对应该发生的事情使用钩子,不管模型是否记得它们:

审计日志

备份

同步

韵律学

卫生处理

错误校正

@hooks.post_tool_call

async def audit(result):

write_log({

"tool": result.name,

"result": result.result,

"error": result.error,

})

6.仅在行为可重复使用后添加另一个曲面

一旦行为生活在工具和技能中,第二个表面就变得便宜多了。

首先是终端。然后是电报、Slack、web、cron或文件触发器。

表面要薄。代理行为不应该存在。

更重要的是

构建人工智能特性的老方法是写一个大的提示,并希望模型遵循它。

这对真正的特工来说是不够的。

一个真正的代理需要关注点的分离:

Capabilities → tools

Reusable behavior → skills

Hard boundaries → policies

System guarantees → hooks

External events → triggers

User interface → thin surface

这就是反重力SDK让人愉悦的地方。我可以构建一个代理运行时,并在终端、电报和甲板生成中重用它。我可以保持家教行为SKILL.md文件而不是复制它。我可以用策略和挂钩来包装真正的副作用,而不是信任模型的行为。

安奇导师只是具体的例子。

这种模式很普遍。

支持代理可以将分类行为保留在技能中,将票证更新作为工具公开,通过策略拒绝破坏性写入,并通过挂钩审核每个状态更改。

代码评审代理可以在技能中保留评审规则,将GitHub作为工具公开,在评论前要求批准,并验证每一个发布的评审。

一个研究代理可以在技能中保持提取协议,使用文件触发器来处理新的论文,并且只在验证之后才编写结构化的输出。