Akka.NET에 LLM Agent 객체 만들기 — Akka.io Agent SDK를 액터 모델로 이식하기
수집일: 2026-05-16
대상 독자: .NET + 액터 모델 + LLM 통합에 관심 있는 IT 개발자
발행 매체: Notion AI-DOC 하위 (Type B*)
연관 메모리 (새창 열기):
한 줄 요약
Akka.io(JVM)의
Agent 컴포넌트는 LLM 호출 + 세션 메모리 + 도구 호출을 하나로 묶은 1급 추상화다. Akka.NET은 이 추상화를 아직 공식 제공하지 않지만, ReceiveActor + Behaviors 전이 + 자식 액터 감독 + 외부 LLM 클라이언트 조합으로 같은 모양을 만들 수 있다 — 그 설계와 동작 샘플을 정리한다.배경 — 왜 Akka.NET에는 Agent가 없는가
측면 | Akka.io (JVM/Java SDK) | |
:--- | :--- | :--- |
액터 모델 | ✅ 1973 Hewitt 이론 기반 동일 | ✅ Akka.io를 .NET으로 포팅, 동일 |
Agent 컴포넌트 | ✅ 1급 시민 (Akka SDK 2024+) | ❌ 없음 — 직접 조립 필요 |
Workflow / Entity / View | ✅ 어노테이션 + SDK 런타임 | ❌ 라이브러리 수준 (Persistence·Streams 등 별도) |
메모리 관리 | Event Sourced Entity 내장 | Akka.Persistence (등가) |
Tool calling 통합 | LLM 응답 → 자동 dispatch | 직접 구현 (Newtonsoft + reflection) |
Akka.io의 Agent SDK는 Akka Agents 플랫폼 메모에서 다뤘듯 Context Gathering → Reasoning → Action Taking → Progress Evaluation 4단계 라이프사이클을 하나의 추상 클래스로 감싼다. .NET 측에서는 이 모양을 액터 + Behavior 전이로 직접 구현해야 한다.
1. Akka.io Agent 한 줄 분석
공식 Hello World 튜토리얼 기준 최소 형태:
@Component(id = "hello-world-agent") public class HelloWorldAgent extends Agent { private static final String SYSTEM_MESSAGE = "You are a cheerful AI..."; public Effect<String> greet(String userGreeting) { return effects() .systemMessage(SYSTEM_MESSAGE) .userMessage(userGreeting) .thenReply(); } }
effects() 빌더가 제공하는 메서드:메서드 | 역할 |
:--- | :--- |
model(ModelProvider) | 사용할 LLM 지정 (OpenAI/Anthropic/MCP/로컬) |
systemMessage(String) | 시스템 프롬프트 |
userMessage(String) | 사용자 입력 |
tools(...) | Java 함수를 LLM에 도구로 노출 |
memoryProvider(...) | 세션 메모리 소스 |
thenReply() | 응답 반환 |
핵심은 세션이 메모리 키 라는 점.
componentClient.forAgent().inSession(userId).method(HelloWorldAgent::greet).invoke(text) 식으로 호출하면 같은 userId 내 이전 대화가 자동으로 system prompt에 prepend된다.2. .NET 매핑 표 — 각 Akka.io 자산의 등가물
Akka.NET / .NET 생태계 | 비고 | |
:--- | :--- | :--- |
Agent 컴포넌트 | LlmAgentActor : ReceiveActor (사용자 정의) | 본 글에서 설계 |
Effect<T> 빌더 | AgentEffectBuilder<T> (POCO) | fluent API |
@Component(id="...") | Props.Create<LlmAgentActor>(...) • ActorRefs 명명 | 명시적 child path |
componentClient.forAgent().inSession(id) | agents.GetOrCreate(sessionId) (라우터 액터) | 또는 Akka.Cluster.Sharding |
세션 메모리 (자동) | SessionMemoryActor : PersistentActor • Snapshot | Akka.Persistence |
tools(...) | IAgentTool 인터페이스 + [AgentFunction] attribute | 리플렉션으로 JSON Schema 생성 |
model(ModelProvider) | ILlmClient (OpenAI/Anthropic/Ollama SDK 래퍼) | DI로 주입 |
Effect<String> 반환 | Receive<AgentRequest> → Sender.Tell(AgentResponse) | Ask 패턴 또는 단방향 |
Workflow 컴포넌트 | WorkflowActor : ReceiveActor (FSM/Behavior 기반) | Become(...) 으로 상태 전이 |
Event Sourced Entity | PersistentActor | 기능 동일 |
View (CQRS read side) | Akka.Streams • 프로젝션 액터 | 또는 Akka.Persistence.Query |
memorizer-v1이 이미 이 매핑을 실전 적용한 사례다 — Memorizer 액터 협업 챕터 참조.3. AkkaAgent 추상화 설계
3-1. 메시지 프로토콜
public abstract record AgentMessage; public sealed record AgentRequest( string SessionId, string UserMessage, Dictionary<string, object>? Context = null ) : AgentMessage; public sealed record AgentResponse( string SessionId, string Reply, IReadOnlyList<ToolInvocation> ToolCalls, int PromptTokens, int CompletionTokens ) : AgentMessage; public sealed record AgentError( string SessionId, string Reason, Exception? Cause = null ) : AgentMessage; public sealed record ToolInvocation(string Name, string ArgsJson, string ResultJson);
3-2. 추상 액터 — 라이프사이클 4단계를 Behavior 전이로
public abstract class LlmAgentActor : ReceiveActor, IWithUnboundedStash { public IStash Stash { get; set; } = null!; private readonly ILlmClient _llm; private readonly IActorRef _memory; // SessionMemoryActor (자식) private readonly IReadOnlyList<IAgentTool> _tools; private readonly string _agentId; protected LlmAgentActor( string agentId, ILlmClient llm, IReadOnlyList<IAgentTool> tools) { _agentId = agentId; _llm = llm; _tools = tools; _memory = Context.ActorOf(SessionMemoryActor.Props(), "memory"); Become(Idle); } // ---- Behavior 1: Idle (= Akka.io "수신 대기") ---- private void Idle() { Receive<AgentRequest>(req => { Stash.Stash(); _memory.Tell(new LoadSessionMessages(req.SessionId), Self); Become(() => GatheringContext(req, Sender)); }); } // ---- Behavior 2: GatheringContext (= Context Gathering) ---- private void GatheringContext(AgentRequest req, IActorRef caller) => Receive<SessionMessages>(loaded => { Stash.UnstashAll(); var prompt = BuildPrompt(req, loaded.Messages); Become(() => Reasoning(req, caller, prompt)); Self.Tell(prompt); }); // ---- Behavior 3: Reasoning (= LLM 호출) ---- private void Reasoning(AgentRequest req, IActorRef caller, AgentPrompt prompt) => Receive<AgentPrompt>(async _ => { try { var raw = await _llm.CompleteAsync(prompt, _tools.Select(t => t.Schema)); Become(() => Acting(req, caller, raw)); Self.Tell(raw); } catch (Exception ex) { caller.Tell(new AgentError(req.SessionId, ex.Message, ex)); Become(Idle); } }); // ---- Behavior 4: Acting (= 도구 호출) ---- private void Acting(AgentRequest req, IActorRef caller, LlmCompletion raw) => Receive<LlmCompletion>(async _ => { if (raw.ToolCalls.Count == 0) { _memory.Tell(new AppendMessages(req.SessionId, raw.AssistantMessage)); caller.Tell(new AgentResponse(req.SessionId, raw.Content, Array.Empty<ToolInvocation>(), raw.PromptTokens, raw.CompletionTokens)); Become(Idle); return; } var invocations = new List<ToolInvocation>(); foreach (var call in raw.ToolCalls) { var tool = _tools.First(t => t.Name == call.Name); var result = await tool.InvokeAsync(call.ArgsJson); invocations.Add(new ToolInvocation(call.Name, call.ArgsJson, result)); } // tool 결과를 prompt 에 다시 주입 → Reasoning 으로 복귀 var nextPrompt = AppendToolResults(BuildPromptFromCompletion(raw), invocations); Become(() => Reasoning(req, caller, nextPrompt)); Self.Tell(nextPrompt); }); protected abstract AgentPrompt BuildPrompt(AgentRequest req, IReadOnlyList<ChatMessage> history); }
3-3. 도구 추상화
public interface IAgentTool { string Name { get; } string Description { get; } JsonSchema Schema { get; } Task<string> InvokeAsync(string argsJson); } [AttributeUsage(AttributeTargets.Method)] public sealed class AgentFunctionAttribute : Attribute { public string? Description { get; init; } } // 사용 예 — 리플렉션으로 자동 IAgentTool 생성 public sealed class WeatherTool { [AgentFunction(Description = "Get current weather for a city")] public Task<WeatherResult> GetWeather(string city) => /* HTTP 호출 */; }
3-4. 세션 메모리 (PersistentActor)
public sealed class SessionMemoryActor : ReceivePersistentActor { public override string PersistenceId => $"session-memory-{_sessionId}"; private readonly string _sessionId; private readonly LinkedList<ChatMessage> _history = new(); private const int MaxHistory = 20; public SessionMemoryActor(string sessionId) { _sessionId = sessionId; Recover<MessageAppended>(evt => Apply(evt)); Recover<SnapshotOffer>(offer => { if (offer.Snapshot is List<ChatMessage> snap) foreach (var m in snap) _history.AddLast(m); }); Command<LoadSessionMessages>(_ => Sender.Tell(new SessionMessages(_history.ToList()))); Command<AppendMessages>(cmd => Persist(new MessageAppended(cmd.Message), evt => { Apply(evt); if (_history.Count % 50 == 0) SaveSnapshot(_history.ToList()); })); } private void Apply(MessageAppended evt) { _history.AddLast(evt.Message); while (_history.Count > MaxHistory) _history.RemoveFirst(); } }
4. 동작 샘플 — HelloAgent (Q&A)
4-1. 구체 액터
public sealed class HelloAgentActor : LlmAgentActor { private const string SystemPrompt = "You are a cheerful AI assistant. Greet users in a new language each turn."; public HelloAgentActor(ILlmClient llm) : base("hello-agent", llm, Array.Empty<IAgentTool>()) { } protected override AgentPrompt BuildPrompt( AgentRequest req, IReadOnlyList<ChatMessage> history) { var msgs = new List<ChatMessage> { ChatMessage.System(SystemPrompt) }; msgs.AddRange(history); msgs.Add(ChatMessage.User(req.UserMessage)); return new AgentPrompt(msgs); } public static Props Props(ILlmClient llm) => Akka.Actor.Props.Create(() => new HelloAgentActor(llm)); }
4-2. 호출 — 세션 단위 라우팅
public sealed class AgentRouter : ReceiveActor { private readonly Func<string, Props> _propsFactory; private readonly Dictionary<string, IActorRef> _bySession = new(); public AgentRouter(Func<string, Props> propsFactory) { _propsFactory = propsFactory; Receive<AgentRequest>(req => { if (!_bySession.TryGetValue(req.SessionId, out var agent)) { agent = Context.ActorOf(_propsFactory(req.SessionId), $"agent-{req.SessionId}"); _bySession[req.SessionId] = agent; } agent.Forward(req); }); } } // 실행 var system = ActorSystem.Create("agent-host"); var llm = new OpenAiClient(apiKey: ...); var router = system.ActorOf( Props.Create(() => new AgentRouter(_ => HelloAgentActor.Props(llm))), "agents"); var reply = await router.Ask<AgentResponse>( new AgentRequest("alice", "Hi, I'm Alice"), TimeSpan.FromSeconds(30)); // reply.Reply: "Hello, Alice! (English)"
4-3. 두 번째 호출 — 메모리 자동 적용
reply = await router.Ask<AgentResponse>( new AgentRequest("alice", "I live in New York"), TimeSpan.FromSeconds(30)); // reply.Reply: "Bonjour, New York! Ah, la ville qui ne dort jamais. (French)"
같은
SessionId="alice" 라우팅 → 같은 자식 액터 → 같은 SessionMemoryActor → 이전 대화 자동 prepend.5. 동작 샘플 — WeatherAgent (도구 호출)
public sealed class WeatherAgentActor : LlmAgentActor { private const string SystemPrompt = "You are an activity planner. Use the weather tool before recommending outdoor activities."; public WeatherAgentActor(ILlmClient llm, WeatherTool weather) : base("weather-agent", llm, new IAgentTool[] { AgentToolFactory.Wrap(weather) }) { } protected override AgentPrompt BuildPrompt( AgentRequest req, IReadOnlyList<ChatMessage> history) => /* 동일 */; }
LLM이 응답에
tool_calls: [{name: "GetWeather", args: {city: "Madrid"}}]을 포함하면 → Acting Behavior 가 WeatherTool.GetWeather를 호출 → 결과를 다음 prompt 에 추가 → Reasoning 으로 복귀 → 최종 답변 생성. 사용자에게는 한 번의 AgentResponse만 도달하지만 내부적으로 LLM 왕복 2회 발생.6. 전체 흐름 다이어그램
sequenceDiagram autonumber participant U as User participant R as AgentRouter participant A as LlmAgentActor<br/>(WeatherAgent) participant M as SessionMemoryActor participant LLM as ILlmClient participant T as WeatherTool U->>R: AgentRequest("alice", "Madrid 추천?") R->>A: Forward (세션별 라우팅) Note over A: Behavior=Idle A->>M: LoadSessionMessages M-->>A: SessionMessages(history) Note over A: Behavior=GatheringContext → Reasoning A->>LLM: complete(prompt, tools) LLM-->>A: tool_call: GetWeather("Madrid") Note over A: Behavior=Acting A->>T: GetWeather("Madrid") T-->>A: "비, 18°C" Note over A: Behavior=Reasoning (재진입) A->>LLM: complete(prompt + tool_result) LLM-->>A: "비가 와요. Prado 미술관 추천." A->>M: AppendMessages A-->>R: AgentResponse R-->>U: AgentResponse Note over A: Behavior=Idle
7. 운영 고려사항
항목 | Akka.io 자동 | Akka.NET 직접 처리 |
:--- | :--- | :--- |
세션 메모리 압축 (오래된 메시지) | SDK | SessionMemoryActor에서 MaxHistory • 요약 로직 추가 |
Token 초과 시 truncate | SDK | BuildPrompt에서 tokenizer로 사전 계산 |
도구 호출 timeout/retry | SDK | AskTimeout • Polly + Supervisor OneForOneStrategy |
분산 라우팅 (세션 → 노드) | Cluster Sharding 자동 | Akka.Cluster.Sharding으로 명시 적용 |
관찰 가능성 | OpenTelemetry 통합 | Akka.Logger.Serilog • Phobos 또는 Petabridge.Cmd |
LLM 응답 스트리밍 | StreamEffect | Akka.Streams Source.Single(...).MapAsync(...) 또는 SSE |
장애 격리 | Supervisor 자동 | SupervisorStrategy 명시 — Resume / Restart / Escalate |
8. 안티패턴
안티패턴 | 결과 | 올바른 방법 |
:--- | :--- | :--- |
세션마다 새 ActorSystem | 메모리/스레드 폭증 | ActorSystem 1개 + 세션별 자식 액터 |
LLM 호출을 동기 차단 ( Task.Wait) | 메일박스 정체 → 모든 메시지 지연 | 반드시 async Receive + PipeTo 또는 await |
Tool 결과를 Sender에 직접 응답 | Acting → Reasoning 복귀 누락, 단일 LLM 라운드 | Self에 메시지로 재진입 (Become 유지) |
모든 액터를 PersistentActor로 | snapshot 빈도/디스크 IO 증가 | 세션 메모리만 영속화, 라우터/Agent는 일반 액터 |
Tool 함수를 Agent 액터 안에 인라인 | Agent 책임 비대화 + 테스트 어려움 | IAgentTool 별도 DI, Agent는 조립만 |
9. Akka.io 공식 샘플 → Akka.NET 포팅 매트릭스
Akka.io 샘플 | 핵심 컴포넌트 | Akka.NET 포팅 매핑 | 난이도 |
:--- | :--- | :--- | :--- |
Agent • 세션 메모리 | HelloAgentActor (§4) | ⭐ | |
Agent × 3 + Workflow • Entity | §9-1 TripPlannerWorkflowActor • 3개 Agent | ⭐⭐⭐ | |
RAG + Streaming • Workflow | §9-2 RagChatAgentActor • Indexer Workflow | ⭐⭐⭐ | |
Agent • 2 Tools + Timers • EventSourcedEntity | §9-3 CustomerServiceAgentActor | ⭐⭐⭐ | |
Agent Memory + Workflow • Entity | Multi-agent 변형 — 동일 패턴 | ⭐⭐ | |
Summarization + Workflow • Entity | RAG + 분류 결합 — §9-2 변형 | ⭐⭐⭐ | |
IoT Trend + Anomaly + Agent | Akka.Streams + Agent 결합 | ⭐⭐⭐⭐ | |
Entity • Timed Action • Anthropic | Cron 스케줄러 + Agent | ⭐⭐ | |
Tools + Spring AI | §5 WeatherAgent 확장 | ⭐⭐ |
9-1. Multi-Agent Planner (Workflow 조율)
Akka 공식 Step 4의 Java 코드:
@ComponentId("plan-trip") public class PlanTripWorkflow extends Workflow<State> { @StepName("get-weather") private StepEffect getWeatherStep() { var weather = componentClient .forAgent().method(WeatherAgent::query) .invoke(currentState().query()); return stepEffects() .updateState(currentState().withWeather(weather)) .thenTransitionTo(PlanTripWorkflow::suggestActivityStep); } @StepName("suggest-activity") private StepEffect suggestActivityStep() { var context = "Weather: " + currentState().weather() + "\nPreferences: " + componentClient .forEntity(currentState().userId()) .method(PreferencesEntity::getPreferences) .invoke(); var suggestion = componentClient .forAgent().method(ActivityAgent::query).invoke(context); return stepEffects() .updateState(currentState().withSuggestion(suggestion)) .thenPause(); } }
Akka.NET 포팅 —
Workflow 컴포넌트는 없으므로 FSM 액터 + Akka.Persistence 조합:// 1) 메시지·상태 public sealed record PlanTripStart(string UserId, string Query); public sealed record PlanTripState( string UserId, string Query, string Weather = "", string Suggestion = ""); public sealed record WeatherReady(string Weather); public sealed record SuggestionReady(string Suggestion); // 2) Workflow 액터 — Become으로 Step 전이 + PersistentActor로 체크포인트 public sealed class TripPlannerWorkflowActor : ReceivePersistentActor { public override string PersistenceId => $"trip-planner-{_userId}"; private readonly string _userId; private readonly IActorRef _weatherAgent; private readonly IActorRef _activityAgent; private readonly IActorRef _prefsEntity; private PlanTripState _state = null!; public TripPlannerWorkflowActor( string userId, IActorRef weather, IActorRef activity, IActorRef prefs) { _userId = userId; _weatherAgent = weather; _activityAgent = activity; _prefsEntity = prefs; Become(Idle); // Persistence recovery (생략) } private void Idle() { Command<PlanTripStart>(cmd => { _state = new PlanTripState(cmd.UserId, cmd.Query); // Step 1: Get Weather _weatherAgent.Tell(new AgentRequest(cmd.UserId, cmd.Query)); Become(GettingWeather); }); } private void GettingWeather() => Command<AgentResponse>(resp => { Persist(new WeatherReady(resp.Reply), evt => { _state = _state with { Weather = evt.Weather }; // Step 2: Get Preferences from Entity _prefsEntity.Tell(new GetPreferences(_userId), Self); Become(GettingPreferences); }); }); private string _preferences = ""; private void GettingPreferences() => Command<PreferencesResponse>(resp => { _preferences = resp.Value; var ctx = $"Weather: {_state.Weather}\nPreferences: {_preferences}"; _activityAgent.Tell(new AgentRequest(_userId, ctx)); Become(GettingSuggestion); }); private void GettingSuggestion() => Command<AgentResponse>(resp => { Persist(new SuggestionReady(resp.Reply), evt => { _state = _state with { Suggestion = evt.Suggestion }; Context.Parent.Tell(_state); Become(Idle); }); }); }
핵심 차이:
componentClient.forAgent().method(...).invoke(...)=agentActorRef.Tell(...)(비동기,Receive<AgentResponse>로 결과 받음)
transitionTo(NextStep)=Become(NextStep)
- 자동 체크포인트 =
PersistentActor.Persist(evt, handler)
thenPause()=Become(WaitingForExternalInput)(예: 사용자 승인 대기)
9-2. RAG Chat Agent (스트리밍 + 벡터 검색)
Akka 공식 RAG 튜토리얼 Java 코드:
@Component(id = "ask-akka-agent") public class AskAkkaAgent extends Agent { public Effect<StreamingResponse<String>> chat(String question) { return effects() .systemMessage("You are a helpful Akka assistant.") .userMessage(question) .thenReplyStreaming(); } }
public sealed record RagQueryRequest(string SessionId, string Question); public sealed record RagChunk(string SessionId, string Token); public sealed record RagDone(string SessionId, int TokenCount); public sealed class RagChatAgentActor : LlmAgentActor { private readonly IActorRef _ragHelper; // 벡터 검색 액터 private readonly IStreamingLlmClient _llmStream; protected override AgentPrompt BuildPrompt( AgentRequest req, IReadOnlyList<ChatMessage> history) { // 1) 질문 임베딩 + 유사 문서 5건 검색 (Ask 패턴) var docs = _ragHelper.Ask<TopKDocs>( new SimilaritySearch(req.UserMessage, K: 5), TimeSpan.FromSeconds(3)).Result; // 2) 검색된 문서를 컨텍스트로 추가 var ctx = string.Join("\n\n", docs.Items.Select(d => $"[doc:{d.Id}] {d.Text}")); var msgs = new List<ChatMessage> { ChatMessage.System("Answer based on the docs below.\n\n" + ctx) }; msgs.AddRange(history); msgs.Add(ChatMessage.User(req.UserMessage)); return new AgentPrompt(msgs); } // 스트리밍 전용 메서드 (Reasoning Behavior 분기) public IAsyncEnumerable<RagChunk> ChatStreaming(RagQueryRequest req) => Source.From(_llmStream.StreamComplete(BuildPrompt( new AgentRequest(req.SessionId, req.Question), Array.Empty<ChatMessage>()))) .Select(token => new RagChunk(req.SessionId, token)) .RunAsAsyncEnumerable(materializer); }
Indexer Workflow도 동일 패턴 —
IndexerWorkflowActor가 디렉터리 스캔 → 청킹 → 임베딩 API → 벡터 DB 저장을 Akka.Streams Source.From(files).MapAsync(8, ...) 로 처리.9-3. Real Estate CS Agent (도구 2개 + Timer + EventSourced)
Akka 공식 Real Estate CS Agent 의 핵심: 2개 도구 + Workflow + Timer + EventSourced Entity.
public sealed class CustomerServiceAgentActor : LlmAgentActor { public CustomerServiceAgentActor( ILlmClient llm, IActorRef emailGateway, // Tool 1: 이메일 발송 IActorRef customerEntity) // Tool 2 + Entity: 고객 정보 저장 : base("cs-agent", llm, new IAgentTool[] { AgentToolFactory.Wrap(new SendEmailTool(emailGateway)), AgentToolFactory.Wrap(new SaveCustomerTool(customerEntity)), }) { } protected override AgentPrompt BuildPrompt(...) => /* "고객 정보가 부족하면 SendEmail로 후속 질문, 충분하면 SaveCustomer 호출" */; } // Timer: 후속 응답 없으면 24시간 후 reminder public sealed class CustomerInquiryWorkflowActor : ReceivePersistentActor { public CustomerInquiryWorkflowActor(IActorRef agent, ITimerScheduler timers) { Command<EmailReceived>(email => { agent.Tell(new AgentRequest(email.Sender, email.Body)); Become(WaitingForFollowUp); timers.StartSingleTimer( "reminder", new SendReminder(email.Sender), TimeSpan.FromHours(24)); }); } }
핵심은 Tool이 단순 함수가 아니라 다른 액터의 ActorRef를 감싼다는 점. LLM이
SendEmail 도구를 호출하면 _emailGateway.Tell(new SendEmail(...)) 가 디스패치되어, 메일 전송도 액터 시스템 안에서 일관된 supervision 트리 안에서 일어난다.9-4. 포팅 시 공통 규칙
:--- | :--- |
@Component(id="x") | Context.ActorOf(Props, "x") (자식 path) |
@StepName("get-weather") | Become(GetWeather) 함수명 |
componentClient.forAgent().method(M::f).invoke(x) | agentRef.Ask<Resp>(req, timeout) 또는 Tell • Receive<Resp> |
componentClient.forEventSourcedEntity(id).method(M::f).invoke() | Cluster.Sharding.Get(system).ShardRegion("...").Tell(env) |
effects().thenReplyStreaming() | Source.From(stream).RunAsAsyncEnumerable(mat) |
@Tool(description="...") | [AgentFunction(Description="...")] • reflection |
Workflow thenPause() | Become(WaitingExternal) • 외부 메시지로 깨움 |
Workflow thenTransitionTo(X::step) | Become(Step) 함수 호출 |
요약: Akka.io의 어노테이션 기반 라이프사이클은 .NET에서 명시적Become전이 +PersistentActor체크포인트로 1:1 매핑된다. 자동성이 약간 줄지만, 상태 머신이 코드에 직접 드러나는 장점이 있다 — 디버깅·운영 시점에 큰 이점.
10. 다음 단계
- 샘플 GitHub 레포 분기: memorizer-v1의
ChatBotActor→ 본 글의 4-Behavior 패턴으로 리팩토링 시도
- Multi-Agent 시너지: Akka Multi-Agent Planner 의 Activity/Weather/Summarizer 3-Agent 협업 패턴을 .NET 라우터 + 자식 액터로 재현
- Akka.Cluster.Sharding 통합: 세션 ID → ShardId 매핑으로 다중 노드 확장
- MCP 도구 연결:
IAgentTool구현체에 MCP 서버 호출을 감싸 LLM에 외부 도구 노출
11. 참고 자료
- Akka.io 공식 Agent 가이드: https://doc.akka.io/java/agents.html
- Memorizer-v1 GitHub: https://github.com/psmon/memorizer-v1
태그
#AkkaNET #ActorModel #LLMAgent #DotNet #AkkaIO #AgenticAI #DistributedSystems #PersistentActor #ToolCalling #2026-05