@innerlife/agent
v0.1.2
Published
Character-agent runtime for building NPCs and dialogue agents with memory, emotion, secrets, personality, and inner continuity
Readme
InnerLife Agent
用于构建有内心连续性的 Character Agent 的 TypeScript runtime,支持人格驱动对话、情绪状态、多层记忆、秘密/心智理论、工具调用、世界书和对话历史召回。
InnerLife 的目标不是单纯做记忆框架,而是给 NPC、虚拟角色和长期对话 Agent 提供稳定的人格、情绪、秘密、关系和自我连续性,让角色像一个有自己想法的个体持续存在。
快速开始
安装依赖
cd innerlife-agent
npm install环境变量配置
创建 .env 文件(或在系统环境中设置):
# ─── 主 LLM(用于对话、决策)───────────────────────
OPENAI_API_KEY=sk-your-key
OPENAI_BASE_URL=https://api.openai.com/v1 # 可选,默认 OpenAI 官方
OPENAI_MODEL=gpt-4o # 可选,默认 gpt-4o
# ─── 激素 LLM(可选,默认复用主 LLM)──────────────────
# 推荐使用轻量模型降成本
HORMONE_LLM_API_KEY=sk-your-key
HORMONE_LLM_BASE_URL=https://api.deepseek.com/v1
HORMONE_LLM_MODEL=deepseek-chat
# ─── 记忆压缩器 LLM(可选,默认复用主 LLM)──────────────
COMPRESSOR_LLM_API_KEY=sk-your-key
COMPRESSOR_LLM_BASE_URL=https://api.deepseek.com/v1
COMPRESSOR_LLM_MODEL=deepseek-chat
# ─── 如果使用 Anthropic ──────────────────────────────
ANTHROPIC_API_KEY=sk-ant-your-key
**envPrefix机制**:每个 Provider 实例可通过envPrefix指定环境变量前缀。例如envPrefix: 'HORMONE_LLM'会读取HORMONE_LLM_API_KEY、HORMONE_LLM_BASE_URL、HORMONE_LLM_MODEL。查找优先级:显式参数 →
{PREFIX}_* 环境变量 →OPENAI_*默认环境变量 → 硬编码默认值
编译 & 测试
# 类型检查
npm run lint
# 编译
npm run build
# 运行测试
npm test
# 开发模式(监听编译)
npm run dev
# 测试监听模式
npm run test:watch使用示例
基础用法
import {
AgentFactory,
EventPriority,
OpenAIProvider,
Runner,
} from '@innerlife/agent';
// 1. 创建工厂
const factory = new AgentFactory({
provider: new OpenAIProvider(), // 自动读取 OPENAI_* 环境变量
});
// 2. 构建 Agent
const agent = await factory.create({
id: 'npc-001',
personaPath: './personas/my-character.yaml',
});
// 3. 推入一条事件
agent.inbox.push({
id: 'evt-001',
type: 'player:dialogue',
source: 'player-001',
text: '你好!',
priority: EventPriority.PLAYER_COMMAND,
timestamp: Date.now(),
ttl: 60_000,
});
// 4. 执行一轮
const runner = new Runner();
const result = await runner.run(agent);
console.log(result.dialogue);多模型配置
const factory = new AgentFactory({
// 主 LLM —— 大模型,负责对话和决策
provider: new OpenAIProvider({ model: 'gpt-4o' }),
// 激素 LLM —— 轻量模型,负责情绪感知
hormoneProvider: new OpenAIProvider({
envPrefix: 'HORMONE_LLM', // 读取 HORMONE_LLM_* 环境变量
}),
// 记忆压缩 —— 轻量模型,负责记忆蒸馏
compressorProvider: new OpenAIProvider({
envPrefix: 'COMPRESSOR_LLM',
}),
});接入 OpenAI 兼容服务
框架支持所有 OpenAI Chat Completions 兼容 API:
// DeepSeek
const deepseek = new OpenAIProvider({
apiKey: 'sk-xxx',
baseURL: 'https://api.deepseek.com/v1',
model: 'deepseek-chat',
});
// Moonshot
const moonshot = new OpenAIProvider({
apiKey: 'sk-xxx',
baseURL: 'https://api.moonshot.cn/v1',
model: 'moonshot-v1-8k',
});
// 本地 Ollama
const ollama = new OpenAIProvider({
baseURL: 'http://localhost:11434/v1',
model: 'llama3',
apiKey: 'ollama', // Ollama 不需要真实 key
});
// 也可通过环境变量配置,无需改代码
// OPENAI_BASE_URL=http://localhost:11434/v1
// OPENAI_MODEL=llama3
// OPENAI_API_KEY=ollama
const localProvider = new OpenAIProvider();使用 Anthropic
import { AnthropicProvider } from '@innerlife/agent';
const factory = new AgentFactory({
provider: new AnthropicProvider({
model: 'claude-sonnet-4-20250514',
}),
});人格 YAML 配置
创建 personas/my-character.yaml:
name: 云无涯
description: 清风宗内门弟子,性格沉稳内敛
coreTraits:
- 沉默寡言
- 重承诺
- 对修炼极其认真
speechStyle:
patterns:
- "……{content}。"
- "{content},罢了。"
vocabulary:
- 道友
- 前辈
forbiddenWords:
- 哈哈
- 太棒了
tone: 沉稳
identityAnchors:
- fact: 清风宗内门弟子
importance: critical
- fact: 修炼无相剑诀
importance: high
constraints:
- 不主动与人攀谈
- 不轻易表露情绪注册工具
import { z } from 'zod';
import type { ToolDefinition } from '@innerlife/agent';
const WeatherParams = z.object({
location: z.string().describe('地点'),
});
const checkWeatherTool: ToolDefinition = {
name: 'check_weather',
description: '查看当前天气',
tags: ['weather', 'observation'],
parameters: WeatherParams,
canUse: () => true,
execute: async (params) => {
const { location } = params as z.infer<typeof WeatherParams>;
return {
toolName: 'check_weather',
success: true,
output: `${location}当前天气晴,25°C。`,
metadata: { weather: '晴', temperature: 25 },
};
},
};
// 单 Agent 快速注册
agent.toolRegistry.register(checkWeatherTool);多 Agent 场景下,推荐把工具注册到共享 ToolRegistry,再用每个 Agent 的 tools 白名单、工具的 scope 和 canUse(agentId, context) 控制可见性,避免逐个 Agent 重复注册:
import { AgentFactory, ToolRegistry } from '@innerlife/agent';
const toolRegistry = new ToolRegistry();
toolRegistry.registerBatch([
checkWeatherTool,
// sendMessageTool,
// lookAroundTool,
]);
const factory = new AgentFactory({
provider,
toolRegistry,
});
const npcA = await factory.create({
id: 'npc-a',
personaPath: './personas/npc-a.yaml',
tools: ['check_weather'],
});
const npcB = await factory.create({
id: 'npc-b',
personaPath: './personas/npc-b.yaml',
tools: [], // npcB 看不到 check_weather
});注册 Hooks
import type { Hook } from '@innerlife/agent';
const riskControlHook: Hook = {
name: 'risk-control',
phase: 'post-output',
priority: 10,
readonly: true,
critical: false,
execute: async (ctx) => {
// 检查输出是否合规
return { action: 'continue' };
},
};
agent.hooks.register(riskControlHook);Hook 的 phase 必须是具体管道阶段,例如 pre-inbox、post-worldbook、pre-llm、post-output。如果多个 Agent 都需要同一批 Hooks,建议封装统一注册函数:
import type { Agent, Hook } from '@innerlife/agent';
const commonHooks: Hook[] = [riskControlHook];
function registerCommonHooks(agent: Agent) {
for (const hook of commonHooks) {
agent.hooks.register(hook);
}
}添加世界书条目
agent.worldBook.addEntry({
id: 'realm-system',
title: '境界体系',
content: '修仙境界从低到高:练气、筑基、金丹、元婴、化神...',
category: '修炼体系',
keywords: ['境界', '修炼', '突破'],
visibility: 'public_knowledge',
alwaysActive: true,
priority: 5,
});
agent.worldBookRetriever.rebuildIndex();注意:world_truth 表示只有叙事者/游戏系统知道的真相,永远不会注入任何 Agent 的 prompt。角色应该知道的常识用 public_knowledge;只有部分身份知道的内容用 character_known + knowledgeScope。
多 Agent 场景下,不要手写复制世界书。把世界书词条维护成一份共享数据,然后在创建 Agent 时批量挂载:
import type { AgentConfig, LoreEntry } from '@innerlife/agent';
const commonLore: LoreEntry[] = [
{
id: 'realm-system',
title: '境界体系',
content: '修仙境界从低到高:练气、筑基、金丹、元婴、化神...',
category: '修炼体系',
keywords: ['境界', '修炼', '突破'],
visibility: 'public_knowledge',
alwaysActive: true,
priority: 5,
},
];
async function createGameAgent(config: AgentConfig) {
const agent = await factory.create(config);
agent.worldBook.addEntries(commonLore);
agent.worldBookRetriever.rebuildIndex();
registerCommonHooks(agent);
return agent;
}项目结构
innerlife-agent/
├── src/
│ ├── index.ts # 公共 API 导出
│ ├── core/ # Agent + Runner + AgentFactory
│ ├── hormone/ # 激素情绪系统
│ ├── inbox/ # 事件收件箱
│ ├── persona/ # 人格配置
│ ├── memory/ # 多层记忆系统
│ ├── prompt/ # Prompt 组装 + 信任边界
│ ├── tools/ # 工具注册与执行
│ ├── skills/ # 技能系统
│ ├── hooks/ # Hooks 扩展引擎
│ ├── worldbook/ # 世界书
│ ├── relationship/ # 关系建模
│ ├── providers/ # LLM Provider(OpenAI / Anthropic)
│ └── observability/ # EventBus + Tracer
├── __tests__/ # 测试用例
├── docs/ # 架构、指南、专题文档
├── scripts/ # 本地测试和 TUI 调试脚本
├── package.json
├── tsconfig.json
└── vitest.config.ts调试技巧
使用 EventBus 监听内部事件
agent.eventBus.on('emotion:applied', (data) => {
console.log('情绪变化:', data);
});
agent.eventBus.on('tool:executed', (data) => {
console.log('工具调用:', data);
});
agent.eventBus.on('memory:flush:complete', (data) => {
console.log('记忆写入:', data);
});查看执行追踪
Runner 每轮返回的 TurnResult 包含完整的 ExecutionTrace:
agent.inbox.push(event);
const result = await runner.run(agent);
console.log(JSON.stringify(result.trace, null, 2));
// 包含:激素阶段、世界书命中、token 用量、工具调用、hooks 执行使用 FileSystemStore 查看记忆
默认存储路径为 ./data/agents/{agentId}/memory,可直接查看 JSON 文件:
cat ./data/agents/npc-001/memory/store.json | jq .许可证
UNLICENSED
