feat(ai-orch): 모든 그룹 실행에 코디네이터 무조건 앞단 내장 + 가상 코디네이터 자동 생성
Build and Push Images / build-and-push (push) Has been cancelled
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:
@@ -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, "")];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user