Skip to content

Repository files navigation

Vicky | 未奇

未奇(AI生成)

Maven Central Release License Build Status Java

灵感来源于 SCP: 4K黑洞纪元的舰载人工智能 -- 未奇

一个精简、易维护的 Kotlin Agent 框架,对接 OpenAI 通用协议(兼容 OpenAI / DeepSeek / one-api / 本地推理服务等)。

底层用 aallam/openai-kotlin 作 API 客户端,agent 主循环、模式、权限、工具注册全部自写,方便按需改造。

模块 功能分类 功能点 说明
vicky-core Agent 运行时 多模式推理 内置 SILENT / VERBOSE / CHAT,支持继承 AgentMode 自定义模式
主循环引擎 maxSteps 限制的多轮推理,支持工具调用链与步数耗尽自动汇报
OpenAI 协议客户端 基于 openai-kotlin,兼容 OpenAI / DeepSeek / one-api / 本地推理,支持流式
消息 IO 抽象 InboundMessage / OutboundMessage / MessageSink 解耦输入输出
权限系统 ToolAuthorizer 框架层按 userId + toolName 鉴权
工具系统 双输出调用 ToolResult(toAgent, userReply) 分别喂回 Agent 与推送给用户
运行时工具管理 registerTool / unregisterTool,下一轮自动同步到 system prompt
上下文注入 ToolContext 提供会话历史、工具注册表、消息缓冲区等运行时访问
步数耗尽保护 maxSteps 用尽时自动追加系统提示,让模型整理现状并汇报
上下文管理 上下文压缩 ContextCompactor 超 token 上限时自动调用 LLM 生成摘要
安全防护 # Security 段始终固定拼接,不可被 agentMd 关闭,防御提示词注入/反取
system prompt 拼接 固定顺序:agentMdSecurityMemoryOutput rulesAvailable tools
记忆与 RAG 语义记忆(长时) 对话知识向量化持久化,每轮自动 recall 注入 system prompt
记忆蒸馏 定时任务(默认每日 02:00)将原始对话通过 LLM 压缩为精炼记忆
双层存储 RawMemory 保留原始对话用于回溯,Memory 存储蒸馏后精炼记忆
语义搜索 基于向量相似度检索记忆,支持按用户过滤
文件语义搜索 自动索引 Agent 启动时增量索引文本文件,只处理新增/修改
段落分块 文件按段落分块后向量化存储
语义检索 通过向量相似度搜索已索引文件内容
技能系统 SKILL.md 定义 Frontmatter(name / description)+ Markdown 内容描述技能
分组机制 同一目录(含 group.md)的技能折叠展示,按需展开
动态加载 支持按技能名或分组名加载全文,支持 enable / disable / delete 生命周期
vicky-ksp 注解处理 编译期代码生成 扫描 @VickyTool 生成 Tool 子类与 ToolRegistry
参数适配 @ToolParam(required = false) 或 Kotlin 默认值 → 可选参数
隐式注入 userId: String 自动接收调用者 ID,不进入 JSON Schema
上下文注入 ToolContext 参数自动注入运行时上下文
返回值包装 返回 ToolResult 直接使用,其他类型自动 toString() 包装
vicky-script 脚本引擎 TS/JS 执行 Rhino JS 引擎 + 内嵌 TypeScript 4.9.5 编译器 + Promise polyfill
三种脚本模式 顶层直接执行 / 导出 execute 作为工具 / 导出 onLoad+onUnload 插件模式
自动加载 config/scripts/.ts 文件启动时自动加载,无 name 时以文件名命名
Kotlin 互操作 Object 单例自动注入 JS 全局,普通类注入为构造函数代理
协程支持 coroutine.launch() 启动后台任务,脚本卸载时自动取消
主动消息 ctx.sendGroupMessage() / sendMessage() / setTimer()
生命周期安全 onLoad 10s 超时、循环依赖检测、onUnload 异常隔离
运行时 API 技能 扫描类自动生成 API 文档技能,归入 runtime-api 分组
通用 配置 AgentConfig 支持 model / apiKey / baseUrl / maxSteps / temperature / mode / debug / think / streaming 等参数

模块结构

项目由四个独立模块 + 一个根应用组成:

