# 문제 해결 ## 텍스트만 답하고 리포트가 생성되지 않음 **원인**: 포털이 단일 조회만 실행했거나, MCP discovery 결과에 이전 결과 입력형 렌더러가 없다. **확인**: 1. 포털 설정 endpoint가 `https://hmm-backoffice.cloud-handson.com/mcp`인지 확인한다. 2. `tools/list` 결과에 조회 도구와 `reportJson` 입력을 가진 HTML/리포트 렌더러가 모두 있는지 확인한다. 3. 포털 실행 상세에서 실행 방식이 `agent`, Agent 단계가 두 번인지 확인한다. **해결**: endpoint와 허용 목록을 함께 갱신한 뒤 포털 서비스를 재기동한다. 렌더러 설명에는 `조회 결과 후속 처리`처럼 선행 결과를 받는다는 문구를 유지한다. **재발 방지**: 새 업무 리포트 도구도 `reportJson`류 입력과 후속 처리 의도를 설명에 선언한다. 포털 코드에 특정 도구명을 추가하지 않는다. ## 렌더러가 입력 형식 오류를 반환함 **원인**: 조회 도구가 `items`/`results` 배열을 반환하지 않거나, 렌더러가 요구하는 payload 계약과 템플릿 계약이 다르다. **확인**: 첫 Agent 단계의 응답에 행 배열이 있는지, 두 번째 단계 arguments에 `report`와 `rows`가 있는지 확인한다. 비밀값·Bearer token은 실행 상세에 남기지 않는다. **해결**: 조회 도구는 구조화 행 배열을 반환하고, 렌더러 함수는 `report`와 `rows` 계약을 유지한다. ## 생성된 HTML이 화면에 표시되지 않음 **원인**: 렌더러 응답에 `html` 문자열이 없거나, HTML이 유효하지 않다. **확인**: 두 번째 MCP 응답의 `response.html` 존재와 길이를 확인한다. **해결**: DB 템플릿 활성 상태와 custom Agent Tool의 반환 형식을 확인한다. 포털은 유효한 `html` 또는 `rendered_html`만 sandboxed iframe으로 표시한다. ## 일반 답변에 HTML 표가 반복되거나 글자가 잘 보이지 않음 **원인**: 최종 답변 모델이 사용자의 `HTML로 보여줘`를 artifact 생성이 아니라 답변 본문 형식 요청으로 해석해 `` 또는 Markdown 표를 다시 작성했다. **확인**: MCP 마지막 응답에 유효한 `html`이 있으면서 저장된 `answer`에도 HTML 태그나 표 행이 있는지 확인한다. **해결**: 최종 답변 지침에 HTML artifact 우선 규칙을 적용하고, 출력 후 검사에서 일반 답변을 생성 완료·제목·조회 건수 안내로 정규화한다. iframe CSS나 조회 Tool은 변경하지 않는다. **재발 방지**: renderer가 반환된 시나리오에서 일반 답변에 `
`과 Markdown 표가 없는지 회귀 테스트한다. ## 리포트 제목에 질문 전체 문장이 표시됨 **원인**: `report.title`에 사용자 질문을 그대로 복사했다. **확인**: renderer 호출의 `reportJson.report.title`과 `question`이 완전히 같은지 확인한다. **해결**: 제목 생성 지침으로 출력 형식·행동 문구를 제거한 짧은 명사형 제목을 만들고, `report.question`에는 원문을 유지한다. **재발 방지**: 제목과 원문 질문이 역할상 분리되고 제목 길이 제한이 적용되는지 검증한다. ## 사용자를 바꿨는데 이전 사용자의 이름·팀이 제목에 남음 **증상**: E1001로 리포트를 만든 뒤 같은 대화에서 E1002를 선택하고 `내 담당 선사`를 요청했는데, 데이터는 E1002의 2건이면서 제목은 `E1001 팀장 ...`으로 표시된다. **원인**: 질문 독립화 단계가 사용자 구분 없이 이전 대화 turn을 불러와 `내`를 이전 사용자인 E1001로 치환했다. VPD는 현재 Bearer token으로 정상 적용되므로 데이터와 제목 범위가 어긋난다. **확인**: 1. 저장된 turn의 `selected_user_id`, `question`, `standalone_question`을 비교한다. 2. E1002 turn의 `standalone_question` 또는 renderer `report.title`에 E1001이 남아 있는지 확인한다. 3. 선행 조회 행은 E1002 담당 선사만 반환되는지 별도로 확인한다. **해결**: 대화 문맥을 현재 `selected_user_id`로 필터링하고, 질문 독립화와 제목 생성에 현재 사용자 ID를 명시적으로 전달한다. 1인칭은 현재 사용자만 가리키며 이전 사용자의 관리자·팀 범위를 재사용하지 않도록 공통 지침을 적용한다. `report.requestedBy`도 현재 선택 사용자 ID를 사용한다. **재발 방지**: 한 대화에서 E1001→E1002로 연속 전환하는 회귀 테스트를 유지한다. 제목과 행 수를 사용자별로 고정하지 말고 E1002 결과에 E1001 식별자가 포함되지 않는지를 검사한다. ## `reportJson.rows`가 `htmlRow`와 ``만 포함함 **원인**: 포털이 `HTML로 보여줘`가 포함된 원문을 첫 Select AI 데이터 조회에 그대로 전달해, 조회 Tool이 컬럼별 구조화 행 대신 HTML 조각을 반환했다. **확인**: 첫 Agent 단계의 MCP argument와 원본 결과를 확인한다. 조회 질문에 표현 형식이 남아 있고 행 key가 `htmlRow`뿐이면 이 문제다. **해결**: 포털의 데이터 질문 재작성 instruction이 업무 대상·필터·지표는 유지하면서 HTML·리포트· 차트 생성 요청만 제거하도록 한다. renderer나 DB 조회 Tool을 변경하지 않는다. **재발 방지**: `HTML로 보여줘`가 포함된 통합 질문으로 실제 연속 호출하고, 조회 결과와 `reportJson.rows` 모두 업무 필드를 가지며 HTML 태그가 없는지 검증한다. ## 리포트는 생성됐지만 행이 0건임 **원인**: 다음 둘 중 하나다. 1. 선행 조회가 실제로 0건이다. 2. 선행 Select AI Tool은 행을 반환했지만 포털이 `response.result` 안의 JSON 배열 문자열을 `reportJson.rows`로 구조화하지 못했다. **확인**: Agent 첫 단계의 원본 `response.result` 배열 건수와 두 번째 단계 `reportJson.rows`를 비교한다. 원본이 8건이고 `rows`가 0건이면 권한이나 Select AI 문제가 아니라 결과 전달 문제다. **해결**: 포털의 범용 결과 해석기가 `items`, `results`, `rows`, `response`, `result` wrapper와 JSON 배열 문자열을 재귀적으로 처리하는지 확인한다. 조회 Tool, endpoint, VPD 정책, DB package를 교체하지 않는다. **재발 방지**: 중첩 JSON 배열 회귀 테스트와 운영 연속 호출 검증을 유지한다. 같은 Bearer 문맥에서 조회 원본 행 수와 renderer 입력 행 수가 같아야 배포를 완료한다. ## 첫 리포트는 정상인데 두 번째부터 값이 비거나 깨짐 **원인**: Select AI가 컬럼 label을 underscore 대신 공백 또는 혼합 대소문자로 반환했고, 포털이 underscore만 camelCase로 변환했다. **확인**: 연속 호출의 `reportJson.rows`에서 `LATEST_REVENUE_USD`, `LATEST REVENUE USD`, `latest Revenue Usd`가 모두 `latestRevenueUsd`로 정규화되는지 확인한다. **해결**: 공백, underscore, hyphen을 공통 단어 경계로 처리한다. 대화 상태나 업무 컬럼 이름을 조건문에 하드코딩하지 않는다. **재발 방지**: 서로 다른 컬럼 label 표기의 두 응답을 연속 정규화하는 회귀 테스트를 유지한다. ## 두 번째 리포트의 컬럼이 한글 alias로 바뀌어 깨짐 **원인**: Select AI가 첫 호출에는 `CARRIER_CODE`, 두 번째 호출에는 `선사코드`처럼 의미는 같지만 번역된 alias를 생성했다. 구분자 정규화만으로는 서로 다른 언어의 key를 같은 필드로 판단할 수 없다. **확인**: 선행 조회 2건은 정상인데 두 번째 `reportJson.rows`가 `선사명`, `선사코드`, `최신매출Usd` 등을 가지는지 확인한다. **해결**: 선사 Select AI query contract에 renderer가 요구하는 ASCII uppercase underscore 출력 alias를 선언한다. 실제 SQL, 직원 코드, 결과 값은 고정하지 않는다. **재발 방지**: 같은 팀원 질의를 두 번 호출해 두 응답의 key 집합과 renderer HTML을 비교한다.