feat: add OpenAI types and AI caller utility

- Consolidate OpenAI types to types.ts
- Create reusable callOpenAI() function
- Add tool call parsing and result handling

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
kappa
2026-02-05 11:34:15 +09:00
parent c8e1362375
commit ffd310c903
3 changed files with 181 additions and 5 deletions

View File

@@ -466,8 +466,11 @@ export interface BraveSearchResponse {
};
}
// OpenAI API 응답 타입
export interface ToolCall {
// ============================================
// OpenAI API Types (for Function Calling)
// ============================================
export interface OpenAIToolCall {
id: string;
type: 'function';
function: {
@@ -479,19 +482,50 @@ export interface ToolCall {
export interface OpenAIMessage {
role: 'system' | 'user' | 'assistant' | 'tool';
content: string | null;
tool_calls?: ToolCall[];
tool_calls?: OpenAIToolCall[];
tool_call_id?: string;
name?: string; // For tool responses
}
export interface OpenAIChoice {
message: OpenAIMessage;
finish_reason: string;
}
export interface OpenAIResponse {
choices?: OpenAIChoice[];
export interface OpenAIAPIResponse {
choices: OpenAIChoice[];
}
export interface ToolDefinition {
type: 'function';
function: {
name: string;
description: string;
parameters: {
type: 'object';
properties: Record<string, unknown>;
required?: string[];
};
};
}
// Parsed tool call result
export interface ParsedToolCall {
name: string;
arguments: Record<string, unknown>;
}
// AI caller result
export interface AICallerResult {
response: string;
toolCalls?: ParsedToolCall[];
finishReason?: string;
}
// Legacy type alias for backward compatibility
export type ToolCall = OpenAIToolCall;
export type OpenAIResponse = OpenAIAPIResponse;
// Context7 API 응답 타입
export interface Context7Library {
id: string;

127
src/utils/ai-caller.ts Normal file
View File

@@ -0,0 +1,127 @@
import { createLogger } from './logger';
import { AI_CONFIG } from '../constants/agent-config';
import type {
Env,
OpenAIAPIResponse,
OpenAIToolCall,
ToolDefinition,
AICallerResult,
ParsedToolCall
} from '../types';
const logger = createLogger('ai-caller');
export interface AICallerConfig {
model?: string;
maxTokens: number;
temperature: number;
maxToolCalls?: number;
responseFormat?: { type: 'json_object' } | { type: 'text' };
}
export interface ChatMessage {
role: 'system' | 'user' | 'assistant' | 'tool';
content: string;
tool_call_id?: string;
}
/**
* Call OpenAI API with optional function calling support
*/
export async function callOpenAI(
messages: ChatMessage[],
tools: ToolDefinition[] | undefined,
config: AICallerConfig,
env: Env
): Promise<AICallerResult> {
const model = config.model || AI_CONFIG.model;
const maxToolCalls = config.maxToolCalls ?? AI_CONFIG.maxToolCalls;
try {
const requestBody: Record<string, unknown> = {
model,
messages,
max_tokens: config.maxTokens,
temperature: config.temperature,
};
if (tools && tools.length > 0) {
requestBody.tools = tools;
requestBody.tool_choice = 'auto';
}
if (config.responseFormat) {
requestBody.response_format = config.responseFormat;
}
const response = await fetch(`${env.OPENAI_API_BASE}/chat/completions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${env.OPENAI_API_KEY}`,
},
body: JSON.stringify(requestBody),
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`OpenAI API error: ${response.status} - ${errorText}`);
}
const data = await response.json() as OpenAIAPIResponse;
const choice = data.choices[0];
if (!choice) {
throw new Error('No response from OpenAI');
}
const message = choice.message;
const result: AICallerResult = {
response: message.content || '',
finishReason: choice.finish_reason,
};
// Parse tool calls if present
if (message.tool_calls && message.tool_calls.length > 0) {
result.toolCalls = message.tool_calls
.slice(0, maxToolCalls)
.map(parseToolCall)
.filter((tc): tc is ParsedToolCall => tc !== null);
}
return result;
} catch (error) {
logger.error('OpenAI API 호출 실패', error as Error);
throw error;
}
}
/**
* Parse a single tool call from OpenAI response
*/
function parseToolCall(toolCall: OpenAIToolCall): ParsedToolCall | null {
try {
return {
name: toolCall.function.name,
arguments: JSON.parse(toolCall.function.arguments),
};
} catch (error) {
logger.error('도구 호출 파싱 실패', error as Error, { toolCall });
return null;
}
}
/**
* Execute tool calls and get results
* Returns array of tool results for follow-up API call
*/
export function createToolResultMessages(
toolCalls: OpenAIToolCall[],
results: string[]
): ChatMessage[] {
return toolCalls.map((tc, index) => ({
role: 'tool' as const,
tool_call_id: tc.id,
content: results[index] || 'Error executing tool',
}));
}

15
src/utils/index.ts Normal file
View File

@@ -0,0 +1,15 @@
export * from './logger';
export * from './session-manager';
export * from './ai-caller';
export * from './formatters';
export * from './patterns';
export * from './retry';
export * from './circuit-breaker';
export * from './metrics';
export * from './optimistic-lock';
export * from './api-helper';
export * from './api-urls';
export * from './env-validation';
export * from './error';
export * from './email-decoder';
export * from './reconciliation';