src/main/
├── vicky-core/          # 核心框架(Maven: io.github.zenthxsin:vicky-core)
│   └── kotlin/org/example/vicky/
│       ├── agent/           # Agent 基类、配置、模式、管理器
│       ├── annotations/     # @VickyTool @ToolParam @ToolGroup 注解
│       ├── context/         # ContextManager 接口
│       ├── io/              # InboundMessage / OutboundMessage / MessageSink
│       ├── llm/             # OpenAI 客户端工厂
│       ├── skill/           # Skill 数据模型 + SkillManager
│       └── tool/            # Tool 抽象、ToolRegistry、ToolContext、ToolResult、ToolAuthorizer
│
├── vicky-ksp/           # KSP 注解处理器(Maven: io.github.zenthxsin:vicky-ksp)
│   └── src/main/kotlin/org/example/vicky/ksp/
│       ├── VickyToolProcessor.kt       # @VickyTool 注解处理 → 生成 Tool 子类
│       └── VickyToolProcessorProvider.kt
│
├── vicky-script/        # 动态脚本模块(Maven: io.github.zenthxsin:vicky-script)
│   ├── kotlin/org/example/vicky/script/
│   │   ├── ScriptEngine.kt          # Rhino JS 引擎 + TS 编译 + Promise polyfill
│   │   ├── ScriptManager.kt         # 脚本生命周期 + 热加载
│   │   ├── ScriptToolBridge.kt      # JS Tool → vicky Tool 适配
│   │   ├── ClassAutoRegistry.kt     # classpath 类自动注入到 JS 全局
│   │   └── ScriptConfig.kt          # 配置模型
│   └── resources/
│       └── typescript.js            # 内嵌 TypeScript 4.9.5 编译器
│
├── vicky-vibe/          # 单轮自适应编排 / Code CLI(当前仓库内模块)
│   └── kotlin/org/example/vicky/vibe/
│       ├── agent/           # VibeAgent + 专用 ContextManager
│       ├── engine/          # 单轮 message pipeline
│       ├── orchestrator/    # VibeOrchestrator / system prompt / 结果适配
│       ├── status/          # 状态面板、观察者、快照
│       ├── task/            # 任务图与任务状态
│       ├── tool/            # tool use queue / tool use result
│       └── turn/            # turn request / runner / result
│
└── kotlin/org/example/vicky/    # 根应用(OneBot 机器人 + 内置工具实现)
    ├── agent/EmbeddingConfig.kt
    ├── buffer/MessageBuffer.kt
    ├── channel/onebot/          # OneBot WebSocket + Mirai 工具集(注解式)
    ├── config/ConfigManager.kt
    ├── context/                 # ContextBuilder / ContextCompactor / ConversationStore / DefaultContextManager
    ├── examples/                # ConsoleMain / StreamDumpMain / MindustryMITToolImpl
    ├── file/FileIndexService.kt
    ├── llm/                     # EmbeddingClient / OpenAiEmbeddingClient / EmbeddingClientFactory
    ├── logging/SilentMiraiLoggerFactory.kt
    ├── memory/                  # Memory / RawMemory / MemoryStore / QdrantMemoryStore / Distiller / DistillationScheduler
    ├── skill/SkillFrontmatterParser.kt / SkillLoader.kt
    ├── tool/builtin/            # 注解式内置工具(BuiltinToolImpl / InvokeSkillTool / ManageSkillsTool / ToolManagementTool)
    ├── tool/file/FileDownloader.kt
    └── vector/VectorStore.kt / QdrantVectorStore.kt

核心能力

三种处理模式(可自定义)

模式 toolsEnabled emitAgentText 工具 userReply
SILENT
VERBOSE
CHAT —(无工具)

模式是可继承的抽象类 AgentMode,可自定义:

object ReviewMode : AgentMode() {
    override val name = "REVIEW"
    override val toolsEnabled = true
    override val emitAgentText = false
    override val instructions = "You are reviewing code. Only speak to the user through tools."
}

工具系统

  • 工具调用双输出:每个工具返回 ToolResult(toAgent, userReply)toAgent 喂回 agent 继续推理,userReply(可选)实时推送给 user。
  • 步数耗尽汇报maxSteps 用尽时追加系统提示,让模型整理已有信息并向用户汇报。
  • 权限系统ToolAuthorizer.allow(userId, toolName) 框架层鉴权。
  • 运行时增删工具registerTool / unregisterTool,下一轮自动反映到 system prompt。

注解式工具定义(vicky-ksp)

使用 @VickyTool@ToolParam@ToolGroup 注解定义工具,KSP 自动生成 Tool 子类和 ToolRegistry

@ToolGroup(name = "my_tools")
object MyTools {

    @VickyTool(name = "ping", description = "Ping a host and measure latency.")
    suspend fun ping(
        @ToolParam(description = "Host to ping.") host: String,
        @ToolParam(description = "Timeout in ms.", required = false) timeout: Int = 5000,
    ): ToolResult {
        val result = doPing(host, timeout)
        return ToolResult(toAgent = result, userReply = result)
    }

