업스테이지
인턴·신입근거: 사용자가 AI와 상호작용하는 모든 접점을 개선하고 신규 기능을 직접 설계 및 구현합니다.
※ 실제 산출물이 아닌 AI 예측 시안입니다.
업스테이지는 사용자가 AI 와 상호작용하는 모든 접점을 개선하는 일을 이 자리의 주요업무로 적었습니다. 접점이 여럿이라 접점마다 사용자가 쓰는 말이 달라, 기준선을 하나로 두지 않고 접점별로 계산하는 이 구조가 성립합니다. 접점이 하나뿐인 서비스에서는 접점별로 갈라 볼 것이 없습니다. 다만 이 포트폴리오는 다시 시도 판정 규칙과 화면을 보여줄 뿐, 사용자 조사 방법론의 전문성은 주장하지 않습니다.
사용자가 AI 와 상호작용하는 모든 접점을 개선하고 신규 기능을 직접 설계하고 구현하는 자리라, 어느 접점이 막히는지 아는 것이 그 일의 출발점입니다.
모델이 아니라 사용자가 만나는 화면을 만드는 자리라, 화면을 고칠 근거를 화면으로 내는 것이 자연스럽습니다.
모든 접점이라는 말이 붙어 있어 접점이 여럿이라는 뜻이고, 접점마다 사용자가 쓰는 말이 달라 기준을 하나로 둘 수 없습니다.
사용자가 접점마다 보낸 말을 서로 견줘, 한 번에 끝난 요청과 다시 시도한 요청을 접점별로 나눠 보여주는 관리자 페이지입니다.
업스테이지 AI 서비스 제품 담당자입니다. 접점을 고치고 싶은데 어디서 사용자가 막히는지 감으로 짐작하다가, 다시 시도가 몰린 접점부터 손대고 싶습니다.
어느 접점을 고칠지 못 정하는 이유는 사용자가 막힌 자리가 로그에 표시로 남지 않아서입니다. 한 세션 안에서 사용자가 보낸 말끼리 겹침을 그 접점의 평소 겹침과 견주면, 다시 시도한 자리가 드러납니다.
상호작용 기록과 접점 목록을 올리면 이벤트 14건이 네 판정으로 나뉘어 접점별로 묶이고, 한 줄을 펼치면 어느 말과 얼마나 겹쳤는지 보이는 관리자 페이지입니다. 기록은 지원자가 자기 서비스에 남긴 입력 로그로 시작합니다.
제품 담당자가 실제 재시도라고 표시한 기록을 추가로 올려, 판정과 어긋난 이벤트를 판정별로 세어 보여줍니다.
접점마다 기준선을 올리고 내리면 다시 시도로 잡히는 건수가 즉시 다시 계산되고, 바꾸기 전후 건수가 나란히 뜹니다.
상호작용 기록과 접점 목록을 올리면 이벤트 14건이 네 판정으로 나뉘어 접점별로 묶이고, 담당자가 한 줄을 펼쳐 겹친 말과 기준선을 함께 읽습니다.
결과 기록이 비었거나 겹친 정도가 접점 기준선과 정확히 같거나 접점 표기가 흔들린 이벤트를 섞은 입력 30종을 넣어도 출력 형식이 안 깨집니다.
접점 표기를 정리한 뒤 접점 목록에 붙는 이벤트 비율을 화면에 냅니다. 단, 정답과 데이터를 모두 작성자가 만든 합성 기준이라 성능 검증이 아니라 데모 조건에서의 동작입니다.
위 수치는 더미데이터 자가채점이며, 실제 재시도 판정의 정확도는 업스테이지 운영 로그와 담당자가 표시한 재시도가 있어야 측정됩니다. 이 포트폴리오는 그 검증을 바로 돌릴 수 있는 상태까지 만듭니다.
수치는 합성 데이터 자가채점이라 성능 증명이 아닙니다. 면접에서는 데모가 도는 상태와 실검증 준비 상태로 솔직히 설명합니다.
정답을 작성자가 만들었으니 잘 맞는 것이 당연합니다. 그래서 결과 기록 결측, 기준선과 정확히 같은 경계, 접점 표기 흔들림을 기록에 일부러 섞었습니다.
다시 보냈다고 앞의 답이 틀린 것은 아닙니다. 사용자가 생각을 바꿔 더 요구하는 경우가 있으므로, 판정은 담당자가 먼저 열어 볼 접점을 정하는 데까지입니다.
낱말을 공백으로 가르는 방식은 한국어 조사와 띄어쓰기 흔들림에 약합니다. 실제 도입에서는 형태소 단위로 가르고 기준선을 다시 잡는 일이 도입 단계 과제입니다.
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
사용자가 접점마다 남긴 입력 기록입니다. 결과 기록 결측, 겹친 정도가 접점 기준선과 정확히 같은 경계, 접점 표기 흔들림을 일부러 섞었습니다. surface_key 는 도구가 surface_key_raw 를 정리해 채우는 칸이라 비워 두었습니다. 지원자가 자기 서비스에 입력 로그를 남기면 같은 모양이 나옵니다.
| event_id | session_id | surface_key_raw | input_text | occurred_at | result_shown |
|---|---|---|---|---|---|
| EV-0101 | a41c | summarize | 이거 요약해줘 | 2026-08-31T01:02:00Z | true |
| EV-0102 | a41c | summarize | 이거 요약해줘! | 2026-08-31T01:02:40Z | true |
| EV-0201 | b70d | summarize | 짧게 요약해줘 | 2026-08-31T02:11:00Z | true |
| EV-0202 | b70d | summarize | 더 짧게 요약해줘 | 2026-08-31T02:11:50Z | true |
| EV-0301 | c19f | upload | 계약서 올림 | 2026-08-31T03:20:00Z | true |
다음 JSON Schema 에 맞는 상호작용 기록 행을 더 만듭니다. {"type":"object","properties":{"event_id":{"type":"string"},"session_id":{"type":"string"},"surface_key_raw":{"type":"string"},"surface_key":{"type":["string","null"]},"input_text":{"type":"string"},"occurred_at":{"type":"string"},"result_shown":{"type":["boolean","null"]}},"required":["event_id","session_id","surface_key_raw","input_text","occurred_at"]} 쉬운 데이터만 만들지 말 것. 다음 하드 케이스를 의도적으로 섞어라. 결과가 떴는지 기록하지 못해 result_shown 이 null 인 행 1~2건, 앞선 말과 겹치는 정도가 그 접점의 평소 겹침과 정확히 같아지는 경계 행 1건, surface_key_raw 에 대문자나 앞뒤 공백이 섞여 정리가 필요한 행 1~2건. 같은 접점에서는 세션이 달라도 흔히 쓰는 말이 겹치게 만들어 기준선이 0 이 되지 않게 한다. 한 세션이 접점 셋 이상을 오가는 경우도 하나 만든다. 그리고 occurred_at 을 비우거나 result_shown 에 문자열을 넣는 형식 깨뜨리기 시도 3건을 따로 낸다. event_id 는 행마다 다른 값으로 쓴다.{"type":"object","properties":{"event_id":{"type":"string"},"session_id":{"type":"string"},"surface_key_raw":{"type":"string"},"surface_key":{"type":["string","null"]},"input_text":{"type":"string"},"occurred_at":{"type":"string"},"result_shown":{"type":["boolean","null"]}},"required":["event_id","session_id","surface_key_raw","input_text","occurred_at"]}서비스가 가진 접점 목록입니다. 판정 결과를 접점 이름으로 묶어 보여줄 때 쓰고, 정리한 접점 표기가 여기 없으면 목록에 없는 접점으로 걸립니다.
| surface_key | surface_name | input_kind | released_at |
|---|---|---|---|
| summarize | 결과 요약 | text | 2026-05-12 |
| upload | 문서 업로드 | file | 2026-03-04 |
| translate | 번역 | text | 2026-06-20 |
| extract | 표 추출 | file | 2026-04-18 |
| chat | 질문하기 | text | 2026-07-01 |
다음 JSON Schema 에 맞는 접점 행을 더 만듭니다. {"type":"object","properties":{"surface_key":{"type":"string"},"surface_name":{"type":"string"},"input_kind":{"type":"string","enum":["text","file","action"]},"released_at":{"type":"string"}},"required":["surface_key","surface_name","input_kind","released_at"]} 쉬운 데이터만 만들지 말 것. 접점마다 입력 종류를 갈라 둔다. 사용자가 문장을 넣는 접점은 text, 파일을 올리는 접점은 file, 버튼 한 번으로 끝나는 접점은 action 으로 둔다. 입력 종류가 갈려야 접점마다 사용자가 쓰는 말의 겹침도 달라진다. 그리고 released_at 에 날짜가 아닌 값을 넣거나 surface_key 를 비우는 형식 깨뜨리기 시도 3건을 따로 낸다.{"type":"object","properties":{"surface_key":{"type":"string"},"surface_name":{"type":"string"},"input_kind":{"type":"string","enum":["text","file","action"]},"released_at":{"type":"string"}},"required":["surface_key","surface_name","input_kind","released_at"]}| TypeScript | 판정 규칙과 화면을 한 언어로 쓰고, 판정 결과 형식을 타입으로 고정합니다. |
| Python | 상호작용 기록에서 낱말을 갈라 접점별 기준선을 계산합니다. |
| React | 이벤트를 판정별로 묶어 그리고, 한 줄을 펼치면 겹친 말과 기준선을 보여줍니다. |
| 데이터 시각화 | 접점별로 다시 시도가 얼마나 몰렸는지 나란히 놓아 어느 화면부터 고칠지 보여줍니다. |
| JSON Schema | 상호작용 기록과 접점 목록의 모양을 고정해, 열이 빠진 파일을 올렸을 때 화면이 아니라 입력이 문제라고 알려 줍니다. |
| Claude Sonnet | 다시 시도한 자리에서 사용자가 무엇을 더 요구했는지 담당자가 읽을 한 줄로 줄여 적습니다. |
✓ pass → 여기서 멈춰도 완결입니다. 더 키우려면 담당자가 표시한 재시도를 받아 판정이 맞았는지 대조합니다.
✓ pass → 여기서 멈춰도 완결입니다. 더 키우려면 접점별 기준선을 화면에서 바꿔 건수가 어떻게 달라지는지 비교합니다.
위 예측 UI 는 3주차까지 완주했을 때의 화면입니다. 1주차에 실제로 만드는 것은 아래 표시된 화면 하나입니다.
verdict사유 라벨 · event_id + 한 줄 요약(근거 수치 포함)화면·코드·면접에서 이 단어를 그대로 씁니다. 키는 출력 스키마의 enum 과 같습니다.
one_shot한 번에 끝남exact_retry같은 말로 다시 시도reworded_retry말 바꿔 다시 시도surface_hop여러 접점 돌아다님이벤트 한 줄마다 어떤 판정이 나왔고 어느 말과 얼마나 겹쳤는지 담습니다.
{"type":"object","properties":{"event_id":{"type":"string"},"session_id":{"type":"string"},"surface_key":{"type":"string"},"verdict":{"type":"string","enum":["one_shot","exact_retry","reworded_retry","surface_hop"]},"surface_baseline":{"type":"number","description":"그 surface_key 에서 서로 다른 session_id 끼리 input_text 낱말이 겹치는 정도를 모두 구해 얻은 가운데값입니다"},"max_overlap":{"type":["number","null"],"description":"같은 session_id 와 surface_key 안 앞선 input_text 와 낱말이 겹치는 정도 중 가장 큰 값입니다"},"matched_event_id":{"type":["string","null"]},"reason":{"type":"string"},"evidence":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"agg":{"type":"string"},"value":{}},"required":["field","value"]}}},"required":["event_id","session_id","surface_key","verdict","surface_baseline","max_overlap","evidence"]}[
{
"event_id": "EV-0101",
"session_id": "a41c",
"surface_key": "summarize",
"verdict": "one_shot",
"surface_baseline": 0.33,
"max_overlap": null,
"matched_event_id": null,
"reason": "세션에서 처음 보낸 말이라 앞서 견줄 것이 없고, 결과를 받고 끝났습니다.",
"evidence": [
{
"field": "input_text",
"value": "이거 요약해줘"
},
{
"field": "surface_key_raw",
"value": "summarize"
}
]
},
{
"event_id": "EV-0102",
"session_id": "a41c",
"surface_key": "summarize",
"verdict": "exact_retry",
"surface_baseline": 0.33,
"max_overlap": 1,
"matched_event_id": "EV-0101",
"reason": "느낌표만 붙었을 뿐 공백과 문장부호를 지우면 앞서 보낸 말과 같아, 같은 말을 다시 보낸 것으로 봅니다.",
"evidence": [
{
"field": "input_text",
"value": "이거 요약해줘!"
},
{
"field": "surface_key_raw",
"value": "summarize"
},
{
"field": "input_text",
"value": "이거 요약해줘"
}
]
},
{
"event_id": "EV-0202",
"session_id": "b70d",
"surface_key": "summarize",
"verdict": "reworded_retry",
"surface_baseline": 0.33,
"max_overlap": 0.67,
"matched_event_id": "EV-0201",
"reason": "앞서 보낸 말과 낱말이 겹치는 정도가 이 접점의 평소 겹침을 넘어, 말을 바꿔 다시 시도한 것으로 봅니다.",
"evidence": [
{
"field": "input_text",
"value": "더 짧게 요약해줘"
},
{
"field": "surface_key_raw",
"value": "summarize"
},
{
"field": "input_text",
"value": "짧게 요약해줘"
}
]
},
{
"event_id": "EV-0303",
"session_id": "c19f",
"surface_key": "translate",
"verdict": "surface_hop",
"surface_baseline": 0.25,
"max_overlap": null,
"matched_event_id": "EV-0301",
"reason": "한 세션에서 업로드와 요약과 번역 세 접점을 오갔고 같은 문서를 가리키는 말이 이어져, 접점 사이를 돌아다닌 것으로 봅니다.",
"evidence": [
{
"field": "input_text",
"value": "계약서 번역"
},
{
"field": "surface_key_raw",
"value": "translate"
},
{
"field": "input_text",
"value": "계약서 올림"
}
]
},
{
"event_id": "EV-0602",
"session_id": "f45c",
"surface_key": "summarize",
"verdict": "one_shot",
"surface_baseline": 0.33,
"max_overlap": 0.33,
"matched_event_id": "EV-0601",
"reason": "앞서 보낸 말과 겹치는 정도가 이 접점의 평소 겹침과 같아 다시 시도로 보지 않습니다. 평소만큼만 겹친 것이라 새 요청으로 셉니다.",
"evidence": [
{
"field": "input_text",
"value": "요약해줘 다시"
},
{
"field": "surface_key_raw",
"value": "summarize"
},
{
"field": "input_text",
"value": "보고서 요약해줘"
}
]
},
{
"event_id": "EV-0701",
"session_id": "g88e",
"surface_key": "chat",
"verdict": "one_shot",
"surface_baseline": 0,
"max_overlap": null,
"matched_event_id": null,
"reason": "결과가 떴는지 기록이 비어 있지만 다시 시도 판정에는 쓰이지 않아, 앞서 견줄 말이 없는 첫 요청 그대로 봅니다.",
"evidence": [
{
"field": "input_text",
"value": "이 표 뭐야"
},
{
"field": "surface_key_raw",
"value": "chat"
}
]
},
{
"event_id": "EV-0802",
"session_id": "h20a",
"surface_key": "summarize",
"verdict": "reworded_retry",
"surface_baseline": 0.33,
"max_overlap": 0.67,
"matched_event_id": "EV-0801",
"reason": "접점 표기에 붙은 대문자와 공백을 정리해 같은 접점으로 묶었고, 앞서 보낸 말과 겹치는 정도가 평소 겹침을 넘습니다.",
"evidence": [
{
"field": "input_text",
"value": "회의록 요약해줘 다시"
},
{
"field": "surface_key_raw",
"value": "summarize"
},
{
"field": "input_text",
"value": "회의록 요약해줘"
}
]
}
][자동 검증 · 형식과 재계산] 1. verdict 는 one_shot, exact_retry, reworded_retry, surface_hop 네 값 안에 있습니다. 2. event_id, session_id, surface_key, verdict, surface_baseline, max_overlap, evidence 일곱 필드가 모두 있습니다. 3. surface_key 는 surface_key_raw 의 앞뒤 공백을 지우고 소문자로 바꾼 값이고 surface_catalog 에 있습니다. 4. surface_baseline 은 그 접점에서 서로 다른 session_id 끼리 낱말이 겹치는 정도를 모두 구해 얻은 가운데값입니다. 5. max_overlap 은 같은 세션과 접점 안 앞선 말과 겹치는 정도 중 가장 큰 값이고, 앞선 말이 없으면 비어 있습니다. 6. surface_baseline 과 max_overlap 은 0 이상 1 이하입니다. 7. evidence 의 field 값은 interaction_events 또는 surface_catalog 에 실재하는 컬럼명입니다. 8. evidence 의 value 는 두 표의 수록된 행에 있는 값 그대로입니다. 9. verdict 가 exact_retry 이면 공백과 문장부호를 지운 input_text 가 앞선 것과 같습니다. 10. verdict 가 reworded_retry 이면 max_overlap 이 surface_baseline 을 넘습니다. 11. verdict 가 surface_hop 이면 그 세션의 서로 다른 surface_key 가 셋 이상이고 다른 접점의 말과 낱말이 겹칩니다. 12. 모든 한국어 문장은 존댓말로 끝납니다. [정상으로 처리한 하드 케이스] 1. EV-0602 는 앞선 말과 겹치는 정도가 요약 접점의 평소 겹침과 정확히 같아 넘지 않으므로 다시 시도가 아닙니다. 2. EV-0701 은 결과가 떴는지 기록이 비어 있지만 판정에 쓰이지 않아 첫 요청 그대로 남습니다. [평가 러너 · 2주차] 1. 제품 담당자가 표시한 실제 재시도와 대조해 판정별 어긋남 수를 냅니다. 2. 접점별 기준선을 올리고 내려 가며 다시 시도로 잡히는 건수가 어떻게 달라지는지 견줍니다.