はじめに
Agent クラスは調整センターであり、LLM Router、Tool Registry、およびメモリを接続します。ユーザーがメッセージを送信するたびに、エージェントはフロー全体を調整します。つまり、メッセージの構築 → LLM の呼び出し → ツールの実行 → 必要に応じて再度ループします。
1. エージェント クラスの構造
// packages/core/src/agent/agent.ts
export interface AdditionalTool {
definition: ToolDefinition;
handler: ToolHandler;
}
export class Agent {
private config: AgentConfig;
private llm: LLMAdapter;
private toolRegistry: ToolRegistry;
private memory: MemoryManager;
constructor(
config: AgentConfig,
llm: LLMAdapter,
toolRegistry: ToolRegistry,
memory: MemoryManager,
) {
this.config = config;
this.llm = llm;
this.toolRegistry = toolRegistry;
this.memory = memory;
}
// Synchronous chat — trả về full response
async chat(
userMessage: string,
context: ToolContext,
additionalTools?: AdditionalTool[],
): Promise<string> {
const messages = await this.buildMessages(userMessage, context);
const tools = this.gatherTools(additionalTools);
let iterations = 0;
const maxIterations = this.config.maxToolIterations || 10;
while (iterations < maxIterations) {
const response = await this.llm.chat(messages, tools);
iterations++;
// No tool calls → final response
if (!response.toolCalls?.length) {
await this.memory.save(context.sessionId, userMessage, response.content);
return response.content;
}
// Execute tools & add results to messages
messages.push({
role: 'assistant',
content: response.content,
toolCalls: response.toolCalls,
});
const results = await this.executeToolCalls(response.toolCalls, context);
for (const result of results) {
messages.push({
role: 'tool',
content: JSON.stringify(result.result),
toolCallId: result.toolCallId,
});
}
}
return 'Max tool iterations reached';
}
}
2. buildMessages — コンテキストアセンブリ
private async buildMessages(
userMessage: string,
context: ToolContext,
): Promise<LLMMessage[]> {
const messages: LLMMessage[] = [];
// 1. System prompt
messages.push({
role: 'system',
content: this.buildSystemPrompt(),
});
// 2. Memory context (conversation history)
const history = await this.memory.getHistory(context.sessionId, {
limit: 20,
summarizeOlder: true,
});
for (const entry of history) {
messages.push({ role: entry.role, content: entry.content });
}
// 3. RAG context (if knowledge base available)
const ragContext = await this.memory.searchKnowledge(userMessage, context.tenantId);
if (ragContext.length > 0) {
messages.push({
role: 'system',
content: `Relevant knowledge:\n${ragContext.map(d => d.content).join('\n---\n')}`,
});
}
// 4. Current user message
messages.push({ role: 'user', content: userMessage });
return messages;
}
private buildSystemPrompt(): string {
const parts = [
this.config.persona || 'You are a helpful AI assistant.',
];
if (this.config.systemPrompt) {
parts.push(this.config.systemPrompt);
}
// Add available tool descriptions
const tools = this.toolRegistry.getDefinitions();
if (tools.length > 0) {
parts.push(`Available tools: ${tools.map(t => t.name).join(', ')}`);
}
return parts.join('\n\n');
}
3. ツールの実行サイクル
private async executeToolCalls(
toolCalls: ToolCall[],
context: ToolContext,
): Promise<ToolResult[]> {
const results: ToolResult[] = [];
for (const call of toolCalls) {
console.log(`[Agent] Executing tool: ${call.name}`);
const start = performance.now();
try {
const result = await this.toolRegistry.execute(
call.name,
call.arguments,
context,
);
results.push({
...result,
toolCallId: call.id,
});
} catch (error) {
results.push({
toolCallId: call.id,
success: false,
result: null,
error: error instanceof Error ? error.message : String(error),
duration: performance.now() - start,
});
}
}
return results;
}
private gatherTools(additional?: AdditionalTool[]): ToolDefinition[] {
const tools = [...this.toolRegistry.getDefinitions()];
if (additional) {
for (const tool of additional) {
// Register temporarily
this.toolRegistry.register(tool.definition, tool.handler);
tools.push(tool.definition);
}
}
return tools;
}
4. フロー図
User Message
│
▼
buildMessages()
│ system prompt + history + RAG context + user message
▼
LLM.chat(messages, tools)
│
├── No tool_calls → Return response ✅
│
└── Has tool_calls
│
▼
executeToolCalls()
│ results added to messages
▼
Loop back to LLM.chat() ←─── (max N iterations)
各反復: LLM は追加のツール結果を受け取り、追加のツールを呼び出すか、最終的な回答を返すことができます。
5. まとめ
- エージェント はチャット フロー全体を調整します - ビジネス ロジックはなく、調整のみです
- buildMessages() — 複数のソースからのアセンブリ コンテキスト
- ツール ループ — LLM がどのツールを何回呼び出すかを決定します
- maxToolIterations — 無限ループを回避するための安全制限
次の記事: ストリーミングと EventBus — リアルタイムの応答配信。