refactor: 파일 분리 리팩토링 (routes, services, tools, utils)
아키텍처 개선: - index.ts: 921줄 → 205줄 (77% 감소) - openai-service.ts: 1,356줄 → 148줄 (89% 감소) 새로운 디렉토리 구조: - src/routes/ - Webhook, API, Health check 핸들러 - webhook.ts (287줄) - api.ts (318줄) - health.ts (14줄) - src/services/ - 비즈니스 로직 - bank-sms-parser.ts (143줄) - deposit-matcher.ts (88줄) - src/tools/ - Function Calling 도구 모듈화 - weather-tool.ts (37줄) - search-tool.ts (156줄) - domain-tool.ts (725줄) - deposit-tool.ts (183줄) - utility-tools.ts (60줄) - index.ts (104줄) - 도구 레지스트리 - src/utils/ - 유틸리티 함수 - email-decoder.ts - Quoted-Printable 디코더 타입 에러 수정: - routes/webhook.ts: text undefined 체크 - summary-service.ts: D1 타입 캐스팅 - summary-service.ts: Workers AI 타입 처리 - n8n-service.ts: Workers AI 타입 + 미사용 변수 제거 빌드 검증: - TypeScript 타입 체크 통과 - Wrangler dev 로컬 빌드 성공 문서: - REFACTORING_SUMMARY.md 추가 - ROUTE_ARCHITECTURE.md 추가 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
156
src/tools/search-tool.ts
Normal file
156
src/tools/search-tool.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
import type { Env } from '../types';
|
||||
|
||||
// Cloudflare AI Gateway를 통해 OpenAI API 호출 (지역 제한 우회)
|
||||
const OPENAI_API_URL = 'https://gateway.ai.cloudflare.com/v1/d8e5997eb4040f8b489f09095c0f623c/telegram-bot/openai/chat/completions';
|
||||
|
||||
export const searchWebTool = {
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'search_web',
|
||||
description: '웹에서 최신 정보를 검색합니다. 실시간 가격, 뉴스, 현재 날짜 이후 정보, 특정 사실 확인이 필요할 때 반드시 사용하세요. "비트코인 가격", "오늘 뉴스", "~란", "~뭐야" 등의 질문에 사용합니다.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
query: {
|
||||
type: 'string',
|
||||
description: '검색 쿼리',
|
||||
},
|
||||
},
|
||||
required: ['query'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const lookupDocsTool = {
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'lookup_docs',
|
||||
description: '프로그래밍 라이브러리의 공식 문서를 조회합니다. React, OpenAI, Cloudflare Workers 등의 최신 문서와 코드 예제를 검색할 수 있습니다.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
library: {
|
||||
type: 'string',
|
||||
description: '라이브러리 이름 (예: react, openai, cloudflare-workers, next.js)',
|
||||
},
|
||||
query: {
|
||||
type: 'string',
|
||||
description: '찾고 싶은 내용 (예: hooks 사용법, API 호출 방법)',
|
||||
},
|
||||
},
|
||||
required: ['library', 'query'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export async function executeSearchWeb(args: { query: string }, env?: Env): Promise<string> {
|
||||
let query = args.query;
|
||||
try {
|
||||
if (!env?.BRAVE_API_KEY) {
|
||||
return `🔍 검색 기능이 설정되지 않았습니다.`;
|
||||
}
|
||||
|
||||
// 한글이 포함된 경우 영문으로 번역 (기술 용어, 제품명 등)
|
||||
const hasKorean = /[가-힣]/.test(query);
|
||||
let translatedQuery = query;
|
||||
|
||||
if (hasKorean && env?.OPENAI_API_KEY) {
|
||||
try {
|
||||
const translateRes = await fetch(OPENAI_API_URL, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${env.OPENAI_API_KEY}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'gpt-4o-mini',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: `사용자의 검색어를 영문으로 번역하세요.
|
||||
- 외래어/기술용어는 원래 영문 표기로 변환 (예: 판골린→Pangolin, 도커→Docker)
|
||||
- 일반 한국어는 영문으로 번역
|
||||
- 검색에 최적화된 키워드로 변환
|
||||
- 번역된 검색어만 출력, 설명 없이`
|
||||
},
|
||||
{ role: 'user', content: query }
|
||||
],
|
||||
max_tokens: 100,
|
||||
temperature: 0.3,
|
||||
}),
|
||||
});
|
||||
if (translateRes.ok) {
|
||||
const translateData = await translateRes.json() as any;
|
||||
translatedQuery = translateData.choices?.[0]?.message?.content?.trim() || query;
|
||||
console.log(`[search_web] 번역: "${query}" → "${translatedQuery}"`);
|
||||
}
|
||||
} catch {
|
||||
// 번역 실패 시 원본 사용
|
||||
}
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
`https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(translatedQuery)}&count=5`,
|
||||
{
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'X-Subscription-Token': env.BRAVE_API_KEY,
|
||||
},
|
||||
}
|
||||
);
|
||||
if (!response.ok) {
|
||||
return `🔍 검색 오류: ${response.status}`;
|
||||
}
|
||||
const data = await response.json() as any;
|
||||
|
||||
// Web 검색 결과 파싱
|
||||
const webResults = data.web?.results || [];
|
||||
if (webResults.length === 0) {
|
||||
return `🔍 "${query}"에 대한 검색 결과가 없습니다.`;
|
||||
}
|
||||
|
||||
const results = webResults.slice(0, 3).map((r: any, i: number) =>
|
||||
`${i + 1}. <b>${r.title}</b>\n ${r.description}\n ${r.url}`
|
||||
).join('\n\n');
|
||||
|
||||
// 번역된 경우 원본 쿼리도 표시
|
||||
const queryDisplay = (hasKorean && translatedQuery !== query)
|
||||
? `${query} (→ ${translatedQuery})`
|
||||
: query;
|
||||
|
||||
return `🔍 검색 결과: ${queryDisplay}\n\n${results}`;
|
||||
} catch (error) {
|
||||
return `검색 중 오류가 발생했습니다: ${String(error)}`;
|
||||
}
|
||||
}
|
||||
|
||||
export async function executeLookupDocs(args: { library: string; query: string }): Promise<string> {
|
||||
const { library, query } = args;
|
||||
try {
|
||||
// Context7 REST API 직접 호출
|
||||
// 1. 라이브러리 검색
|
||||
const searchUrl = `https://context7.com/api/v2/libs/search?libraryName=${encodeURIComponent(library)}&query=${encodeURIComponent(query)}`;
|
||||
const searchResponse = await fetch(searchUrl);
|
||||
const searchData = await searchResponse.json() as any;
|
||||
|
||||
if (!searchData.libraries?.length) {
|
||||
return `📚 "${library}" 라이브러리를 찾을 수 없습니다.`;
|
||||
}
|
||||
|
||||
const libraryId = searchData.libraries[0].id;
|
||||
|
||||
// 2. 문서 조회
|
||||
const docsUrl = `https://context7.com/api/v2/context?libraryId=${encodeURIComponent(libraryId)}&query=${encodeURIComponent(query)}`;
|
||||
const docsResponse = await fetch(docsUrl);
|
||||
const docsData = await docsResponse.json() as any;
|
||||
|
||||
if (docsData.error) {
|
||||
return `📚 문서 조회 실패: ${docsData.message || docsData.error}`;
|
||||
}
|
||||
|
||||
const content = docsData.context || docsData.content || JSON.stringify(docsData, null, 2);
|
||||
return `📚 ${library} 문서 (${query}):\n\n${content.slice(0, 1500)}`;
|
||||
} catch (error) {
|
||||
return `📚 문서 조회 중 오류: ${String(error)}`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user