Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/plugin-api-v3.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
- uses: actions/checkout@v4
with:
repository: akashic-plugins/citation
ref: a9abeb31c25458b8e799dc6aae25d3e83b912c83
ref: b82453cd1eae71da8d25eb31aada30b01c659b54
path: .citation
- uses: actions/setup-python@v5
with:
Expand All @@ -49,7 +49,7 @@ jobs:
cache-dependency-path: .akashic-core/requirements.txt
- name: Install pinned Core dependencies
run: python -m pip install -r .akashic-core/requirements.txt pytest pytest-asyncio
- name: Compare v2 and v3 Meme receipts
- name: Verify Meme v3 behavior
env:
AKASHIC_AGENT_ROOT: .akashic-core
AKASHIC_CITATION_ROOT: .citation
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
| `dashboard_module = "dashboard.py"` | 声明 v3 Dashboard |
| `workspace_roots = ("memes",)` | 取得 Core 分配的表情包资产根 |

插件通过模块命名导出声明静态贡献,通过 `apply(ctx, config)` 自行构建 `MemeCatalog`/`MemeDecorator` 并注册接入点。`citation.protocol` 是硬依赖:Citation 先剥离 cited metadata 并保留 meme tag,Meme 再完成媒体装饰,Citation cleanup 最后清除残留协议标签。Core 只分配生命周期、依赖顺序与 workspace root;Meme 的目录结构、随机选图和 Dashboard 仍由插件拥有。旧 `MemePlugin` 暂时保留,只用于迁移期差分验证。
插件通过模块命名导出声明静态贡献,通过 `apply(ctx, config)` 自行构建 `MemeCatalog`/`MemeDecorator` 并注册接入点。`citation.protocol` 是硬依赖:Citation 先剥离 cited metadata 并保留 meme tag,Meme 再完成媒体装饰,Citation cleanup 最后清除残留协议标签。Core 只分配生命周期、依赖顺序与 workspace root;Meme 的目录结构、随机选图和 Dashboard 仍由插件拥有。

---

Expand All @@ -24,7 +24,7 @@

从工作区路径(`workspace/memes/`)加载 `manifest.json`,构建 `MemeCatalog` 和 `MemeDecorator` 实例。`MemeCatalog` 按需检测 manifest 的 mtime,变动时自动热重载,不需要重启。

### 2. 注入 catalog(MemePromptModule)
### 2. 注入 catalog

每轮推理前,调用 `catalog.build_prompt_block()` 把启用的表情包类别(名称、描述、别名)拼成文本块,追加到系统 prompt 底部,告知 LLM 可以在回复中嵌入 `<meme:tag>` 标签。如果 catalog 为空则跳过注入。

Expand Down
5 changes: 5 additions & 0 deletions akashic.plugin.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
schema_version = 1
name = "meme"
version = "1.0.0"
api_version = 3
entrypoint = "plugin.py"
71 changes: 0 additions & 71 deletions plugin.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
from __future__ import annotations

import re
from pathlib import Path
from typing import Any, cast

from agent.lifecycle.composition import (
AFTER_REASONING_PREPROCESS_EVENT,
PROMPT_RENDER_EVENT,
)
from agent.lifecycle.types import AfterReasoningCtx, PromptRenderCtx
from agent.plugin_composition import Context, ServiceKey
from agent.plugins import Plugin, on_after_reasoning
from agent.prompting import PromptSectionRender
from .runtime import MemeCatalog, MemeDecorator

_CTX_SLOT = "prompt:ctx"
_MEME_RE = re.compile(
r"(?<!`)<meme:([a-zA-Z0-9_-]+)>(?!`)",
re.IGNORECASE,
Expand Down Expand Up @@ -44,22 +40,6 @@ def decorate_meme_ctx(ctx: AfterReasoningCtx, decorator: MemeDecorator) -> None:
ctx.meme_tag = decorated.tag


class MemePromptModule:
slot = "meme.prompt"
requires = ("prompt_render.emit", "citation.prompt", _CTX_SLOT)
produces = (_CTX_SLOT,)

def __init__(self, plugin: "MemePlugin") -> None:
self._plugin = plugin

async def run(self, frame: Any) -> Any:
ctx = frame.slots.get(_CTX_SLOT)
if not isinstance(ctx, PromptRenderCtx):
return frame
append_meme_prompt(ctx, self._plugin.catalog)
return frame


api_version = 3
name = "meme"
version = "1.0.0"
Expand Down Expand Up @@ -88,51 +68,6 @@ def answer_listener(answer: AfterReasoningCtx) -> None:
_ = await ctx.on(AFTER_REASONING_PREPROCESS_EVENT, answer_listener)


class MemePlugin(Plugin):
api_version = 2

@classmethod
def dashboard_module(cls) -> str | None:
return "dashboard.py"

name = "meme"
version = "1.0.0"

@classmethod
def skill_roots(cls) -> tuple[str, ...]:
return ("skills",)

_catalog: Any = None
_decorator: Any = None

async def prepare(self) -> None:
memes_dir = (
_workspace(self.context.plugin_dir, self.context.workspace) / "memes"
)
self._catalog = MemeCatalog(memes_dir)
self._decorator = MemeDecorator(self._catalog)

def prompt_render_modules(self) -> list[object]:
return [MemePromptModule(self)]

@on_after_reasoning()
async def decorate_meme(self, ctx: AfterReasoningCtx) -> AfterReasoningCtx:
decorate_meme_ctx(ctx, self.decorator)
return ctx

@property
def catalog(self) -> Any:
if self._catalog is None:
raise RuntimeError("meme 插件尚未初始化")
return self._catalog

@property
def decorator(self) -> Any:
if self._decorator is None:
raise RuntimeError("meme 插件尚未初始化")
return self._decorator


def _extract_meme_tag(response: str) -> tuple[str, str | None]:
match = _MEME_RE.search(response)
if match is None:
Expand All @@ -141,9 +76,3 @@ def _extract_meme_tag(response: str) -> tuple[str, str | None]:
cleaned = re.sub(r"[ \t]+\n", "\n", cleaned)
cleaned = re.sub(r" {2,}", " ", cleaned)
return cleaned.strip(), match.group(1).lower()


def _workspace(plugin_dir: Path, configured: Path | None) -> Path:
if configured is not None:
return configured
return cast(Path, plugin_dir.parent.parent)
Loading
Loading