[G1-Sync] Manual knowledge update
This commit is contained in:
@@ -0,0 +1,341 @@
|
||||
---
|
||||
id: ai-langgraph-agent-frameworks
|
||||
title: Agent Frameworks — LangGraph / Mastra / CrewAI
|
||||
category: Coding
|
||||
status: draft
|
||||
source_trust_level: B
|
||||
verification_status: conceptual
|
||||
created_at: 2026-05-09
|
||||
updated_at: 2026-05-09
|
||||
tags: [ai, agent, framework, vibe-coding]
|
||||
tech_stack: { language: "TS / Python", applicable_to: ["Backend"] }
|
||||
applied_in: []
|
||||
aliases: [LangGraph, LangChain, Mastra, CrewAI, AutoGen, agent state, multi-agent]
|
||||
---
|
||||
|
||||
# Agent Frameworks
|
||||
|
||||
> Agent 구현 framework. **LangGraph (state machine), Mastra (TS modern), CrewAI (multi-agent), AutoGen (Microsoft)**. 자체 implementation 도 좋음 — overkill 주의.
|
||||
|
||||
## 📖 핵심 개념
|
||||
- State graph: node + edge.
|
||||
- State persistence: checkpoint.
|
||||
- Streaming: 매 step 응답.
|
||||
- Human-in-the-loop: 중간 confirm.
|
||||
|
||||
## 💻 코드 패턴
|
||||
|
||||
### LangGraph (Python / JS)
|
||||
```ts
|
||||
import { StateGraph, END, MemorySaver } from '@langchain/langgraph';
|
||||
import { ChatAnthropic } from '@langchain/anthropic';
|
||||
import { z } from 'zod';
|
||||
|
||||
const State = z.object({
|
||||
messages: z.array(z.any()),
|
||||
toolResults: z.record(z.string()).optional(),
|
||||
});
|
||||
|
||||
const llm = new ChatAnthropic({ model: 'claude-opus-4-7' });
|
||||
|
||||
const graph = new StateGraph(State)
|
||||
.addNode('agent', async (state) => {
|
||||
const response = await llm.invoke(state.messages);
|
||||
return { messages: [...state.messages, response] };
|
||||
})
|
||||
.addNode('tools', async (state) => {
|
||||
const last = state.messages.at(-1);
|
||||
const results: Record<string, string> = {};
|
||||
for (const call of last.tool_calls ?? []) {
|
||||
results[call.id] = await executeTool(call.name, call.args);
|
||||
}
|
||||
return {
|
||||
messages: [...state.messages, ...Object.entries(results).map(([id, content]) => ({ role: 'tool', tool_call_id: id, content }))],
|
||||
toolResults: results,
|
||||
};
|
||||
})
|
||||
.addEdge('__start__', 'agent')
|
||||
.addConditionalEdges('agent', (state) => {
|
||||
const last = state.messages.at(-1);
|
||||
return last.tool_calls?.length > 0 ? 'tools' : END;
|
||||
})
|
||||
.addEdge('tools', 'agent')
|
||||
.compile({ checkpointer: new MemorySaver() });
|
||||
|
||||
// 실행 (streaming)
|
||||
const stream = await graph.stream(
|
||||
{ messages: [{ role: 'user', content: '...' }] },
|
||||
{ configurable: { thread_id: 'session-1' } }
|
||||
);
|
||||
|
||||
for await (const chunk of stream) {
|
||||
console.log(chunk);
|
||||
}
|
||||
```
|
||||
|
||||
### Mastra (TS modern)
|
||||
```ts
|
||||
import { Mastra } from '@mastra/core';
|
||||
|
||||
const mastra = new Mastra({
|
||||
agents: {
|
||||
weatherAgent: new Agent({
|
||||
name: 'Weather',
|
||||
instructions: 'Help users with weather questions.',
|
||||
model: openai('gpt-4o'),
|
||||
tools: { getWeather: weatherTool },
|
||||
}),
|
||||
},
|
||||
workflows: {
|
||||
customerSupport: workflow,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await mastra.agents.weatherAgent.generate('Tokyo weather?');
|
||||
```
|
||||
|
||||
→ TS-first, modern, evals + observability built-in.
|
||||
|
||||
### CrewAI (multi-agent, Python)
|
||||
```python
|
||||
from crewai import Agent, Task, Crew
|
||||
|
||||
researcher = Agent(
|
||||
role='Senior Researcher',
|
||||
goal='Discover latest AI trends',
|
||||
backstory='You are an expert AI researcher...',
|
||||
tools=[search_tool],
|
||||
)
|
||||
|
||||
writer = Agent(
|
||||
role='Tech Writer',
|
||||
goal='Write engaging articles',
|
||||
)
|
||||
|
||||
task1 = Task(description='Research 2026 AI trends', agent=researcher)
|
||||
task2 = Task(description='Write article based on research', agent=writer)
|
||||
|
||||
crew = Crew(agents=[researcher, writer], tasks=[task1, task2])
|
||||
result = crew.kickoff()
|
||||
```
|
||||
|
||||
→ Role-based multi-agent. 빠른 시작 but 정밀 제어 어려움.
|
||||
|
||||
### Vercel AI SDK (modern, simple)
|
||||
```ts
|
||||
import { generateText, tool } from 'ai';
|
||||
import { openai } from '@ai-sdk/openai';
|
||||
import { z } from 'zod';
|
||||
|
||||
const result = await generateText({
|
||||
model: openai('gpt-4o'),
|
||||
tools: {
|
||||
getWeather: tool({
|
||||
description: 'Get weather',
|
||||
parameters: z.object({ city: z.string() }),
|
||||
execute: async ({ city }) => fetchWeather(city),
|
||||
}),
|
||||
},
|
||||
maxSteps: 5, // tool loop
|
||||
prompt: 'Tokyo weather?',
|
||||
});
|
||||
|
||||
console.log(result.text, result.toolCalls);
|
||||
```
|
||||
|
||||
→ 단순 use case 강력.
|
||||
|
||||
### State 영속 (LangGraph)
|
||||
```ts
|
||||
import { PostgresSaver } from '@langchain/langgraph-checkpoint-postgres';
|
||||
|
||||
const checkpointer = PostgresSaver.fromConnString('postgresql://...');
|
||||
const graph = ...compile({ checkpointer });
|
||||
|
||||
// 같은 thread_id = 이어서
|
||||
await graph.invoke(input, { configurable: { thread_id: userId } });
|
||||
```
|
||||
|
||||
### Human-in-the-loop
|
||||
```ts
|
||||
const graph = new StateGraph(State)
|
||||
.addNode('plan', planNode)
|
||||
.addNode('confirm', confirmNode) // 사용자 승인 대기
|
||||
.addNode('execute', executeNode)
|
||||
.addEdge('plan', 'confirm')
|
||||
.addConditionalEdges('confirm', (state) =>
|
||||
state.approved ? 'execute' : END
|
||||
)
|
||||
.compile({
|
||||
interrupt_before: ['execute'], // 항상 멈춤
|
||||
});
|
||||
|
||||
// 1. Plan 까지 실행
|
||||
const state = await graph.invoke(input, config);
|
||||
|
||||
// 2. UI 가 사용자 confirm
|
||||
if (await askUser(state.plan)) {
|
||||
// 3. 이어서 실행
|
||||
await graph.invoke(null, { ...config, recursionLimit: 10 });
|
||||
}
|
||||
```
|
||||
|
||||
### Streaming + tools
|
||||
```ts
|
||||
const stream = await graph.streamEvents(input, {
|
||||
...config,
|
||||
version: 'v2',
|
||||
});
|
||||
|
||||
for await (const event of stream) {
|
||||
if (event.event === 'on_chat_model_stream') {
|
||||
process.stdout.write(event.data.chunk.content);
|
||||
}
|
||||
if (event.event === 'on_tool_start') {
|
||||
console.log('\nTool:', event.name);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 자체 implementation (가벼운)
|
||||
```ts
|
||||
class Agent {
|
||||
private messages: Message[] = [];
|
||||
private maxIters = 10;
|
||||
|
||||
constructor(
|
||||
private llm: LLM,
|
||||
private tools: Tool[],
|
||||
private systemPrompt: string,
|
||||
) {}
|
||||
|
||||
async run(userMsg: string): Promise<string> {
|
||||
this.messages.push({ role: 'user', content: userMsg });
|
||||
|
||||
for (let i = 0; i < this.maxIters; i++) {
|
||||
const r = await this.llm.chat({
|
||||
system: this.systemPrompt,
|
||||
messages: this.messages,
|
||||
tools: this.tools,
|
||||
});
|
||||
|
||||
this.messages.push({ role: 'assistant', content: r.content });
|
||||
|
||||
if (r.stopReason === 'end_turn') return r.text;
|
||||
|
||||
if (r.toolCalls) {
|
||||
const results = await Promise.all(
|
||||
r.toolCalls.map(call => this.executeTool(call))
|
||||
);
|
||||
this.messages.push(...results.map(toToolResult));
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('max iters');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
→ 위 [[AI_Function_Calling_Deep]] 가 baseline.
|
||||
|
||||
### Observability
|
||||
```ts
|
||||
// LangSmith / Langfuse / Helicone
|
||||
import { LangSmithTracer } from 'langsmith';
|
||||
|
||||
const tracer = new LangSmithTracer({
|
||||
projectName: 'my-agent',
|
||||
});
|
||||
|
||||
await graph.invoke(input, {
|
||||
callbacks: [tracer],
|
||||
});
|
||||
```
|
||||
|
||||
→ 매 LLM call + tool call 추적.
|
||||
|
||||
### Memory systems
|
||||
```ts
|
||||
// Short-term: conversation messages
|
||||
// Long-term: vector DB
|
||||
import { MemoryClient } from '@mem0/sdk';
|
||||
|
||||
const memory = new MemoryClient();
|
||||
|
||||
// Save
|
||||
await memory.add(userId, 'User prefers dark mode and minimal UI');
|
||||
|
||||
// Retrieve relevant
|
||||
const memories = await memory.search(userId, currentQuery);
|
||||
|
||||
// Inject to system prompt
|
||||
const system = `${baseSystem}\n\nRelevant context:\n${memories.join('\n')}`;
|
||||
```
|
||||
|
||||
### Eval (위 LLM Eval 문서)
|
||||
```ts
|
||||
import { evalDataset, exactMatch, llmJudge } from 'mastra/evals';
|
||||
|
||||
await evalDataset({
|
||||
agent: weatherAgent,
|
||||
cases: [
|
||||
{ input: 'Tokyo weather?', expected: { contains: 'Tokyo' } },
|
||||
],
|
||||
metrics: [exactMatch, llmJudge('helpful')],
|
||||
});
|
||||
```
|
||||
|
||||
### 비교
|
||||
```
|
||||
LangGraph:
|
||||
+ 가장 강력 / production-ready
|
||||
+ State machine 명시
|
||||
- Python 우월 (TS 제한)
|
||||
|
||||
Mastra:
|
||||
+ Modern TS-first
|
||||
+ Eval / observability built-in
|
||||
- 새로움 (검증 적음)
|
||||
|
||||
CrewAI:
|
||||
+ Multi-agent simple
|
||||
- 정밀 제어 어려움
|
||||
|
||||
Vercel AI SDK:
|
||||
+ 단순 / TS 친화
|
||||
- Multi-step 제한적
|
||||
|
||||
자체:
|
||||
+ 정확 제어
|
||||
- 모든 거 직접
|
||||
```
|
||||
|
||||
## 🤔 의사결정 기준
|
||||
| 상황 | 추천 |
|
||||
|---|---|
|
||||
| 단순 1-2 step | Vercel AI SDK |
|
||||
| Multi-step / state | LangGraph (Python) / Mastra (TS) |
|
||||
| Multi-agent | CrewAI / AutoGen |
|
||||
| Production / 큰 규모 | LangGraph + LangSmith |
|
||||
| Quick prototype | Vercel AI SDK |
|
||||
| 완전 제어 | 자체 |
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Framework 선택 전 use case 명시 X**: overkill / 부족.
|
||||
- **State persistence 없음 + long task**: crash 시 잃음.
|
||||
- **Max iter 없음**: 무한 / 비용 폭발.
|
||||
- **HITL 없음 + dangerous tool**: 실수 책임.
|
||||
- **Multi-agent 가 single agent 대체**: 단일 가 충분 자주.
|
||||
- **Memory 모든 거 inject**: context 폭발. retrieval.
|
||||
- **Eval 없음**: 향상 측정 X.
|
||||
|
||||
## 🤖 LLM 활용 힌트
|
||||
- 단순 = Vercel AI SDK.
|
||||
- Production state = LangGraph / Mastra.
|
||||
- Multi-agent overkill 자주.
|
||||
- HITL + state persistence 필수.
|
||||
|
||||
## 🔗 관련 문서
|
||||
- [[AI_Agentic_Patterns]]
|
||||
- [[AI_Function_Calling_Deep]]
|
||||
- [[AI_LLM_Eval_Patterns]]
|
||||
Reference in New Issue
Block a user