인티그레이션
인턴·신입근거: 모델 학습 → 서빙 → 재학습 선순환을 사내 DevOps·DE와 함께 설계해 지속 가능한 AI 서비스 구축
※ 실제 산출물이 아닌 AI 예측 시안입니다.
공고가 주요업무에 '모델 학습 → 서빙 → 재학습 선순환을 사내 DevOps·DE와 함께 설계해 지속 가능한 AI 서비스 구축' 을 적었습니다. 그 선순환에서 가장 먼저 정해야 하는 것이 무엇을 다시 배울 자료로 삼을지이고, 이 포트폴리오는 그 결정을 항목별 기준표로 옮깁니다. 'Node.js / TypeScript 기반 AI Agent 백엔드 설계·구현·운영' 이 초안과 최종본이 지나가는 자리라, 기록을 남기는 일도 같은 사람이 맡습니다. 이 포트폴리오가 보여주는 것은 수집 기준을 데이터로 두고 초안과 최종본을 견줘 학습 자료를 가르는 기술이며, 한의학 지식이나 진료 내용 판단은 주장하지 않습니다. 모르는 깊이는 면접에서 모른다고 말하는 편이 낫습니다.
'모델 학습 → 서빙 → 재학습 선순환을 사내 DevOps·DE와 함께 설계'. 그 선순환의 첫 칸이 무엇을 모을지 정하는 일입니다.
'Node.js / TypeScript 기반 AI Agent 백엔드 설계·구현·운영'. 초안과 최종본이 지나가는 자리가 그 백엔드입니다.
'AI 모델의 결과물이 사용자에게 가치를 전달하는' 자리까지 책임집니다. 고쳐 쓴 흔적이 그 가치를 재는 눈금입니다.
에이전트가 쓴 초안과 사용자가 고쳐 낸 최종본을 견줘 재학습에 쓸 것을 가릅니다. 항목별 수집 기준에 모을지 여부와 최소 편집 비율, 제외 사유를 두고 초안 목록과 맞춰, 많이 고친 초안은 후보로 모으고 그렇지 않으면 그대로 씀·편집 적음·항목 제외 중 어느 쪽인지 사유로 남깁니다. 최종본이 없는 초안은 판단할 수 없다고 표시하고, 기준에 없는 항목은 미선언으로 남겨 먼저 정하게 합니다. 초안 목록은 실 진료 자료 대신 지원자가 만든 에이전트를 직접 써 가며 남긴 것을 가정하며 실제 환자 정보를 쓰지 않습니다.
사내 에이전트를 만들고 고치는 프로덕트 엔지니어입니다. 사용자가 초안을 어떻게 고쳐 쓰는지가 가장 좋은 학습 자료인 것을 알면서도, 그 기록이 화면을 떠나면 어디에도 남지 않아 매번 스스로 다시 읽어 보던 사람입니다.
항목마다 모을지 여부와 최소 편집 비율을 기준으로 두고 초안과 최종본을 견주면, 재학습에 쓸 자료와 버릴 자료가 갈리고 뺀 이유가 근거로 남는다는 가정입니다. 기준에 없는 항목은 미선언으로 빠지므로 아직 정하지 않은 화면이 그대로 드러납니다.
초안 목록과 항목별 기준을 받아 초안마다 수집·제외를 판정하고, 제외면 그대로 씀·편집 적음·항목 제외·최종본 없음·미선언 중 어느 쪽인지 붙여 항목별로 묶은 수집 리포트와 후보 묶음 파일을 내는 CLI 가 도는 완결품입니다.
작은 에이전트를 만들어 초안 300건을 내고 스스로 고쳐 쓰며 기록을 남긴 뒤, 손으로 붙인 정답 수집 여부와 대조해 일치율을 냅니다.
항목별로 자주 고쳐지는 자리를 모아 프롬프트에 넣을 지시 문장 후보를 뽑고, 그 문장을 넣은 판과 안 넣은 판의 편집 비율을 견주는 단계입니다.
프로덕트 엔지니어가 초안 목록과 기준표를 넣으면 초안마다 수집 여부와 제외 사유가 항목별로 묶여 나오는 과정을 시연합니다.
직접 만든 에이전트로 남긴 초안 300건과 손으로 붙인 정답 수집 여부를 대조해 일치율을 냅니다. 단, 정답을 작성자가 붙인 기준이라 성능 검증이 아니라 데모 조건 동작입니다.
최종본이 비었거나 항목 표기가 흔들리거나 기준에 없는 항목이 섞인 입력 30종을 넣어도 출력 형식이 안 깨지고 사유별로 빠지는 비율을 보여줍니다.
최소 편집 비율을 바꿨을 때 옛 판정이 어떻게 뒤집히는지 회귀로 확인합니다.
진짜 성능 검증은 여기서 멈춥니다. 위 수치는 작성자가 남긴 기록의 자가채점이며, 실제 재학습 효과는 인티그레이션의 실 사용자 기록과 실 모델이 있어야 측정됩니다. 이 포트폴리오는 그 검증을 바로 돌릴 수 있는 상태까지 만듭니다.
수치는 작성자가 남긴 기록의 자가채점이라 성능 증명이 아니므로, 면접에서는 데모 동작과 실검증 준비 상태로 솔직히 설명합니다.
정답을 작성자가 붙였으니 잘 맞는 게 당연하므로, 최종본 결측과 임계에 걸치는 편집, 기준에 없는 항목을 일부러 섞어 쉬운 초안만 푸는 함정을 피합니다.
이 포트폴리오가 보여주는 것은 수집 기준을 데이터로 두고 초안과 최종본을 견줘 학습 자료를 가르는 기술이며, 한의학 지식이나 진료 내용 판단은 주장하지 않습니다. 판정은 글자 수 비교가 하고 LLM 은 담당자가 읽을 사유 문장만 맡습니다.
글자를 많이 고쳤다고 그 최종본이 더 낫다는 뜻은 아니어서, 모은 후보를 그대로 학습에 넣지 않고 사람이 한 번 고르는 단계를 둡니다.
실제 도입 때는 항목마다 개인정보가 섞이는 정도가 달라, 제외 항목을 정하는 일을 보안 담당과 함께 하는 도입 과제로 둡니다.
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
에이전트가 낸 초안과 사용자가 고쳐 낸 최종본을 짝지어 편 것입니다. 초안 번호와 세션 번호, 항목 표기, 초안 글, 최종 글, 각 글자 수, 두 글에 함께 남은 글자 수, 고친 사람 역할, 고친 시각을 한 행에 둡니다. 최종본이 빈 행과 한 글자도 안 고친 행, 숫자 하나만 바꾼 행, 기준표에 없는 항목을 일부러 섞어 두어 사유가 갈리게 둡니다. 글은 모두 지어낸 문장이고 실제 환자 정보를 담지 않습니다.
| draft_no | session_no | field_raw | agent_text | final_text | agent_chars | final_chars | kept_chars | role_raw | edited_at |
|---|---|---|---|---|---|---|---|---|---|
| DR-001 | SS-101 | 초진 요약 | 어깨 통증으로 2주 전부터 불편을 느끼셨습니다. | 어깨 통증이 2주 전 시작되었고 야간에 심해집니다. | 26 | 28 | 14 | 원장 | 2026-08-20T10:12:00 |
| DR-002 | SS-101 | 생활 지도 | 따뜻한 물로 샤워하고 무리한 운동은 피하세요. | 따뜻한 물로 샤워하고 무리한 운동은 피하세요. | 25 | 25 | 25 | 원장 | 2026-08-20T10:13:00 |
| DR-003 | SS-102 | 예약 안내 | 다음 진료는 일주일 뒤로 잡아 드리겠습니다. | 다음 진료는 일주일 뒤 같은 요일로 잡아 드리겠습니다. | 23 | 28 | 22 | 데스크 | 2026-08-20T11:02:00 |
| DR-004 | SS-102 | 초진 요약 | 소화 불편을 자주 느끼신다고 하셨습니다. | — | 22 | — | — | — | — |
| DR-005 | SS-103 | 환자 연락처 정리 | 연락처를 최근 통화 기준으로 정리했습니다. | 연락처를 최근 통화 기준으로 다시 정리했습니다. | 23 | 26 | 22 | 데스크 | 2026-08-21T09:30:00 |
아래 JSON Schema 를 그대로 만족하는 초안 행을 300개 더 만들어 주세요. 항목을 대여섯 가지로 섞고 세션마다 초안 두셋을 붙입니다. 2할은 최종본과 고친 시각을 비우고, 몇 행은 초안과 최종본을 똑같이 두며, 몇 행은 숫자나 낱말 하나만 바꿔 두세요. 함께 남은 글자 수는 두 글을 실제로 견줘 셈한 값으로 채웁니다. 기준표에 없는 항목도 몇 행 넣습니다. 실제 사람의 정보를 쓰지 말고 판정 결과는 넣지 마세요.
{"type": "object", "properties": {"draft_no": {"type": "string"}, "session_no": {"type": "string"}, "field_raw": {"type": "string"}, "agent_text": {"type": "string"}, "final_text": {"type": ["string", "null"]}, "agent_chars": {"type": "integer"}, "final_chars": {"type": ["integer", "null"]}, "kept_chars": {"type": ["integer", "null"]}, "role_raw": {"type": ["string", "null"]}, "edited_at": {"type": ["string", "null"]}}, "required": ["draft_no", "session_no", "field_raw", "agent_text", "final_text", "agent_chars", "final_chars", "kept_chars", "role_raw", "edited_at"]}{"type": "object", "properties": {"draft_no": {"type": "string"}, "session_no": {"type": "string"}, "field_raw": {"type": "string"}, "agent_text": {"type": "string"}, "final_text": {"type": ["string", "null"]}, "agent_chars": {"type": "integer"}, "final_chars": {"type": ["integer", "null"]}, "kept_chars": {"type": ["integer", "null"]}, "role_raw": {"type": ["string", "null"]}, "edited_at": {"type": ["string", "null"]}}, "required": ["draft_no", "session_no", "field_raw", "agent_text", "final_text", "agent_chars", "final_chars", "kept_chars", "role_raw", "edited_at"]}항목별 수집 기준표입니다. 기준 번호와 항목, 모을지 여부, 최소 편집 비율, 제외 사유, 담당을 한 행에 둡니다. 항목마다 기준이 달라 같은 정도로 고쳐도 어떤 항목은 모이고 어떤 항목은 빠집니다. 개인정보나 규정 문구가 들어가는 항목은 아예 모으지 않도록 두고, 초안에 나오는 항목 중 일부는 일부러 넣지 않아 미선언이 도출되게 둡니다.
| policy_no | field_raw | collect_flag | min_edit_ratio | exclude_reason | owner_name |
|---|---|---|---|---|---|
| EP-01 | 초진 요약 | true | 0.2 | — | 임상프로덕트 |
| EP-02 | 생활 지도 | true | 0.2 | — | 임상프로덕트 |
| EP-03 | 예약 안내 | true | 0.15 | — | 운영프로덕트 |
| EP-04 | 환자 연락처 정리 | false | 0 | 개인정보가 그대로 들어갑니다 | 보안 |
| EP-05 | 내부 메모 | true | 0.25 | — | 임상프로덕트 |
아래 JSON Schema 를 그대로 만족하는 수집 기준 행을 20개 더 만들어 주세요. 항목마다 최소 편집 비율을 0.1 에서 0.3 사이로 다르게 두고, 개인정보나 규정 문구가 들어가는 항목은 모으지 않기로 두며 그 사유를 한 문장으로 적습니다. 초안에 나오는 항목 중 일부는 일부러 넣지 마세요 — 미선언이 있어야 기준표의 구멍이 드러납니다. 판정 결과는 넣지 마세요.
{"type": "object", "properties": {"policy_no": {"type": "string"}, "field_raw": {"type": "string"}, "collect_flag": {"type": "boolean"}, "min_edit_ratio": {"type": "number"}, "exclude_reason": {"type": ["string", "null"]}, "owner_name": {"type": "string"}}, "required": ["policy_no", "field_raw", "collect_flag", "min_edit_ratio", "owner_name"]}{"type": "object", "properties": {"policy_no": {"type": "string"}, "field_raw": {"type": "string"}, "collect_flag": {"type": "boolean"}, "min_edit_ratio": {"type": "number"}, "exclude_reason": {"type": ["string", "null"]}, "owner_name": {"type": "string"}}, "required": ["policy_no", "field_raw", "collect_flag", "min_edit_ratio", "owner_name"]}| TypeScript | 초안과 최종본을 견줘 편집 비율을 세고 기준과 맞추는 판정기를 맡습니다. |
| Node.js | CLI 실행기와 에이전트 기록 읽기를 맡습니다. |
| PostgreSQL | 초안과 최종본, 판정 결과를 담습니다. |
| React | 2주차에 초안을 고쳐 쓰며 기록을 남기는 작은 화면을 만듭니다. |
✗ fail → 미선언이 절반을 넘으면 기준표를 먼저 채웁니다.
✓ pass → 2주차 직접 기록 수집으로 넘어갑니다.
✗ fail → 어긋난 항목을 모아 최소 편집 비율을 다시 봅니다.
✓ pass → 3주차 프롬프트 개선안으로 넘어갑니다.
초안 하나와 수집 기준이 만나는 자리마다 재학습에 쓸지 정합니다. 빼면 그대로 씀·편집 적음·항목 제외·최종본 없음·미선언 중 어느 쪽인지 싣습니다. 리포트는 이 사유 코드로 묶습니다.
{"type": "object", "properties": {"verdict_no": {"type": "string"}, "draft_no": {"type": "string"}, "session_no": {"type": "string"}, "field_raw": {"type": "string"}, "policy_no": {"type": ["string", "null"]}, "collect_code": {"type": "string", "enum": ["collect", "unchanged", "edit_too_small", "field_excluded", "final_missing", "field_undeclared"]}, "edit_ratio": {"type": ["number", "null"], "description": "agent_chars 와 final_chars 중 큰 값에서 kept_chars 를 뺀 뒤 그 큰 값으로 나눈 비율입니다. 고친 글이 있는 초안에만 채웁니다."}, "min_edit_ratio": {"type": ["number", "null"]}, "owner_name": {"type": ["string", "null"]}, "evidence": {"type": "array", "items": {"type": "object", "properties": {"field": {"type": "string"}, "agg": {"type": ["string", "null"]}, "value": {}}, "required": ["field", "value"]}}, "summary": {"type": "string"}, "hedge": {"type": ["string", "null"]}}, "required": ["verdict_no", "draft_no", "session_no", "field_raw", "collect_code", "evidence", "summary"]}[
{
"verdict_no": "RC-001",
"draft_no": "DR-001",
"session_no": "SS-101",
"field_raw": "초진 요약",
"policy_no": "EP-01",
"collect_code": "collect",
"edit_ratio": 0.5,
"min_edit_ratio": 0.2,
"owner_name": "임상프로덕트",
"evidence": [
{
"field": "kept_chars",
"agg": null,
"value": 14
},
{
"field": "final_chars",
"agg": null,
"value": 28
}
],
"summary": "절반 가까이 고쳐 써서 재학습 후보로 모읍니다.",
"hedge": null
},
{
"verdict_no": "RC-002",
"draft_no": "DR-002",
"session_no": "SS-101",
"field_raw": "생활 지도",
"policy_no": "EP-02",
"collect_code": "unchanged",
"edit_ratio": 0,
"min_edit_ratio": 0.2,
"owner_name": "임상프로덕트",
"evidence": [
{
"field": "kept_chars",
"agg": null,
"value": 25
},
{
"field": "agent_chars",
"agg": null,
"value": 25
}
],
"summary": "한 글자도 고치지 않아 배울 것이 없습니다.",
"hedge": null
},
{
"verdict_no": "RC-003",
"draft_no": "DR-003",
"session_no": "SS-102",
"field_raw": "예약 안내",
"policy_no": "EP-03",
"collect_code": "collect",
"edit_ratio": 0.214,
"min_edit_ratio": 0.15,
"owner_name": "운영프로덕트",
"evidence": [
{
"field": "kept_chars",
"agg": null,
"value": 22
},
{
"field": "min_edit_ratio",
"agg": null,
"value": 0.15
}
],
"summary": "이름표 문구를 늘려 고쳐 써서 기준을 넘겨 후보로 모읍니다.",
"hedge": "임계 바로 위라 이 항목은 손으로 확인하는 목록에 함께 담습니다."
},
{
"verdict_no": "RC-004",
"draft_no": "DR-004",
"session_no": "SS-102",
"field_raw": "초진 요약",
"policy_no": "EP-01",
"collect_code": "final_missing",
"edit_ratio": null,
"min_edit_ratio": 0.2,
"owner_name": "임상프로덕트",
"evidence": [
{
"field": "final_text",
"agg": null,
"value": null
},
{
"field": "edited_at",
"agg": null,
"value": null
}
],
"summary": "고친 글이 없어 그대로 썼는지 버렸는지 알 수 없습니다.",
"hedge": null
},
{
"verdict_no": "RC-005",
"draft_no": "DR-005",
"session_no": "SS-103",
"field_raw": "환자 연락처 정리",
"policy_no": "EP-04",
"collect_code": "field_excluded",
"edit_ratio": null,
"min_edit_ratio": 0,
"owner_name": "보안",
"evidence": [
{
"field": "collect_flag",
"agg": null,
"value": false
},
{
"field": "exclude_reason",
"agg": null,
"value": "개인정보가 그대로 들어갑니다"
}
],
"summary": "연락처가 본문에 들어가는 항목이라 모으지 않습니다.",
"hedge": null
},
{
"verdict_no": "RC-006",
"draft_no": "DR-006",
"session_no": "SS-103",
"field_raw": "초진 요약",
"policy_no": "EP-01",
"collect_code": "collect",
"edit_ratio": 0.581,
"min_edit_ratio": 0.2,
"owner_name": "임상프로덕트",
"evidence": [
{
"field": "kept_chars",
"agg": null,
"value": 13
},
{
"field": "final_chars",
"agg": null,
"value": 31
}
],
"summary": "기간과 상황이 크게 바뀌어 고쳐 써서 후보로 모읍니다.",
"hedge": null
},
{
"verdict_no": "RC-007",
"draft_no": "DR-007",
"session_no": "SS-104",
"field_raw": "생활 지도",
"policy_no": "EP-02",
"collect_code": "edit_too_small",
"edit_ratio": 0.048,
"min_edit_ratio": 0.2,
"owner_name": "임상프로덕트",
"evidence": [
{
"field": "kept_chars",
"agg": null,
"value": 20
},
{
"field": "agent_chars",
"agg": null,
"value": 21
}
],
"summary": "숫자 한 곳만 바꾼 정도라 기준에 못 미쳐 모으지 않습니다.",
"hedge": null
},
{
"verdict_no": "RC-008",
"draft_no": "DR-008",
"session_no": "SS-104",
"field_raw": "내부 메모",
"policy_no": "EP-05",
"collect_code": "collect",
"edit_ratio": 0.407,
"min_edit_ratio": 0.25,
"owner_name": "임상프로덕트",
"evidence": [
{
"field": "kept_chars",
"agg": null,
"value": 16
},
{
"field": "final_chars",
"agg": null,
"value": 27
}
],
"summary": "확인할 항목을 늘려 고쳐 써서 후보로 모읍니다.",
"hedge": null
}
][자동 검증 · 코드가 판정합니다] 1. collect_code 가 enum 여섯 값 중 하나인지 확인합니다. 2. verdict_no·draft_no·session_no·field_raw·collect_code·evidence·summary 가 모두 채워졌는지 확인합니다. 3. draft_no 가 초안 목록에 실재하는 번호인지 대조합니다. 4. policy_no 가 있으면 기준표에 실재하는 행 번호인지 대조합니다. 5. policy_no 가 있으면 min_edit_ratio·owner_name 이 그 기준 행의 값과 같은지 대조합니다. 6. field_raw 를 띄어쓰기를 접어 기준표와 맞추고, 맞는 행이 없으면 collect_code 가 field_undeclared 인지 확인합니다. 7. edit_ratio 를 agent_chars 와 final_chars 중 큰 값에서 kept_chars 를 뺀 뒤 그 큰 값으로 나눠 다시 계산합니다. 8. final_text 가 비어 있으면 collect_code 가 final_missing 인지 확인합니다. 9. collect_flag 가 거짓이면 collect_code 가 field_excluded 인지 확인합니다. 10. edit_ratio 가 0 이면 collect_code 가 unchanged 인지 다시 확인합니다. 11. edit_ratio 가 min_edit_ratio 에 못 미치면 collect_code 가 edit_too_small 인지 다시 계산합니다. 12. collect_code 가 collect 이면 edit_ratio 가 min_edit_ratio 이상이고 collect_flag 가 참인지 다시 확인합니다. 13. evidence 의 field 이름이 초안 목록과 기준표에 실재하는 컬럼인지 대조합니다. 14. agg 가 없는 evidence 의 value 가 그 컬럼에 수록된 행 값과 같은지 확인합니다. 15. summary 가 존댓말로 끝나는지 확인합니다. [평가 러너 · 2주차] 직접 만든 에이전트로 남긴 초안 300건에 손으로 붙인 정답 수집 여부와 대조해 일치율과 사유 코드별 분포를 내고, 미선언이 몰리는 항목을 목록으로 냅니다. [수동 검증 · 사람이 봅니다] 1. 기준에 없는 항목을 모을지 담당 조직이 정합니다. 2. 모은 후보를 학습에 넣을지 사람이 한 번 고릅니다.