    // ToolContext 注入:访问会话历史、工具注册表、消息缓冲区等
    @VickyTool(name = "clear", description = "Clear conversation context.")
    fun clear(ctx: ToolContext): ToolResult {
        ctx.contextManager.clear(ctx.conversationId)
        return ToolResult(toAgent = "done", userReply = "上下文已清除。")
    }

    // userId 注入:自动接收调用者 ID,不进入参数 Schema
    @VickyTool(name = "whoami", description = "Get caller user ID.")
    fun whoami(userId: String): ToolResult =
        ToolResult(toAgent = "userId=$userId")
}

特性:

  • @ToolParam(required = false) 或 Kotlin 默认值 → 可选参数
  • userId: String 参数 → 自动注入调用者 ID,不进入 JSON Schema
  • ToolContext 参数 → 注入运行时上下文(会话历史、工具注册表、消息缓冲区等)
  • 返回值是 ToolResult 直接使用,否则自动 toString() 包装
  • 生成代码位于 org.example.vicky.generated

技能系统(Skill)

技能是给 LLM 看的操作指南,通过 SKILL.md 文件定义。支持分组:同一分组的技能在 system prompt 中折叠展示,Agent 按需展开。

config/skills/
├── runtime-api/               ← 分组目录(含 group.md)
│   ├── group.md               ← 分组介绍
│   ├── AgentManager/
│   │   └── SKILL.md
│   └── ConfigManager/
│       └── SKILL.md
├── code-review/               ← 普通技能目录
│   └── SKILL.md
└── translate/
    └── SKILL.md

SKILL.md 格式:

---
name: code-review
description: 代码审查技能
---

你是一个代码审查专家。当用户请求代码审查时...

group.md 格式(分组介绍):

---
name: runtime-api
description: Vicky 运行时类和对象的 API 文档
---
此分组包含所有自动注入的运行时类。

分组展示逻辑:

  • 无分组技能:system prompt 中直接显示 name: description
  • 有分组技能:只显示分组名 + 描述,Agent 调用 invoke_skill(name="<group>") 查看组内技能

相关工具:

  • invoke_skill — 加载技能全文;传入分组名则返回组内所有技能
  • manage_skills — list / enable / disable / delete 技能,支持按 group 过滤

语义记忆系统(Qdrant)

  • 长时记忆(RAG):对话知识持久化到 Qdrant 向量数据库,每轮自动 recall 注入 system prompt。
  • 记忆蒸馏:定时(默认每天凌晨 2:00)将原始对话通过 LLM 压缩为精炼记忆。
  • 双层存储:原始对话(RawMemory)+ 蒸馏记忆(Memory),原始信息保留用于回溯。
  • 语义搜索:基于向量相似度的记忆检索,支持按用户过滤。

相关工具:

  • memory_store — 手动存储重要信息到长期记忆
  • memory_search — 语义搜索记忆
  • memory_distill — 手动触发记忆蒸馏

文件语义搜索

  • 自动索引:Agent 启动时自动索引根目录下的文本文件(增量索引,只处理新增/修改的文件)。
  • 分块存储:文件按段落分块,向量化后存入 Qdrant。
  • 语义搜索file_search 工具支持按语义搜索已索引的文件。

相关工具:

  • file_search — 语义搜索已索引的文件
  • file_index — 手动触发后台文件索引

动态脚本系统(vicky-script)

TypeScript 脚本的编译、执行和 Tool 桥接。config/scripts/ 下的 .ts 文件启动时自动加载

