feat(ai-orch): 모든 그룹 실행에 코디네이터 무조건 앞단 내장 + 가상 코디네이터 자동 생성
Build and Push Images / build-and-push (push) Has been cancelled

사용자 요구
- "에이전트 오케스트레이션 앞단에는 무조건 코디네이터 내장"
- "코디네이터가 질문 분석 → 각 에이전트에 명령 전달 → 결과 정리 → 답변" 흐름 강제

Engine 변경 (multiAgentExecutionEngine.ts)
- execute() switch 단순화: parallel/sequential/mixed/coordinator 등 어느 모드든 무조건
  executeCoordinator() 거치도록. 'raw_*' 접두사가 명시된 경우만 기존 분기로 우회 (legacy)
- executeCoordinator():
  · 멤버 중 config.is_coordinator=true 가 있으면 그 멤버를 coordinator 로 사용
  · 없으면 가상(virtual) 코디네이터 자동 생성 — ai_llm_providers 에서 priority 가장 높은
    활성 LLM 으로 model 자동 선택. 모든 실 멤버가 worker 로 동작
  · 가상/실 코디네이터 모두 같은 3-라운드 흐름 (분해 → 병렬 위임 → 합성)
- ai_llm_providers.priority 정렬을 활용해 운영자가 어떤 모델을 자동 코디네이터로 쓸지 제어 가능

DB
- ai_llm_providers.id=6 (ollama/Qwen3.6-35B-A3B) priority 10 → 0 으로 상향
  → 가상 코디네이터가 로컬 Qwen 자동 선택. (Anthropic 키가 죽어있어도 동작)

검증 결과 (PLM 그룹, 멤버에 코디 지정 0)
- 자동 가상 코디네이터(Qwen3.6) 생성 → 사용자 요청 분해 → 두 worker 에 short task 분배
- 환율전문가: rates.JPY=0.10809 정확 인용
- 기상전문가: HTML 응답 → 정직 거부
- 최종 코디네이터 합성: 두 결과를 마크다운 단일 답변으로 정리

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
chpark
2026-04-28 21:00:03 +09:00
parent e2845508fa
commit 080cfe9585
@@ -74,14 +74,19 @@ export class MultiAgentExecutionEngine {
? `${userMessage}\n\n${historyContext}`
: userMessage;
if (executionMode === "parallel") {
// 모든 그룹 실행은 무조건 코디네이터를 앞단에 거친다 (Orchestrator-Worker 강제)
// - 명시적 coordinator 지정이 없으면 가상 코디네이터 자동 생성
// - executionMode 는 라우팅·합성 안내용으로만 활용 (필요 시 system prompt 힌트로 inject)
// - 'raw' 모드 (legacy 호환) 는 예외적으로 기존 분기 유지 — 마이그레이션 끝나면 제거
if (executionMode === "raw_parallel") {
allResults = await this.executeParallel(group.members, enrichedMessage, "");
} else if (executionMode === "sequential") {
} else if (executionMode === "raw_sequential") {
allResults = await this.executeSequential(group.members, enrichedMessage);
} else if (executionMode === "coordinator") {
allResults = await this.executeCoordinator(group.members, enrichedMessage, userMessage);
} else {
} else if (executionMode === "raw_mixed") {
allResults = await this.executeMixed(group.members, enrichedMessage);
} else {
// default: parallel/sequential/mixed/coordinator 등 → 모두 코디네이터 거침
allResults = await this.executeCoordinator(group.members, enrichedMessage, userMessage);
}
// 최종 요약 생성
@@ -190,14 +195,15 @@ export class MultiAgentExecutionEngine {
}
/**
* Orchestrator-Worker 패턴
* Orchestrator-Worker 패턴 (무조건 앞단 코디네이터 내장)
* Round 1: coordinator 가 사용자 요청 분해 → JSON 라우팅 결정
* Round 2: 결정된 sub-agent 들 병렬 실행 (각자 자신의 task 만)
* Round 3: coordinator 가 결과 합성 → 사용자 친화적 최종 답변
*
* Coordinator 식별 우선순위:
* - member.config.is_coordinator === true
* - 없으면 execution_order 가 가장 멤버
* Coordinator 정책:
* 1) 멤버 중 config.is_coordinator === true 가 있으면 그 멤버를 coordinator 로 사용 (workers 에서는 제외)
* 2) 없으면 시스템이 가상(virtual) 코디네이터 자동 생성 — ai_llm_providers 에서 priority 가장 활성 LLM 자동 선택
* → 모든 실 멤버는 worker 로만 동작
*/
private static async executeCoordinator(
members: GroupMember[],
@@ -206,14 +212,51 @@ export class MultiAgentExecutionEngine {
): Promise<ExecutionResult[]> {
if (!members || members.length === 0) return [];
// 1) coordinator 식별
const coord =
members.find((m: any) => m.config?.is_coordinator === true) ||
members.slice().sort((a: any, b: any) => (a.execution_order ?? 999) - (b.execution_order ?? 999))[0];
const workers = members.filter((m: any) => m !== coord);
// 1) coordinator 식별 — 명시 지정 우선, 없으면 가상 코디네이터 자동 생성
const explicitCoord = members.find((m: any) => m.config?.is_coordinator === true);
let coord: GroupMember;
let workers: GroupMember[];
let isVirtual = false;
if (explicitCoord) {
coord = explicitCoord;
workers = members.filter((m: any) => m !== coord);
} else {
// 가상 코디네이터: 활성 LLM 1개 자동 선택
const provRows = await query<any>(
"SELECT model_name FROM ai_llm_providers WHERE is_active = true ORDER BY priority ASC LIMIT 1"
);
const coordModel = provRows[0]?.model_name || "claude-sonnet-4-20250514";
coord = {
id: 0,
group_id: members[0].group_id,
agent_id: 0,
role_name: "코디네이터",
agent_name: "자동 코디네이터",
agent_model: coordModel,
connectors: [],
execution_order: 0,
config: { is_coordinator: true, virtual: true },
} as any;
workers = members; // 실 멤버 전부가 worker
isVirtual = true;
logger.info(`[Coordinator] 가상 코디네이터 자동 생성 (model=${coordModel}, workers=${workers.length})`);
}
if (workers.length === 0) {
// worker 없으면 coordinator 단독 실행
if (isVirtual) {
return [{
memberId: 0,
roleName: "코디네이터(가상)",
agentName: "자동 코디네이터",
modelName: coord.agent_model || "",
executionOrder: 0,
response: "그룹에 worker 에이전트가 없어 처리할 수 없습니다.",
tokensUsed: 0,
durationMs: 0,
}];
}
return [await this.executeSingleAgent(coord, userMessage, "")];
}