파편에서 구조로: My GPT를 사용한 Asana 액션 항목 추출의 신뢰성 확보
요약
회의록에서 실행 가능한 작업 항목을 추출하고 Asana와 같은 도구에 전송하는 과정은 Custom GPT만으로는 신뢰성 확보가 어렵다는 문제점을 지적합니다. 단순히 프롬프트나 예시를 개선하는 것보다, 엄격한 시스템 메시지와 Python 스크립트를 결합하여 데이터 처리의 안정성을 높이는 것이 핵심 해결책임을 제시합니다.
핵심 포인트
- Custom GPT만으로는 JSON 형식이나 책임자 식별 등에서 일관성 확보가 어려움.
- 프롬프트 지침을 커스텀 GPT의 시스템 메시지에 배치하는 것이 중요함.
- LLM 출력을 최종적으로 검증하고 구조화하기 위해 Python 스크립트 사용이 필수적임.
회의록. 우리 모두 작성합니다. 우리는 그 자유 형식의 낙서, 글머리 기호, 그리고 주제 이탈들을 실행 가능한 작업(actionable tasks)으로 바꾸는 것을 정말 싫어합니다. 수년 동안 저는 액션 아이템과 책임자가 제 Asana 프로젝트에 마법처럼 나타나는 세상을 꿈꿔왔습니다. 그래서 당연하게도 Custom GPT가 등장했을 때, 저는 '이거다! 나의 구원이다!'라고 생각했습니다.
스포일러를 하자면: 그렇게 간단하지 않았습니다. 처음에는 말입니다.
꿈과 지저분한 현실
제 초기 아이디어는 명확했습니다. 커스텀 GPT에 회의록을 입력하고, 그것이 액션 아이템들을 뱉어내게 한 다음, 그 내용을 Asana로 전송하는 것이었습니다. 저는 GPT-4 Turbo를 사용하며 낙관적이었고 소중한 수동 데이터 입력 시간을 되찾을 준비가 되어 있었습니다. 제가 GPT에게 내린 지침은 처음에는 상당히 포괄적이었습니다: '액션 아이템과 각 항목에 대한 책임자(person or team)를 식별하고, 이를 JSON 배열로 형식화하세요.' 합리적으로 들렸죠?
아, 내가 얼마나 순진했는지요.
결과는, 완곡하게 표현하자면, 엄청나게 일관성이 없었습니다. 한순간에는 {"task": "Q3 보고서 후속 조치", "who": "Sarah (Sales)"}, 다음 순간에는 {"action": "Q3 수치 확인", "owner": "Sales Team"}이 될 것이었습니다. 때로는 전체 회의를 요약하는 데 그쳐, 제가 요청한 _액션_을 완전히 무시했습니다. 또 다른 때는 노트에 언급조차되지 않은 소유자나 팀을 지어내기도 했습니다. 마치 '해머(whack-a-mole)' 게임 같았는데, 모든 두더지들이 다른 JSON 스키마 위반이나 환각된 세부 사항이었습니다. 저는 프롬프트를 다듬는 데만 일주일 동안 꼬박 10시간을 보냈고, 마법이 전적으로 지침 조정(instruction tuning)에 달려 있다고 생각했습니다.
저는 시도해봤습니다:
- 더 구체적인 프롬프트 언어: “항상 JSON 형식으로 출력하세요. 키는 action_description, responsible_team, responsible_person이어야 합니다. 사람이 언급되지 않으면 responsible_person은 null로 사용하세요.” 이보다 나아졌지만 여전히 불안정했습니다. 때로는 책임 팀(responsible_team)이 “Marketing”이었음에도 불구하고 responsible_person이 “The Marketing Team”으로 나오는 등 중복되거나, 잘못되었거나, 그냥 이상한 경우가 있었습니다.
- 예시 제공: 가상의 메모를 기반으로 완벽한 JSON 예시 2~3개를 제공했습니다. 이것은 어느 정도 도움이 되었지만, GPT가 구조(structure)는 따르면서도 내용(content)을 망치거나 너무 많이 추론하는 경향이 있는 것처럼 느껴질 때가 많았습니다.
- 위협과 설득: “이것은 매우 중요합니다. 벗어나지 마세요.” (네, 실제로 그렇게 시도해봤습니다. 하지만 효과가 없었습니다. 알고 보니 LLM은 감정적인 협박에 잘 반응하지 않습니다.)
가장 큰 문제는 신뢰성이었습니다. 저는 단순히 일반적으로 정확한 출력물이 아니라 일관된 출력이 필요했습니다. 일관되지 않은 데이터를 Asana로 밀어 넣는 것은 단지 다른 종류의 혼란을 만들고 있을 뿐이었습니다.
실제로 해결책이 된 것: 시스템 메시지 + 작은 Python 스크립트
전환점은 제가 커스텀 GPT를 전체 해결책으로 보는 것을 멈추고, 강력하지만 결함 있는 _첫 번째 초안(first pass)_으로 보기 시작했을 때 찾아왔습니다. 실제 해결책에는 두 가지 핵심 구성 요소가 함께 작동하는 것이 포함되었습니다:
- JSON 스키마를 강제하는 매우 엄격한 시스템 메시지(단순 사용자 프롬프트가 아님). 저는 상세한 지침의 상당 부분을 커스텀 GPT의 시스템 메시지(또는 API를 직접 사용하는 경우, 시스템 역할)로 옮겼습니다. 이는 모델의 기본적인 작동 지침을 설정하기 때문에 매우 중요합니다. 현재 제 시스템 메시지는 다음과 같은 내용을 포함하고 있습니다:
You are an expert assistant for extracting structured action items from free-form meeting notes.
Your sole output must be a JSON array of objects, where each object represents an action item.
Each object MUST have the following keys:
- task_description: (string) A concise description of the task.
- responsible_team: (string) The name of the team responsible. If no team is explicitly mentioned, infer from context but prefer general terms (e.g., "Engineering", "Sales", "Marketing", "Product").
- responsible_person: (string | null) The full name of the individual responsible. If no specific person is mentioned, set this to null.
Strictly adhere to this JSON structure. Do NOT include any conversational text before or after the JSON. If no action items are found, return an empty array [].
이것과 특정 회의록을 처리하도록 하는 사용자 레벨 지침을 결합하자 훨씬 더 가까워지기 시작했습니다.
- 유효성 검사 및 Asana 푸시를 위한 작고 의견이 담긴 Python 스크립트. 이것이 진정한 게임 체인저였습니다. 저는 다음과 같은 몇 가지 중요한 작업을 수행하는 스크립트를 작성했습니다:
- JSON 스키마 유효성 검사(JSON Schema Validation): GPT의 출력을 받아
jsonschema를 사용하여 해당 출력이 예상 구조에 실제로 부합하는지 확인합니다. 만약 그렇지 않다면 즉시 플래그를 지정합니다. - 담당자 매핑(Responsible Party Mapping): Asana는 임의의 팀 이름이나 사람 이름을 사용하는 것이 아니라 ID가 필요합니다. 제 스크립트는 일반적인 팀 이름("Marketing" 등)을 Asana 프로젝트 ID 또는 사용자 정의 필드 값에 매핑하는 딕셔너리를 가지고 있으며, 더 중요하게는
person_name("Jane Doe" 등)을 Asana 담당자 사용자 ID(예: 1234567890)에 매핑합니다. 만약responsible_person에 대한 GPT 출력이 제 조회 목록의 어떤 항목과도 일치하지 않는다면, 스크립트는 이를 플래그 지정하거나 유사성 검색(fuzzy-match)을 시도합니다. - Asana API 통합(Asana API Integration): 검증 및 매핑이 완료되면, 스크립트는
asanaPython 클라이언트를 사용하여 작업을 생성하고, 할당하며, 올바른 프로젝트에 추가합니다. 이는 특정 담당자가 없는 작업과 같은 엣지 케이스를 처리합니다 (예: 팀의 프로젝트 리드에게 할당).
다음은 유효성 검사 부분이 어떻게 보이는지에 대한 간소화된 코드 스니펫입니다:
import json
from jsonschema import validate, ValidationError
# ... (GPT 출력 가져오기)
gpt_output_json = json.loads(gpt_raw_output)
schema = {
"type": "array",
"items": {
"type": "object",
"properties": {
"task_description": {"type": "string"},
"responsible_team": {"type": "string"},
"responsible_person": {"type": ["string", "null"]}
},
"required": ["task_description", "responsible_team", "responsible_person"]
}
}
try:
validate(instance=gpt_output_json, schema=schema)
print("GPT 출력 유효함!")
# 매핑 및 Asana 전송 진행
except ValidationError as e:
print(f"유효성 검사 오류: {e.message}")
# 오류 기록, 알림 전송 또는 수동 검토 트리거
이 두 가지 접근 방식은 모든 것을 변화시켰습니다. 커스텀 GPT는 이제 매우 구조화된 JSON을 제공하고, 제 Python 스크립트는 강력한 안전망 역할을 하여, 어떠한 편차도 포착하고, 데이터를 정규화하며, Asana API의 세부 사항까지 처리합니다. 이제 저는 약 95% 정확도의 추출 결과를 얻고 있으며, 이는 Asana로 원활하게 흐릅니다. 이 덕분에 예전에 메모를 작업으로 변환하는 데 사용하던 시간 중 주당 약 두 시간을 절약했습니다. 나머지 5%는 스크립트가 플래그를 지정하는 몇 가지 항목에 대한 간단한 수동 수정만 필요합니다.
단순히 프롬프트 엔지니어링(prompt engineering)의 문제가 아니었습니다. AI 주변에 신뢰할 수 있는 시스템을 구축하는 것이 핵심이었습니다. GPT는 자연어 파싱(parsing natural language)에 있어 믿을 수 없을 만큼 강력하지만, 검증과 통합을 위한 약간의 결정론적 코드(deterministic code)가 더해지자 진정한 프로덕션 레디(production-ready) 상태가 되었습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기