feat: add domain agent AI call functions

- Add domain expert system prompt
- Add domain tools for function calling
- Add callDomainExpertAI function

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
kappa
2026-02-05 09:52:24 +09:00
parent e463a88803
commit 69aac96daa

View File

@@ -188,6 +188,281 @@ export function addMessageToSession(
} }
} }
// Domain Expert System Prompt
const DOMAIN_EXPERT_PROMPT = `당신은 10년 경력의 도메인 컨설턴트입니다.
전문 분야:
- 브랜딩에 적합한 도메인 선택 조언
- SEO 관점의 도메인 추천
- 가격 대비 가치 분석
- TLD 선택 가이드 (.com, .net, .io, .kr 등)
행동 지침:
1. 불필요한 프리미엄 도메인 추천 자제
2. 실용적이고 합리적인 선택 유도
3. 사용자의 예산과 용도를 먼저 파악
4. 명확하지 않은 요청은 질문으로 확인
응답 형식:
- 짧고 명확하게 답변
- 불필요한 인사말이나 서론 없이 바로 본론
- 가격 정보는 항상 원화(₩)로 표시
특수 지시:
- 도메인과 무관한 메시지가 들어오면 반드시 "__PASSTHROUGH__"만 응답
- 세션 종료가 필요하면 "__SESSION_END__"를 응답 끝에 추가`;
// Domain Tools for Function Calling
const DOMAIN_TOOLS = [
{
type: 'function' as const,
function: {
name: 'check_domain',
description: '도메인 가용성 및 가격 확인',
parameters: {
type: 'object',
properties: {
domain: { type: 'string', description: '확인할 도메인 (예: example.com)' }
},
required: ['domain']
}
}
},
{
type: 'function' as const,
function: {
name: 'search_suggestions',
description: '키워드 기반 도메인 추천 검색',
parameters: {
type: 'object',
properties: {
keywords: { type: 'string', description: '도메인 추천을 위한 키워드' }
},
required: ['keywords']
}
}
},
{
type: 'function' as const,
function: {
name: 'get_whois',
description: '도메인 WHOIS 정보 조회',
parameters: {
type: 'object',
properties: {
domain: { type: 'string', description: 'WHOIS 조회할 도메인' }
},
required: ['domain']
}
}
},
{
type: 'function' as const,
function: {
name: 'get_price',
description: 'TLD별 도메인 가격 조회',
parameters: {
type: 'object',
properties: {
tld: { type: 'string', description: '가격 확인할 TLD (예: com, net, io)' }
},
required: ['tld']
}
}
},
{
type: 'function' as const,
function: {
name: 'register_domain',
description: '도메인 등록 요청 (잔액 확인 후 결제 진행)',
parameters: {
type: 'object',
properties: {
domain: { type: 'string', description: '등록할 도메인' }
},
required: ['domain']
}
}
},
{
type: 'function' as const,
function: {
name: 'set_nameservers',
description: '도메인 네임서버 변경',
parameters: {
type: 'object',
properties: {
domain: { type: 'string', description: '변경할 도메인' },
nameservers: {
type: 'array',
items: { type: 'string' },
description: '설정할 네임서버 목록'
}
},
required: ['domain', 'nameservers']
}
}
}
];
// OpenAI API response types
interface OpenAIToolCall {
id: string;
type: 'function';
function: {
name: string;
arguments: string;
};
}
interface OpenAIMessage {
role: 'assistant';
content: string | null;
tool_calls?: OpenAIToolCall[];
}
interface OpenAIAPIResponse {
choices: Array<{
message: OpenAIMessage;
finish_reason: string;
}>;
}
/**
* Domain Expert AI 호출 (Function Calling 지원)
*
* @param session - DomainSession
* @param userMessage - 사용자 메시지
* @param env - Environment
* @returns AI 응답 및 tool_calls (있을 경우)
*/
async function callDomainExpertAI(
session: DomainSession,
userMessage: string,
env: Env
): Promise<{ response: string; toolCalls?: Array<{ name: string; arguments: Record<string, unknown> }> }> {
if (!env.OPENAI_API_KEY) {
throw new Error('OPENAI_API_KEY not configured');
}
const { getOpenAIUrl } = await import('../utils/api-urls');
// Build conversation history
const conversationHistory = session.messages.map(m => ({
role: m.role === 'user' ? 'user' as const : 'assistant' as const,
content: m.content,
}));
const systemPrompt = `${DOMAIN_EXPERT_PROMPT}
## 현재 수집된 정보
${JSON.stringify(session.collected_info, null, 2)}
## 대화 흐름
1. 키워드 파악: "어떤 서비스 이름이나 키워드로 찾으시나요?"
2. 용도 파악: "어떤 용도로 사용하실 건가요? (예: 쇼핑몰, 블로그, 회사 홈페이지)"
3. 예산 확인 (선택): "예산 범위가 있으신가요?"
4. 정보가 충분하면 search_suggestions 도구로 추천
## 도구 사용 가이드
- 키워드가 파악되면 즉시 search_suggestions 호출
- 특정 도메인 확인 요청 → check_domain
- WHOIS 정보 요청 → get_whois
- TLD 가격 문의 → get_price
- 등록 요청 → register_domain
- 네임서버 변경 → set_nameservers`;
try {
const messages: Array<{
role: string;
content: string | null;
tool_calls?: OpenAIToolCall[];
tool_call_id?: string;
name?: string
}> = [
{ role: 'system', content: systemPrompt },
...conversationHistory,
{ role: 'user', content: userMessage },
];
const MAX_TOOL_CALLS = 3;
let toolCallCount = 0;
// Loop to handle tool calls
while (toolCallCount < MAX_TOOL_CALLS) {
const requestBody = {
model: 'gpt-4o-mini',
messages,
tools: DOMAIN_TOOLS,
tool_choice: 'auto',
max_tokens: 800,
temperature: 0.7,
};
const response = await fetch(getOpenAIUrl(env), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${env.OPENAI_API_KEY}`,
},
body: JSON.stringify(requestBody),
});
if (!response.ok) {
const error = await response.text();
throw new Error(`OpenAI API error: ${response.status} - ${error}`);
}
const data = await response.json() as OpenAIAPIResponse;
const assistantMessage = data.choices[0].message;
// Check if AI wants to call tools
if (assistantMessage.tool_calls && assistantMessage.tool_calls.length > 0) {
logger.info('도구 호출 요청', {
tools: assistantMessage.tool_calls.map(tc => tc.function.name),
});
// Return tool calls to be executed by caller
const toolCalls = assistantMessage.tool_calls.map(tc => ({
name: tc.function.name,
arguments: JSON.parse(tc.function.arguments) as Record<string, unknown>,
}));
return {
response: assistantMessage.content || '',
toolCalls,
};
}
// No tool calls - return final response
const aiResponse = assistantMessage.content || '';
logger.info('AI 응답', { response: aiResponse.slice(0, 200) });
// Check for special markers
if (aiResponse.includes('__PASSTHROUGH__')) {
return { response: '__PASSTHROUGH__' };
}
// Check for session end marker
const sessionEnd = aiResponse.includes('__SESSION_END__');
const cleanResponse = aiResponse.replace('__SESSION_END__', '').trim();
return {
response: sessionEnd ? `${cleanResponse}\n\n[세션 종료]` : cleanResponse,
};
}
// Max tool calls reached
logger.warn('최대 도구 호출 횟수 도달', { toolCallCount });
return {
response: '분석이 완료되었습니다. 추천을 진행하겠습니다.',
};
} catch (error) {
logger.error('Domain Expert AI 호출 실패', error as Error);
throw error;
}
}
/** /**
* 도메인 추천 상담 처리 (메인 함수) * 도메인 추천 상담 처리 (메인 함수)
* *