# AI 서비스 개발 가이드

## 📋 **목차**
1. [AI 제안 기능 개발](#1-ai-제안-기능)
2. [용어 사전 기능 개발](#2-용어-사전-기능)
3. [관련 회의록 추천 기능 개발](#3-관련-회의록-추천-기능)
4. [백엔드 API 검증](#4-백엔드-api-검증)
5. [프롬프트 엔지니어링 가이드](#5-프롬프트-엔지니어링)

---

## 1. AI 제안 기능

### 📌 **기능 개요**
- **목적**: STT로 5초마다 수신되는 회의 텍스트를 분석하여 실시간 제안사항 제공
- **입력**: Redis에 축적된 최근 5분간의 회의 텍스트
- **출력**:
  - 논의사항 (Discussions): 회의와 관련있고 추가 논의가 필요한 주제
  - 결정사항 (Decisions): 명확한 의사결정 패턴이 감지된 내용

### ✅ **구현 완료 사항**

1. **SSE 스트리밍 엔드포인트**
   - 파일: `ai/src/main/java/com/unicorn/hgzero/ai/infra/controller/SuggestionController.java:111-131`
   - 엔드포인트: `GET /api/suggestions/meetings/{meetingId}/stream`
   - 프로토콜: Server-Sent Events (SSE)

2. **실시간 텍스트 처리**
   - 파일: `ai/src/main/java/com/unicorn/hgzero/ai/biz/service/SuggestionService.java:120-140`
   - Event Hub에서 TranscriptSegmentReady 이벤트 수신
   - Redis에 최근 5분 텍스트 슬라이딩 윈도우 저장

3. **Claude API 통합**
   - 파일: `ai/src/main/java/com/unicorn/hgzero/ai/infra/client/ClaudeApiClient.java`
   - 모델: claude-3-5-sonnet-20241022
   - 비동기 분석 및 JSON 파싱 구현

### 🔧 **개선 필요 사항**

#### 1.1 프롬프트 엔지니어링 개선

**현재 문제점**:
- 회의와 관련 없는 일상 대화도 분석될 가능성
- 회의 목적/안건 정보가 활용되지 않음

**개선 방법**:

```java
// ClaudeApiClient.java의 analyzeSuggestions 메서드 개선
public Mono<RealtimeSuggestionsDto> analyzeSuggestions(
        String transcriptText,
        String meetingPurpose,    // 추가
        List<String> agendaItems  // 추가
) {
    String systemPrompt = """
        당신은 비즈니스 회의록 작성 전문 AI 어시스턴트입니다.

        **역할**: 실시간 회의 텍스트를 분석하여 업무 관련 핵심 내용만 추출

        **분석 기준**:
        1. 논의사항 (discussions):
           - 회의 안건과 관련된 미결 주제
           - 추가 검토가 필요한 업무 항목
           - "~에 대해 논의 필요", "~검토해야 함" 등의 패턴
           - **제외**: 잡담, 농담, 인사말, 회의와 무관한 대화

        2. 결정사항 (decisions):
           - 명확한 의사결정 표현 ("~하기로 함", "~로 결정", "~로 합의")
           - 구체적인 Action Item
           - 책임자와 일정이 언급된 항목

        **필터링 규칙**:
        - 회의 목적/안건과 무관한 내용 제외
        - 단순 질의응답이나 확인 대화 제외
        - 업무 맥락이 명확한 내용만 추출

        **응답 형식**: 반드시 JSON만 반환
        {
          "discussions": [
            {
              "topic": "구체적인 논의 주제 (회의 안건과 직접 연관)",
              "reason": "회의 안건과의 연관성 설명",
              "priority": "HIGH|MEDIUM|LOW",
              "relatedAgenda": "관련 안건"
            }
          ],
          "decisions": [
            {
              "content": "결정된 내용",
              "confidence": 0.9,
              "extractedFrom": "원문 인용",
              "actionOwner": "담당자 (언급된 경우)",
              "deadline": "일정 (언급된 경우)"
            }
          ]
        }
        """;

    String userPrompt = String.format("""
        **회의 정보**:
        - 목적: %s
        - 안건: %s

        **회의 내용 (최근 5분)**:
        %s

        위 회의 목적과 안건에 맞춰 **회의와 관련된 내용만** 분석해주세요.
        """,
        meetingPurpose,
        String.join(", ", agendaItems),
        transcriptText
    );

    // 나머지 코드 동일
}
```

#### 1.2 회의 컨텍스트 조회 기능 추가

**구현 위치**: `SuggestionService.java`

```java
@RequiredArgsConstructor
public class SuggestionService implements SuggestionUseCase {

    private final MeetingGateway meetingGateway;  // 추가 필요

    private void analyzeAndEmitSuggestions(String meetingId) {
        // 1. 회의 정보 조회
        MeetingInfo meetingInfo = meetingGateway.getMeetingInfo(meetingId);
        String meetingPurpose = meetingInfo.getPurpose();
        List<String> agendaItems = meetingInfo.getAgendaItems();

        // 2. Redis에서 최근 5분 텍스트 조회
        String key = "meeting:" + meetingId + ":transcript";
        Set<String> recentTexts = redisTemplate.opsForZSet().reverseRange(key, 0, -1);

        if (recentTexts == null || recentTexts.isEmpty()) {
            return;
        }

        String accumulatedText = recentTexts.stream()
            .map(entry -> entry.split(":", 2)[1])
            .collect(Collectors.joining("\n"));

        // 3. Claude API 분석 (회의 컨텍스트 포함)
        claudeApiClient.analyzeSuggestions(
                accumulatedText,
                meetingPurpose,   // 추가
                agendaItems       // 추가
            )
            .subscribe(
                suggestions -> {
                    Sinks.Many<RealtimeSuggestionsDto> sink = meetingSinks.get(meetingId);
                    if (sink != null) {
                        sink.tryEmitNext(suggestions);
                        log.info("AI 제안사항 발행 완료 - meetingId: {}, 논의사항: {}, 결정사항: {}",
                            meetingId,
                            suggestions.getDiscussionTopics().size(),
                            suggestions.getDecisions().size());
                    }
                },
                error -> log.error("Claude API 분석 실패 - meetingId: {}", meetingId, error)
            );
    }
}
```

**필요한 Gateway 인터페이스**:

```java
package com.unicorn.hgzero.ai.biz.gateway;

public interface MeetingGateway {
    MeetingInfo getMeetingInfo(String meetingId);
}

@Data
@Builder
public class MeetingInfo {
    private String meetingId;
    private String purpose;
    private List<String> agendaItems;
}
```

---

## 2. 용어 사전 기능

### 📌 **기능 개요**
- **목적**: 회의 중 언급된 전문 용어를 맥락에 맞게 설명
- **입력**: 용어, 회의 컨텍스트
- **출력**:
  - 기본 정의
  - 회의 맥락에 맞는 설명
  - 이전 회의에서의 사용 예시 (있는 경우)

### ✅ **구현 완료 사항**

1. **용어 감지 API**
   - 파일: `ai/src/main/java/com/unicorn/hgzero/ai/infra/controller/TermController.java:35-82`
   - 엔드포인트: `POST /api/terms/detect`

2. **용어 설명 서비스 (Mock)**
   - 파일: `ai/src/main/java/com/unicorn/hgzero/ai/biz/service/TermExplanationService.java:24-53`

### 🔧 **개선 필요 사항**

#### 2.1 RAG 기반 용어 설명 구현

**구현 방법**:

```java
@Service
@RequiredArgsConstructor
public class TermExplanationService implements TermExplanationUseCase {

    private final SearchGateway searchGateway;
    private final ClaudeApiClient claudeApiClient;  // 추가

    @Override
    public TermExplanationResult explainTerm(
            String term,
            String meetingId,
            String context
    ) {
        log.info("용어 설명 생성 - term: {}, meetingId: {}", term, meetingId);

        // 1. RAG 검색: 이전 회의록에서 해당 용어 사용 사례 검색
        List<TermUsageExample> pastUsages = searchGateway.searchTermUsages(
            term,
            meetingId
        );

        // 2. Claude API로 맥락 기반 설명 생성
        String explanation = generateContextualExplanation(
            term,
            context,
            pastUsages
        );

        return TermExplanationResult.builder()
            .term(term)
            .definition(explanation)
            .context(context)
            .pastUsages(pastUsages.stream()
                .map(TermUsageExample::getDescription)
                .collect(Collectors.toList()))
            .build();
    }

    private String generateContextualExplanation(
            String term,
            String context,
            List<TermUsageExample> pastUsages
    ) {
        String prompt = String.format("""
            다음 용어를 회의 맥락에 맞게 설명해주세요:

            **용어**: %s
            **현재 회의 맥락**: %s

            **이전 회의에서의 사용 사례**:
            %s

            **설명 형식**:
            1. 기본 정의 (1-2문장)
            2. 현재 회의 맥락에서의 의미 (1-2문장)
            3. 이전 논의 참고사항 (있는 경우)

            비즈니스 맥락에 맞는 실용적인 설명을 제공하세요.
            """,
            term,
            context,
            formatPastUsages(pastUsages)
        );

        // Claude API 호출 (동기 방식)
        return claudeApiClient.generateExplanation(prompt)
            .block();  // 또는 비동기 처리
    }

    private String formatPastUsages(List<TermUsageExample> pastUsages) {
        if (pastUsages.isEmpty()) {
            return "이전 회의에서 언급된 적 없음";
        }

        return pastUsages.stream()
            .map(usage -> String.format(
                "- [%s] %s: %s",
                usage.getMeetingDate(),
                usage.getMeetingTitle(),
                usage.getDescription()
            ))
            .collect(Collectors.joining("\n"));
    }
}
```

#### 2.2 Azure AI Search 통합 (RAG)

**SearchGateway 구현**:

```java
package com.unicorn.hgzero.ai.infra.search;

@Service
@RequiredArgsConstructor
public class AzureAiSearchGateway implements SearchGateway {

    @Value("${external.ai-search.endpoint}")
    private String endpoint;

    @Value("${external.ai-search.api-key}")
    private String apiKey;

    @Value("${external.ai-search.index-name}")
    private String indexName;

    private final WebClient webClient;

    @Override
    public List<TermUsageExample> searchTermUsages(String term, String meetingId) {
        // 1. 벡터 검색 쿼리 생성
        Map<String, Object> searchRequest = Map.of(
            "search", term,
            "filter", String.format("meetingId ne '%s'", meetingId),  // 현재 회의 제외
            "top", 5,
            "select", "meetingId,meetingTitle,meetingDate,content,score"
        );

        // 2. Azure AI Search API 호출
        String response = webClient.post()
            .uri(endpoint + "/indexes/" + indexName + "/docs/search?api-version=2023-11-01")
            .header("api-key", apiKey)
            .header("Content-Type", "application/json")
            .bodyValue(searchRequest)
            .retrieve()
            .bodyToMono(String.class)
            .block();

        // 3. 응답 파싱
        return parseSearchResults(response);
    }

    private List<TermUsageExample> parseSearchResults(String response) {
        // JSON 파싱 로직
        // Azure AI Search 응답 형식에 맞춰 파싱
        return List.of();  // 구현 필요
    }
}
```

---

## 3. 관련 회의록 추천 기능

### 📌 **기능 개요**
- **목적**: 현재 회의와 관련된 과거 회의록 추천
- **입력**: 회의 목적, 안건, 진행 중인 회의 내용
- **출력**: 관련도 점수와 함께 관련 회의록 목록

### ✅ **구현 완료 사항**

1. **관련 회의록 조회 API**
   - 파일: `ai/src/main/java/com/unicorn/hgzero/ai/infra/controller/RelationController.java:31-63`
   - 엔드포인트: `GET /api/transcripts/{meetingId}/related`

2. **벡터 검색 서비스 (Mock)**
   - 파일: `ai/src/main/java/com/unicorn/hgzero/ai/biz/service/RelatedTranscriptSearchService.java:27-47`

### 🔧 **개선 필요 사항**

#### 3.1 Azure AI Search 벡터 검색 구현

**구현 방법**:

```java
@Service
@RequiredArgsConstructor
public class RelatedTranscriptSearchService implements RelatedTranscriptSearchUseCase {

    private final SearchGateway searchGateway;
    private final MeetingGateway meetingGateway;
    private final ClaudeApiClient claudeApiClient;

    @Override
    public List<RelatedMinutes> findRelatedTranscripts(
            String meetingId,
            String transcriptId,
            int limit
    ) {
        log.info("관련 회의록 검색 - meetingId: {}, limit: {}", meetingId, limit);

        // 1. 현재 회의 정보 조회
        MeetingInfo currentMeeting = meetingGateway.getMeetingInfo(meetingId);
        String searchQuery = buildSearchQuery(currentMeeting);

        // 2. Azure AI Search로 벡터 유사도 검색
        List<SearchResult> searchResults = searchGateway.searchRelatedMeetings(
            searchQuery,
            meetingId,
            limit
        );

        // 3. 검색 결과를 RelatedMinutes로 변환
        return searchResults.stream()
            .map(this::toRelatedMinutes)
            .collect(Collectors.toList());
    }

    private String buildSearchQuery(MeetingInfo meeting) {
        // 회의 목적 + 안건을 검색 쿼리로 변환
        return String.format("%s %s",
            meeting.getPurpose(),
            String.join(" ", meeting.getAgendaItems())
        );
    }

    private RelatedMinutes toRelatedMinutes(SearchResult result) {
        return RelatedMinutes.builder()
            .transcriptId(result.getMeetingId())
            .title(result.getTitle())
            .date(result.getDate())
            .participants(result.getParticipants())
            .relevanceScore(result.getScore() * 100)  // 0-1 -> 0-100
            .commonKeywords(extractCommonKeywords(result))
            .summary(result.getSummary())
            .link("/transcripts/" + result.getMeetingId())
            .build();
    }
}
```

#### 3.2 임베딩 기반 검색 구현

**Azure OpenAI Embedding 활용**:

```java
@Service
public class EmbeddingService {

    @Value("${azure.openai.endpoint}")
    private String endpoint;

    @Value("${azure.openai.api-key}")
    private String apiKey;

    @Value("${azure.openai.embedding-deployment}")
    private String embeddingDeployment;

    private final WebClient webClient;

    /**
     * 텍스트를 벡터로 변환
     */
    public float[] generateEmbedding(String text) {
        Map<String, Object> request = Map.of(
            "input", text
        );

        String response = webClient.post()
            .uri(endpoint + "/openai/deployments/" + embeddingDeployment + "/embeddings?api-version=2024-02-15-preview")
            .header("api-key", apiKey)
            .header("Content-Type", "application/json")
            .bodyValue(request)
            .retrieve()
            .bodyToMono(String.class)
            .block();

        // 응답에서 embedding 벡터 추출
        return parseEmbedding(response);
    }

    private float[] parseEmbedding(String response) {
        // JSON 파싱하여 float[] 반환
        return new float[0];  // 구현 필요
    }
}
```

**Azure AI Search 벡터 검색**:

```java
@Override
public List<SearchResult> searchRelatedMeetings(
        String query,
        String excludeMeetingId,
        int limit
) {
    // 1. 쿼리 텍스트를 벡터로 변환
    float[] queryVector = embeddingService.generateEmbedding(query);

    // 2. 벡터 검색 쿼리
    Map<String, Object> searchRequest = Map.of(
        "vector", Map.of(
            "value", queryVector,
            "fields", "contentVector",
            "k", limit
        ),
        "filter", String.format("meetingId ne '%s'", excludeMeetingId),
        "select", "meetingId,title,date,participants,summary,score"
    );

    // 3. Azure AI Search API 호출
    String response = webClient.post()
        .uri(endpoint + "/indexes/" + indexName + "/docs/search?api-version=2023-11-01")
        .header("api-key", apiKey)
        .bodyValue(searchRequest)
        .retrieve()
        .bodyToMono(String.class)
        .block();

    return parseSearchResults(response);
}
```

---

## 4. 백엔드 API 검증

### 4.1 SSE 스트리밍 테스트

**테스트 방법**:

```bash
# 1. SSE 엔드포인트 연결
curl -N -H "Accept: text/event-stream" \
  "http://localhost:8083/api/suggestions/meetings/test-meeting-001/stream"

# 예상 출력:
# event: ai-suggestion
# data: {"discussionTopics":[...],"decisions":[...]}
```

**프론트엔드 연동 예시** (JavaScript):

```javascript
// 05-회의진행.html에 추가
const meetingId = "test-meeting-001";
const eventSource = new EventSource(
    `http://localhost:8083/api/suggestions/meetings/${meetingId}/stream`
);

eventSource.addEventListener('ai-suggestion', (event) => {
    const suggestions = JSON.parse(event.data);

    // 논의사항 표시
    suggestions.discussionTopics.forEach(topic => {
        addDiscussionCard(topic);
    });

    // 결정사항 표시
    suggestions.decisions.forEach(decision => {
        addDecisionCard(decision);
    });
});

eventSource.onerror = (error) => {
    console.error('SSE 연결 오류:', error);
    eventSource.close();
};

function addDiscussionCard(topic) {
    const card = document.createElement('div');
    card.className = 'ai-suggestion-card';
    card.innerHTML = `
        <div class="ai-suggestion-header">
            <span class="ai-suggestion-time">${new Date().toLocaleTimeString()}</span>
            <span class="badge badge-${topic.priority.toLowerCase()}">${topic.priority}</span>
        </div>
        <div class="ai-suggestion-text">
            <strong>[논의사항]</strong> ${topic.topic}
        </div>
        <div class="ai-suggestion-reason">${topic.reason}</div>
    `;
    document.getElementById('aiSuggestionList').prepend(card);
}
```

### 4.2 용어 설명 API 테스트

```bash
# POST /api/terms/detect
curl -X POST http://localhost:8083/api/terms/detect \
  -H "Content-Type: application/json" \
  -d '{
    "meetingId": "test-meeting-001",
    "text": "오늘 회의에서는 MSA 아키텍처와 API Gateway 설계에 대해 논의하겠습니다.",
    "organizationId": "org-001"
  }'

# 예상 응답:
{
  "success": true,
  "data": {
    "detectedTerms": [
      {
        "term": "MSA",
        "confidence": 0.95,
        "category": "아키텍처",
        "definition": "Microservices Architecture의 약자...",
        "context": "회의 맥락에 맞는 설명..."
      },
      {
        "term": "API Gateway",
        "confidence": 0.92,
        "category": "아키텍처"
      }
    ],
    "totalCount": 2
  }
}
```

### 4.3 관련 회의록 API 테스트

```bash
# GET /api/transcripts/{meetingId}/related
curl "http://localhost:8083/api/transcripts/test-meeting-001/related?transcriptId=transcript-001&limit=5"

# 예상 응답:
{
  "success": true,
  "data": {
    "relatedTranscripts": [
      {
        "transcriptId": "meeting-002",
        "title": "MSA 아키텍처 설계 회의",
        "date": "2025-01-15",
        "participants": ["김철수", "이영희"],
        "relevanceScore": 85.5,
        "commonKeywords": ["MSA", "API Gateway", "Spring Boot"],
        "link": "/transcripts/meeting-002"
      }
    ],
    "totalCount": 5
  }
}
```

---

## 5. 프롬프트 엔지니어링 가이드

### 5.1 AI 제안사항 프롬프트

**핵심 원칙**:
1. **명확한 역할 정의**: AI의 역할을 "회의록 작성 전문가"로 명시
2. **구체적인 기준**: 무엇을 추출하고 무엇을 제외할지 명확히 명시
3. **컨텍스트 제공**: 회의 목적과 안건을 프롬프트에 포함
4. **구조화된 출력**: JSON 형식으로 파싱 가능한 응답 요청

**프롬프트 템플릿**:

```
당신은 비즈니스 회의록 작성 전문 AI 어시스턴트입니다.

**역할**: 실시간 회의 텍스트를 분석하여 업무 관련 핵심 내용만 추출

**분석 기준**:
1. 논의사항 (discussions):
   - 회의 안건과 관련된 미결 주제
   - 추가 검토가 필요한 업무 항목
   - "~에 대해 논의 필요", "~검토해야 함" 등의 패턴
   - **제외**: 잡담, 농담, 인사말, 회의와 무관한 대화

2. 결정사항 (decisions):
   - 명확한 의사결정 표현 ("~하기로 함", "~로 결정", "~로 합의")
   - 구체적인 Action Item
   - 책임자와 일정이 언급된 항목

**필터링 규칙**:
- 회의 목적/안건과 무관한 내용 제외
- 단순 질의응답이나 확인 대화 제외
- 업무 맥락이 명확한 내용만 추출

**응답 형식**: 반드시 JSON만 반환
{
  "discussions": [
    {
      "topic": "구체적인 논의 주제",
      "reason": "회의 안건과의 연관성",
      "priority": "HIGH|MEDIUM|LOW",
      "relatedAgenda": "관련 안건"
    }
  ],
  "decisions": [
    {
      "content": "결정 내용",
      "confidence": 0.9,
      "extractedFrom": "원문 인용",
      "actionOwner": "담당자 (있는 경우)",
      "deadline": "일정 (있는 경우)"
    }
  ]
}

---

**회의 정보**:
- 목적: {meeting_purpose}
- 안건: {agenda_items}

**회의 내용 (최근 5분)**:
{transcript_text}

위 회의 목적과 안건에 맞춰 **회의와 관련된 내용만** 분석해주세요.
```

### 5.2 용어 설명 프롬프트

```
다음 용어를 회의 맥락에 맞게 설명해주세요:

**용어**: {term}
**현재 회의 맥락**: {context}

**이전 회의에서의 사용 사례**:
{past_usages}

**설명 형식**:
1. 기본 정의 (1-2문장, 비전문가도 이해 가능하도록)
2. 현재 회의 맥락에서의 의미 (1-2문장, 이번 회의에서 이 용어가 어떤 의미로 쓰이는지)
3. 이전 논의 참고사항 (있는 경우, 과거 회의에서 관련 결정사항)

비즈니스 맥락에 맞는 실용적인 설명을 제공하세요.
```

### 5.3 관련 회의록 검색 쿼리 생성 프롬프트

```
현재 회의와 관련된 과거 회의록을 찾기 위한 검색 쿼리를 생성하세요.

**현재 회의 정보**:
- 목적: {meeting_purpose}
- 안건: {agenda_items}
- 진행 중인 주요 논의: {current_discussions}

**검색 쿼리 생성 규칙**:
1. 회의 목적과 안건에서 핵심 키워드 추출
2. 동의어와 관련 용어 포함
3. 너무 일반적인 단어는 제외 (예: "회의", "논의")
4. 5-10개의 키워드로 구성

**출력 형식**: 키워드를 공백으로 구분한 문자열
예시: "MSA 마이크로서비스 API게이트웨이 분산시스템 아키텍처설계"
```

---

## 6. 환경 설정

### 6.1 application.yml 확인 사항

```yaml
# Claude API 설정
external:
  ai:
    claude:
      api-key: ${CLAUDE_API_KEY}  # 환경변수 설정 필요
      base-url: https://api.anthropic.com
      model: claude-3-5-sonnet-20241022
      max-tokens: 2000
      temperature: 0.3

# Azure AI Search 설정
  ai-search:
    endpoint: ${AZURE_AI_SEARCH_ENDPOINT}
    api-key: ${AZURE_AI_SEARCH_API_KEY}
    index-name: meeting-transcripts

# Event Hub 설정
  eventhub:
    connection-string: ${AZURE_EVENTHUB_CONNECTION_STRING}
    namespace: hgzero-eventhub-ns
    eventhub-name: hgzero-eventhub-name
    consumer-group:
      transcript: ai-transcript-group
```

### 6.2 환경 변수 설정

```bash
# Claude API
export CLAUDE_API_KEY="sk-ant-..."

# Azure AI Search
export AZURE_AI_SEARCH_ENDPOINT="https://your-search-service.search.windows.net"
export AZURE_AI_SEARCH_API_KEY="your-api-key"

# Azure Event Hub
export AZURE_EVENTHUB_CONNECTION_STRING="Endpoint=sb://..."
```

---

## 7. 테스트 시나리오

### 7.1 전체 통합 테스트 시나리오

1. **회의 시작**
   - 회의 생성 API 호출
   - SSE 스트림 연결

2. **STT 텍스트 수신**
   - Event Hub로 TranscriptSegmentReady 이벤트 발행
   - Redis에 텍스트 축적 확인

3. **AI 제안사항 생성**
   - 5분간 텍스트 축적
   - Claude API 자동 호출
   - SSE로 제안사항 수신 확인

4. **용어 설명 요청**
   - 감지된 용어로 설명 API 호출
   - 맥락에 맞는 설명 확인

5. **관련 회의록 조회**
   - 관련 회의록 API 호출
   - 유사도 점수 확인

---

## 8. 다음 단계

1. ✅ **MeetingGateway 구현**: Meeting 서비스와 통신하여 회의 정보 조회
2. ✅ **SearchGateway Azure AI Search 통합**: 벡터 검색 구현
3. ✅ **ClaudeApiClient 프롬프트 개선**: 회의 컨텍스트 활용
4. ✅ **프론트엔드 SSE 연동**: 05-회의진행.html에 SSE 클라이언트 추가
5. ✅ **통합 테스트**: 전체 플로우 동작 확인
