이전 글에서 원두 설명기를 실제 카페에서 제공 받는 원두 설명지를 기준으로 제작을 해봤고,
사실과 다른 정보들이 등장해 문제가 있었습니다.
이전 글:
https://devbrew.tistory.com/123
[Next.js] 개인 프로젝트 - AI 원두 설명기 제작 (2)(문제 발생)
이전 글에서 OpenAI 가 원두 제품명만 하드 코딩해도 제법 정보를 설명해주는 것을 확인했습니다 ! 이전 글:https://devbrew.tistory.com/120 [Next.js] 미니 프로젝트 - AI 원두 설명기 제작 (1)저는 카페를 참
devbrew.tistory.com
그래서 다시 생각을 해보니,
이 정보들은 카페든 온라인 원두 판매 페이지든 어차피 제공될 명확한 정보인데,
사용자 입장에서 AI 요약으로 이 정보들이 굳이 필요할까? 라는 의문이 들었습니다.
그래서 고민 끝에 표현할 정보를 다시 선정하기로 했습니다.
2. 표현할 정보 다시 선정하기
카페투어 모임을 다니면서 주변에서 가장 궁금해했던 것은 무엇이었는지 고민했을 때,
결국 원두가 산미가 있는지, 맛과 향은 어떤지, 좋은 원두인지 여부였던 것 같았습니다.
문제는 이를 안내하려면 주관적인 영역이지만 기준이 되는 무언가가 필요했습니다.
우선, 영향력이 큰 원두 가공 방식과 원두의 품종을 기준으로 표현하기로 하고
모델에 이 부분을 평가할 컨텍스트를 우선 추가해보기로 했습니다.
3. 컨텍스트 작성하기
컨텍스트는 사용자가 입력한 텍스트에서 특정 키워드가 있는지 탐지하여,
매핑된 설명을 모델에 제공하고, 보장할 수 있는 정보를 바탕으로 답변을 보완하도록 하는 방식입니다.
이를 위해 별도로 lib 폴더를 생성하고, 각각 컨텍스트를 아래와 같이 추가해주었습니다.
가공 방식
export type ProcessKey = "washed" | "natural" | "honey" | "pulped" | "anaerobic" | "carbonic" | "other";
const KEYWORDS: Record<ProcessKey, string[]> = {
washed: ["워시드", "washed"],
natural: ["내추럴", "natural"],
honey: ["허니", "honey"],
pulped: ["펄프드", "펄프드 내추럴", "pulped natural", "pulped"],
anaerobic:["언에어로빅", "아나에어로빅", "에네로빅", "무산소", "무산소 발효", "anaerobic"],
carbonic: ["카보닉", "카보닉 매서레이션", "cm", "carbonic", "maceration"],
other: ["더블 워시드", "세미 워시드", "experimental", "experimental process"]
};
const PHRASE: Record<ProcessKey, string> = {
washed: "단맛이 적고 산미가 산뜻하며, 바디감이 가볍고 깔끔한 맛을 선사함",
natural: "과일향과 단맛이 강하고, 바디감이 있으며, 다채로운 향을 선사함",
honey: "점액질 제거 정도에 따라 차이가 있고, 부드럽고 깔끔한 단맛과 산미를 선사함",
pulped: "풍부한 단맛과 깔끔한 산미가 조화롭고 균형 잡힌 맛을 선사함",
anaerobic:"풍부한 과일향과 독특하고 강렬한 산미가 복합적이고 와인 같은 향을 선사함",
carbonic: "풍부한 과일향과 독특하고 강렬한 산미가 복합적이고 와인 같은 향을 선사함",
other: "개성 있는 가공 특성이 드러납니다"
};
export function detectProcess(raw: string): { key: ProcessKey; phrase: string; } | null {
const t = (raw || "").toLowerCase();
for (const key of ["carbonic","honey","pulped","anaerobic","washed","natural","other"] as ProcessKey[]) {
if (KEYWORDS[key].some(k => t.includes(k.toLowerCase()))) {
return { key, phrase: PHRASE[key] };
}
}
return null;
}
품종
export type VarietyKey = "geisha" | "pink_bourbon";
const KEYWORDS: Record<VarietyKey, string[]> = {
geisha: ["게이샤", "게샤", "geisha", "gesha"],
pink_bourbon: ["핑크 버번", "핑크버번", "핑크 부르봉", "pink bourbon", "pinkbourbon", "pink borbon"],
};
const PHRASE: Record<VarietyKey, string> = {
geisha: "게이샤(Geisha) 품종 특유의 풍부하고 섬세한 향미가 느껴집니다",
pink_bourbon: "핑크 버번(Pink Bourbon) 품종 특유의 좋은 단맛과 은은한 꽃향이 매력적입니다",
};
export function detectVariety(raw: string): { key: VarietyKey; phrase: string } | null {
const t = (raw || "").toLowerCase();
for (const key of ["geisha", "pink_bourbon"] as VarietyKey[]) {
if (KEYWORDS[key].some(w => t.includes(w.toLowerCase()))) {
return { key, phrase: PHRASE[key] };
}
}
return null;
}
수정하고 추가하고 싶은 부분이 아직 많지만.. 일단 이 정도로 작성을 해봤습니다.
KEYWORDS 에 있는 단어들이 감지되면, 매핑된 위치의 PHRASE 설명을 전달하게 됩니다.
4. 메시지 기반 입력으로 변경
import OpenAI from "openai";
import { detectProcess } from "@/lib/coffee/process";
import { detectVariety } from "@/lib/coffee/variety";
const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
export async function POST(req: Request) {
if (!process.env.OPENAI_API_KEY) {
return Response.json({ error: "OPENAI_API_KEY가 존재하지 않습니다." }, { status: 500 });
}
const model = "gpt-4o-mini";
const { prompt } = await req.json();
const user = String(prompt ?? "").trim();
if (!user) {
return Response.json({ error: "prompt가 비어 있습니다." }, { status: 400 });
}
const proc = detectProcess(user);
const variety = detectVariety(user);
const facts = {
name: user,
process_phrase: proc?.phrase ?? null,
variety_phrase: variety?.phrase ?? null,
};
const response = await client.responses.create({
model,
input: [
{
role:"system",
content: [
"입력 받은 원두 제품명의 정보를 요약해서 출력",
"한국어로 4~5 문장으로 작성",
"process_phrase가 null이면 가공 방식 언급을 생략",
"variety_phrase가 null이면 품종 언급을 생략",
"너가 아는 컵노트를 기반으로 맛의 방향성을 제시",
"언급을 생략하는 과정에서 포함되어 있지 않다는 식의 표현 금지"
].join("\n"),
},
{ role: "user", content: user },
{
role: "user",
content: JSON.stringify({ facts })
},
],
});
// 결과 텍스트 반환
return Response.json({
text: (response.output_text ?? "").trim()
});
}
기존에는 RULE 상수로 규칙을 프롬프트에 붙여 보냈지만,
컨텍스트를 원활하게 사용하기 위해 이를 메시지 기반 입력으로 변경했습니다.
시스템 메시지에 행동 지침을 간단히 적고, 사용자 메세지에는 앞서 작성한 컨텍스트를 넣어,
모델이 보장된 정보 위주로 요약을 생성하도록 했습니다.
결과

커피를 고를 때 참고할 수 있는 정보로는 이전보다 괜찮아졌다고 느꼈습니다.
계속 사용하면서 보완하면 더 안정적이고 많은 정보를 표현할 수 있을 것 같습니다.
컨텍스트를 구성하고 연결하는 방법을 이번에 공부해서 추가해봤는데
실무에도 응용할 수 있는 부분이 많을 것 같아서 너무 기대가 되고 좋았습니다 !
앞으로 어떻게 확장을 해봐야할지 고민해봐야겠습니다.
'자기개발 > 프로젝트' 카테고리의 다른 글
| [토이] 키워드 하나로 초간단 캐릭터 프로필 사진 만들기 (0) | 2025.08.31 |
|---|---|
| [Next.js] 개인 프로젝트 - AI 원두 설명기 제작 (2)(문제 발생) (0) | 2025.08.22 |
| [Next.js] 개인 프로젝트 - AI 원두 설명기 제작 (1) (0) | 2025.08.20 |
| [토이] 내 블로그 CSS 를 수정할 수 있는 가짜 맥 터미널 만들어보기 (0) | 2025.08.11 |
| Prettier와 VSCode 설정으로 코드 스타일 통일하기 (0) | 2025.06.26 |