特性:

  • 零配置启动:脚本无需导出 name/execute/onLoad,顶层代码直接运行
  • 自动命名:不导出 name 时用文件名(hello.tshello
  • TypeScript 编译:内嵌 TypeScript 4.9.5 编译器,.ts.js
  • Kotlin object 自动注入AgentManagerConfigManagerMiraiToolImpl 等单例脚本中直接使用
  • 普通类自动注入:其他类注入为构造函数代理,new ClassName(...) 创建实例
  • 运行时 API 技能自动生成:扫描时自动为每个类生成 API 文档技能,归入 runtime-api 分组
  • 真协程coroutine.launch() 启动后台异步任务,脚本卸载时自动取消
  • ctx 主动发消息ctx.sendGroupMessage()/ctx.sendMessage()/ctx.setTimer()
  • 生命周期钩子:可导出 onLoad() / onUnload() 函数
  • 生命周期安全:onLoad 超时(10s)、循环依赖检测、onUnload 异常不阻止卸载

三种模式:

// 1. 顶层直接执行(不导出任何东西)
var bot = MiraiToolImpl.bot;
java.lang.System.out.println("Bot: " + bot.getNick());

// 2. 定义工具(导出 execute)
var name = "hello";
var description = "打招呼";
async function execute(ctx, args) {
    return { toAgent: "hi", userReply: "你好!" };
}

// 3. 插件模式(导出 onLoad/onUnload)
var name = "my_plugin";
function onLoad() { /* 初始化 */ }
function onUnload() { /* 清理 */ }

ctx API(仅在 execute 内可用):

  • ctx.userId / ctx.conversationId / ctx.groupId — 只读属性
  • ctx.sendGroupMessage(groupId, text) — 发群消息
  • ctx.sendMessage(targetId, text) — 发私聊消息
  • ctx.setTimer(intervalMs, callback) — 定时器,返回 timer.cancel()

协程:

coroutine.launch(function(co) {
    co.delay(5000);                        // 非阻塞延迟
    ctx.sendGroupMessage("123", "5秒后");  // execute 内可用 ctx
});

运行时访问(无需 import):

var agents = AgentManager.all();
var config = ConfigManager.loadOrCreate();
var bot = MiraiToolImpl.bot;
var f = new File("./config/config.json");
var content = Files.readString(f.toPath());

根应用提供 manage_scripts 工具,Agent 可按需加载/卸载/重载脚本。启动时自动加载所有 .ts 文件。

Vibe 编排与 Code CLI(vicky-vibe)

vicky-vibe 提供一个更接近 Claude Code / Kode 风格的单轮自适应编排内核,不再依赖固定多阶段 JSON 输出流水线,而是围绕一轮 turn 内的 message history、tool calling、tool result 回灌和收尾总结来运行。

当前能力:

  • 单轮 adaptive loop:一次请求内动态决定“思考 → 调工具 → 基于结果继续推进 → 收尾”。
  • 连续对话conversationId 驱动上下文复用,可关闭 resetContextEachTurn 形成持续会话。
  • 可嵌入 orchestrator API:外部项目可直接复用 VibeOrchestratorVibeTurnRunnerVibeSystemPromptBuilder
  • 过程消息流:通过 MessageSink 输出 AgentReply / ToolReply / Debug / Think / TokenUsage,可自行渲染成终端 UI、聊天 UI 或状态栏。
  • 任务流:工具调用会写入 TaskGraph,便于在 CLI 或宿主应用展示进行中的步骤。

核心入口:

  • org.example.vicky.vibe.orchestrator.VibeOrchestrator
  • org.example.vicky.vibe.turn.VibeTurnRunner
  • org.example.vicky.vibe.orchestrator.VibeSystemPromptBuilder
  • org.example.vicky.examples.VibeCodeCliMain

适合的使用方式:

  • 在现有机器人/服务里嵌入一个 code agent 回合执行器
  • 复用 Vicky 的工具系统做交互式终端或 IDE 助手
  • 在别的 JVM 项目里直接接入连续对话 + 工具调用 + 状态流

Vibe Code CLI 示例

仓库内提供 org.example.vicky.examples.VibeCodeCliMain 作为交互式 code CLI 示例,直接复用当前 config.jsonagentMd 配置。

特性:

  • 多行输入,空行发送
  • /help/clear/status/exit 命令
  • 持续对话(默认复用同一 conversationId
  • 实时显示思考、工具、任务和 token 用量消息流

建议在 IDE 中直接运行 VibeCodeCliMain.main();如果用 Gradle 运行,也可以把该入口配置为应用主类后启动。

上下文管理

  • 上下文压缩ContextCompactor 在会话历史超出 token 上限时自动调用 LLM 生成摘要。
  • 内置安全防护:始终拼接、无法被 agentMd 关闭,抵御提示词反取 / 注入 / 调试输出。

调试输出

  • debug = true:框架运行日志(每步推理、工具调用、上下文清除等)。
  • think = true:agent 中间思考文本。

两者都通过 MessageSink 推送(OutboundMessage.Debug / Think),由外部决定如何展示。

使用方式

作为 Maven 依赖

dependencies {
    implementation("io.github.zenthxsin:vicky-core:xxx")
    // 可选:KSP 注解处理器
    ksp("io.github.zenthxsin:vicky-ksp:xxx")
    // 可选:动态脚本支持
    implementation("io.github.zenthxsin:vicky-script:xxx")
}

编写 Agent 子类

class MyAgent(config: AgentConfig) : Agent(config) {
    override val contextManager = DefaultContextManager(
        store = ConversationStore(),
        builder = ContextBuilder(config.agentMd),
        compactor = ContextCompactor(config, OpenAiClientFactory.create(config)),
    )

    override val sink = MessageSink { out ->
        when (out) {
            is OutboundMessage.AgentReply -> println("[agent] ${out.content}")
            is OutboundMessage.ToolReply  -> println("[tool] ${out.content}")
            is OutboundMessage.Debug      -> println("[debug] ${out.content}")
            is OutboundMessage.Think      -> println("[think] ${out.content}")
        }
    }

    override val authorizer = ToolAuthorizer { userId, toolName ->
        if (toolName == "shutdown") userId == "admin" else true
    }
}

配置并运行

val agent = MyAgent(
    AgentConfig(
        model        = ModelId("deepseek-v4-flash"),
        apiKey       = "sk-...",
        baseUrl      = "http://192.168.0.108:3000/v1",
        mode         = AgentMode.SILENT,
        maxSteps     = 6,
        agentMd      = "你是 Vicky,一个简洁的助手。",
        debug        = false,
        builtinTools = true,
    )
)
agent.receive(InboundMessage("user1", "ping 192.168.0.108"))

Android 作为库使用

Android 应用可以直接依赖现有的 vicky-corevicky-script 坐标,不需要依赖根应用或 vicky-vibe

dependencies {
    implementation("io.github.zenthxsin:vicky-core:<version>")
    implementation("io.github.zenthxsin:vicky-script:<version>")
    ksp("io.github.zenthxsin:vicky-ksp:<version>")
}

库产出 Java 17 字节码。脚本模块建议 Android API 26 及以上,并由宿主开启网络权限、管理 协程和后台生命周期。脚本由宿主以名称和内容加载,不扫描或监听 Android 文件目录:

val bridge = ScriptManager.loadScript(
    scriptName = "hello",
    scriptContent = scriptText,
)

// 需要自动注册脚本导出的 Tool 时:
ScriptManager.loadAndRegister("hello", scriptText, agent.tools)

Android/Dex 无法使用 JVM classpath 扫描。需要暴露给脚本的宿主类应显式注册:

ClassAutoRegistry.registerAll(MyApi::class.java, MyModel::class.java)

会话持久化可使用跨 JVM/Android 的 JsonSessionStore(filesDir);JDBC SqliteSessionStore 保留给桌面 JVM。HTTP MCP 可用,stdio MCP 在 Android 上不支持。 Rhino extend() 的 ART 动态类生成仍在适配中,Android 脚本暂时应使用显式注册的具体类。

配置参数

根应用使用 config/config.json 配置文件,参见 ROOT-MODULE.md

AgentConfig

参数 默认值 说明
model LLM 模型 ID
apiKey API 密钥
baseUrl null OpenAI 兼容端点,null = 官方
maxSteps 8 单次 receive 最大推理轮数
maxMemoryRounds 50 最多保留多少轮用户消息
maxContextLength 0 上下文 token 上限,0 = 不限制
mode SILENT 运行模式:SILENT / VERBOSE / CHAT
temperature null 采样温度
agentMd "You are a helpful assistant." system prompt 人设/指令
debug false 框架运行日志
think false Agent 中间思考文本
streaming true 是否使用流式请求
builtinTools true 是否自动注册内置工具

system prompt 拼接顺序

  1. agentMd —— 人设/指令
  2. # Security —— 内置安全防护(固定拼接,不可关闭)
  3. # Memory —— 长期记忆 recall(如果启用)
  4. # Output rules —— 当前模式说明
  5. # Available tools —— 已注册工具名 + 简介

主循环

1. recall 蒸馏记忆 → 注入 system prompt
2. 用户消息追加到 history
3. for step in 0 until maxSteps:
     resp = chat.completion(history + tools)
     if resp.toolCalls 非空:
         执行工具 → result
         history += assistantToolCall + toolMessage(result.toAgent)
         result.userReply?.let { emit(ToolReply) }
         if result.endTurn → return
         continue
     else:
         emit(AgentReply)
         return
4. 步数耗尽:注入系统提示,让模型整理现状并向用户汇报
5. finally: 保存原始记忆到 Qdrant

测试

./gradlew test

运行脚本模块测试:

./gradlew :src:main:vicky-script:test

许可证

Apache License 2.0

About

一个轻量化的ai agent框架

Topics

Resources

Stars

6 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages