cost_guard_ai 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,28 @@
1
+ # COST_GUARD_AI
2
+
3
+ # Contributing to COST_GUARD_AI
4
+ # COST_GUARD_AI 기여 가이드
5
+
6
+ ---
7
+
8
+ ### English
9
+ Thank you for your interest in contributing to **cost_guard_ai**! We welcome all contributions that help developers optimize LLM costs and security.
10
+
11
+ #### How to contribute
12
+ 1. Fork the repository.
13
+ 2. Create a new branch for your feature or bug fix.
14
+ 3. Make sure to include bilingual comments (English first) in your code.
15
+ 4. Ensure all tests pass by running `npm test`.
16
+ 5. Submit a Pull Request.
17
+
18
+ ---
19
+
20
+ ### 한국어
21
+ **cost_guard_ai**에 관심을 가져주셔서 감사합니다! 개발자들이 LLM 비용과 보안을 최적화하는 데 도움이 되는 모든 기여를 환영합니다.
22
+
23
+ #### 기여 방법
24
+ 1. 저장소를 포크합니다.
25
+ 2. 새로운 기능이나 버그 수정을 위한 브랜치를 생성합니다.
26
+ 3. 코드에 한영 병기 주석(영어 먼저)을 포함해 주세요.
27
+ 4. `npm test`를 실행하여 모든 테스트가 통과하는지 확인합니다.
28
+ 5. 풀 리퀘스트를 제출합니다.
package/LICENSE ADDED
@@ -0,0 +1,27 @@
1
+ COST_GUARD_AI
2
+
3
+ MIT License
4
+
5
+ Copyright (c) 2008-2026 Rheehose (Rhee Creative)
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
24
+
25
+ ---
26
+ English: This license covers the 'cost_guard_ai' package.
27
+ 한국어: 이 라이선스는 'cost_guard_ai' 패키지에 적용됩니다.
package/README.md ADDED
@@ -0,0 +1,59 @@
1
+ # COST_GUARD_AI
2
+
3
+ ## Intelligent Prompt Compression & Security Middleware for LLMs
4
+ ## LLM을 위한 지능형 프롬프트 압축 및 보안 미들웨어
5
+
6
+ ---
7
+
8
+ ### English Description
9
+ **cost_guard_ai** is a powerful guardian for developers and enterprises using LLM APIs (OpenAI, Anthropic, etc.). It sits between your application and the AI to optimize costs, ensure data privacy, and maintain high response quality.
10
+
11
+ #### Key Features
12
+ 1. **Semantic Prompt Compression**: Reduces token consumption by 20-30% while maintaining context.
13
+ 2. **PII Masking**: Automatically detects and masks sensitive information (emails, phone numbers, credit cards, etc.).
14
+ 3. **Caching Layer**: Instantly returns responses for identical or similar queries from a local/DB cache.
15
+ 4. **Budget Management**: Monitors API usage and prevents overspending with budget alerts and hard limits.
16
+
17
+ ---
18
+
19
+ ### 한국어 설명
20
+ **cost_guard_ai**는 LLM API(OpenAI, Anthropic 등)를 사용하는 개발자와 기업을 위한 강력한 가디언입니다. 애플리케이션과 AI 사이에서 비용을 최적화하고, 데이터 프라이버시를 보장하며, 높은 응답 품질을 유지합니다.
21
+
22
+ #### 핵심 기능
23
+ 1. **지능형 프롬프트 압축 (Semantic Prompt Compression)**: 문맥을 유지하면서 토큰 사용량을 20~30% 절감합니다.
24
+ 2. **개인정보 마스킹 (PII Masking)**: 이메일, 전화번호, 신용카드 번호 등 민감 정보를 자동으로 탐지하여 마스킹 처리합니다.
25
+ 3. **캐싱 레이어 (Caching Layer)**: 동일하거나 유사한 질문에 대해 로컬/DB 캐시에서 즉시 응답을 반환합니다.
26
+ 4. **예산 관리 (Budget Management)**: API 사용량을 모니터링하고 설정된 예산을 초과하지 않도록 알림 및 차단 기능을 제공합니다.
27
+
28
+ ---
29
+
30
+ ### Installation / 설치
31
+ ```bash
32
+ npm install cost_guard_ai
33
+ ```
34
+
35
+ ### Usage / 사용법
36
+ ```javascript
37
+ import { AICostGuard } from 'cost_guard_ai';
38
+ import OpenAI from 'openai';
39
+
40
+ const guard = new AICostGuard({
41
+ maxTokenBudget: 10000, // Daily budget limit / 일일 예산 설정
42
+ maskPII: true, // Enable PII masking / 개인정보 마스킹 활성화
43
+ compress: true // Enable prompt compression / 프롬프트 압축 활성화
44
+ });
45
+
46
+ const openai = new OpenAI();
47
+
48
+ // Wrap the existing call / 기존 호출 방식에 가드를 입힘
49
+ const response = await guard.wrap(openai.chat.completions.create({
50
+ model: "gpt-4",
51
+ messages: [{ role: "user", content: "Check the balance for customer Hong Gil-dong (010-1234-5678)." }]
52
+ }));
53
+ ```
54
+
55
+ ---
56
+
57
+ ### License / 라이선스
58
+ MIT License
59
+ Copyright (c) 2008-2026 Rheehose (Rhee Creative)
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Budget Management Module
3
+ * 예산 관리 모듈
4
+ */
5
+ export interface BudgetOptions {
6
+ maxTokenBudget?: number;
7
+ onBudgetExceeded?: (current: number, limit: number) => void;
8
+ }
9
+ /**
10
+ * Budget Manager Class
11
+ * 예산 관리자 클래스
12
+ */
13
+ export declare class BudgetManager {
14
+ private usedTokens;
15
+ private options;
16
+ private lastReset;
17
+ constructor(options: BudgetOptions);
18
+ /**
19
+ * Resets usage daily if necessary.
20
+ * 필요한 경우 일일 사용량을 초기화합니다.
21
+ */
22
+ private checkDailyReset;
23
+ /**
24
+ * Checks if the budget allows further requests.
25
+ * 예산이 추가 요청을 허용하는지 확인합니다.
26
+ *
27
+ * @returns True if within budget / 예산 내에 있으면 true
28
+ */
29
+ canSpend(): boolean;
30
+ /**
31
+ * Tracks token usage.
32
+ * 토큰 사용량을 추적합니다.
33
+ *
34
+ * @param tokens Number of tokens used / 사용된 토큰 수
35
+ */
36
+ track(tokens: number): void;
37
+ /**
38
+ * Gets current token usage.
39
+ * 현재 토큰 사용량을 가져옵니다.
40
+ *
41
+ * @returns Used tokens / 사용된 토큰 수
42
+ */
43
+ getUsage(): number;
44
+ }
package/dist/budget.js ADDED
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ /**
3
+ * Budget Management Module
4
+ * 예산 관리 모듈
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.BudgetManager = void 0;
8
+ /**
9
+ * Budget Manager Class
10
+ * 예산 관리자 클래스
11
+ */
12
+ class BudgetManager {
13
+ constructor(options) {
14
+ this.usedTokens = 0;
15
+ this.options = options;
16
+ this.lastReset = new Date();
17
+ }
18
+ /**
19
+ * Resets usage daily if necessary.
20
+ * 필요한 경우 일일 사용량을 초기화합니다.
21
+ */
22
+ checkDailyReset() {
23
+ const now = new Date();
24
+ if (now.getDate() !== this.lastReset.getDate()) {
25
+ this.usedTokens = 0;
26
+ this.lastReset = now;
27
+ }
28
+ }
29
+ /**
30
+ * Checks if the budget allows further requests.
31
+ * 예산이 추가 요청을 허용하는지 확인합니다.
32
+ *
33
+ * @returns True if within budget / 예산 내에 있으면 true
34
+ */
35
+ canSpend() {
36
+ this.checkDailyReset();
37
+ if (!this.options.maxTokenBudget)
38
+ return true;
39
+ return this.usedTokens < this.options.maxTokenBudget;
40
+ }
41
+ /**
42
+ * Tracks token usage.
43
+ * 토큰 사용량을 추적합니다.
44
+ *
45
+ * @param tokens Number of tokens used / 사용된 토큰 수
46
+ */
47
+ track(tokens) {
48
+ this.usedTokens += tokens;
49
+ if (this.options.maxTokenBudget && this.usedTokens >= this.options.maxTokenBudget) {
50
+ if (this.options.onBudgetExceeded) {
51
+ this.options.onBudgetExceeded(this.usedTokens, this.options.maxTokenBudget);
52
+ }
53
+ }
54
+ }
55
+ /**
56
+ * Gets current token usage.
57
+ * 현재 토큰 사용량을 가져옵니다.
58
+ *
59
+ * @returns Used tokens / 사용된 토큰 수
60
+ */
61
+ getUsage() {
62
+ return this.usedTokens;
63
+ }
64
+ }
65
+ exports.BudgetManager = BudgetManager;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Caching Module
3
+ * 캐싱 모듈
4
+ */
5
+ export interface CacheOptions {
6
+ enabled: boolean;
7
+ ttl?: number;
8
+ }
9
+ /**
10
+ * Response Cache Class
11
+ * 응답 캐시 클래스
12
+ */
13
+ export declare class ResponseCache {
14
+ private cache;
15
+ private options;
16
+ constructor(options: CacheOptions);
17
+ /**
18
+ * Generates a key for the cache based on input messages.
19
+ * 입력 메시지를 기반으로 캐시 키를 생성합니다.
20
+ *
21
+ * @param messages The prompt messages / 프롬프트 메시지
22
+ * @returns Cache key / 캐시 키
23
+ */
24
+ private generateKey;
25
+ /**
26
+ * Retrieves a cached response if available.
27
+ * 캐싱된 응답이 있는 경우 이를 반환합니다.
28
+ *
29
+ * @param messages The prompt messages / 프롬프트 메시지
30
+ * @returns Cached data or null / 캐싱된 데이터 또는 null
31
+ */
32
+ get(messages: any[]): any | null;
33
+ /**
34
+ * Sets a value in the cache.
35
+ * 캐시에 값을 저장합니다.
36
+ *
37
+ * @param messages The prompt messages / 프롬프트 메시지
38
+ * @param response The AI response / AI 응답
39
+ */
40
+ set(messages: any[], response: any): void;
41
+ }
package/dist/cache.js ADDED
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ResponseCache = void 0;
7
+ const node_cache_1 = __importDefault(require("node-cache"));
8
+ /**
9
+ * Response Cache Class
10
+ * 응답 캐시 클래스
11
+ */
12
+ class ResponseCache {
13
+ constructor(options) {
14
+ this.options = {
15
+ enabled: options.enabled ?? true,
16
+ ttl: options.ttl ?? 3600,
17
+ };
18
+ this.cache = new node_cache_1.default({ stdTTL: this.options.ttl });
19
+ }
20
+ /**
21
+ * Generates a key for the cache based on input messages.
22
+ * 입력 메시지를 기반으로 캐시 키를 생성합니다.
23
+ *
24
+ * @param messages The prompt messages / 프롬프트 메시지
25
+ * @returns Cache key / 캐시 키
26
+ */
27
+ generateKey(messages) {
28
+ return JSON.stringify(messages);
29
+ }
30
+ /**
31
+ * Retrieves a cached response if available.
32
+ * 캐싱된 응답이 있는 경우 이를 반환합니다.
33
+ *
34
+ * @param messages The prompt messages / 프롬프트 메시지
35
+ * @returns Cached data or null / 캐싱된 데이터 또는 null
36
+ */
37
+ get(messages) {
38
+ if (!this.options.enabled)
39
+ return null;
40
+ const key = this.generateKey(messages);
41
+ return this.cache.get(key) || null;
42
+ }
43
+ /**
44
+ * Sets a value in the cache.
45
+ * 캐시에 값을 저장합니다.
46
+ *
47
+ * @param messages The prompt messages / 프롬프트 메시지
48
+ * @param response The AI response / AI 응답
49
+ */
50
+ set(messages, response) {
51
+ if (!this.options.enabled)
52
+ return;
53
+ const key = this.generateKey(messages);
54
+ this.cache.set(key, response);
55
+ }
56
+ }
57
+ exports.ResponseCache = ResponseCache;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Prompt Compressor Module
3
+ * 프롬프트 압축 모듈
4
+ */
5
+ export interface CompressorOptions {
6
+ enabled: boolean;
7
+ level?: 'light' | 'medium' | 'aggressive';
8
+ }
9
+ /**
10
+ * Prompt Compressor Class
11
+ * 프롬프트 압축 클래스
12
+ */
13
+ export declare class PromptCompressor {
14
+ private options;
15
+ constructor(options: CompressorOptions);
16
+ /**
17
+ * Compresses the prompt text by removing redundancies.
18
+ * 중복을 제거하여 프롬프트 텍스트를 압축합니다.
19
+ *
20
+ * @param text The input text to compress / 압축할 입력 텍스트
21
+ * @returns Compressed text / 압축된 텍스트
22
+ */
23
+ compress(text: string): string;
24
+ }
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ /**
3
+ * Prompt Compressor Module
4
+ * 프롬프트 압축 모듈
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.PromptCompressor = void 0;
8
+ /**
9
+ * Prompt Compressor Class
10
+ * 프롬프트 압축 클래스
11
+ */
12
+ class PromptCompressor {
13
+ constructor(options) {
14
+ this.options = {
15
+ enabled: options.enabled ?? true,
16
+ level: options.level ?? 'light',
17
+ };
18
+ }
19
+ /**
20
+ * Compresses the prompt text by removing redundancies.
21
+ * 중복을 제거하여 프롬프트 텍스트를 압축합니다.
22
+ *
23
+ * @param text The input text to compress / 압축할 입력 텍스트
24
+ * @returns Compressed text / 압축된 텍스트
25
+ */
26
+ compress(text) {
27
+ if (!this.options.enabled)
28
+ return text;
29
+ let compressed = text;
30
+ // 1. Remove multiple spaces and newlines / 다중 공백 및 줄바꿈 제거
31
+ compressed = compressed.replace(/\s+/g, ' ').trim();
32
+ // 2. Medium level: Remove common filler words (English) / 중간 단계: 일반적인 채움말 제거 (영어)
33
+ if (this.options.level === 'medium' || this.options.level === 'aggressive') {
34
+ const fillerWords = ['actually', 'basically', 'honestly', 'literally', 'really', 'very'];
35
+ const fillerRegex = new RegExp(`\\b(${fillerWords.join('|')})\\b`, 'gi');
36
+ compressed = compressed.replace(fillerRegex, '');
37
+ }
38
+ // 3. Aggressive level: Remove common Korean filler words / 공격적 단계: 한국어 채움말 제거
39
+ if (this.options.level === 'aggressive') {
40
+ const koFillers = ['진짜', '정말', '사실', '기본적으로', '솔직히'];
41
+ const koFillerRegex = new RegExp(`(${koFillers.join('|')})`, 'g');
42
+ compressed = compressed.replace(koFillerRegex, '');
43
+ }
44
+ // Clean up extra spaces caused by replacement / 치환으로 발생한 추가 공백 정리
45
+ return compressed.replace(/\s+/g, ' ').trim();
46
+ }
47
+ }
48
+ exports.PromptCompressor = PromptCompressor;
@@ -0,0 +1,42 @@
1
+ import { MaskerOptions } from './masker';
2
+ import { CompressorOptions } from './compressor';
3
+ import { CacheOptions } from './cache';
4
+ /**
5
+ * AICostGuard Options Interface
6
+ * AICostGuard 옵션 인터페이스
7
+ */
8
+ export interface AICostGuardOptions {
9
+ maxTokenBudget?: number;
10
+ maskPII?: boolean | MaskerOptions;
11
+ compress?: boolean | CompressorOptions;
12
+ cache?: boolean | CacheOptions;
13
+ onBudgetExceeded?: (current: number, limit: number) => void;
14
+ }
15
+ /**
16
+ * Main AICostGuard Class
17
+ * 메인 AICostGuard 클래스
18
+ */
19
+ export declare class AICostGuard {
20
+ private masker;
21
+ private compressor;
22
+ private cache;
23
+ private budget;
24
+ constructor(options?: AICostGuardOptions);
25
+ /**
26
+ * Proxies an OpenAI-like SDK instance to intercept calls.
27
+ * OpenAI 스타일의 SDK 인스턴스를 프록시하여 호출을 가로챕니다.
28
+ *
29
+ * @param sdk The SDK instance (e.g., OpenAI) / SDK 인스턴스 (예: OpenAI)
30
+ * @returns Proxied SDK / 프록시된 SDK
31
+ */
32
+ wrap<T extends any>(sdk: T): T;
33
+ /**
34
+ * Processes the completion request with optimization and security.
35
+ * 최적화 및 보안 처리를 거쳐 완성 요청을 수행합니다.
36
+ *
37
+ * @param originalFn The original create function / 기존 create 함수
38
+ * @param params Request parameters / 요청 파라미터
39
+ * @returns AI response / AI 응답
40
+ */
41
+ private processCompletion;
42
+ }
package/dist/index.js ADDED
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AICostGuard = void 0;
4
+ const masker_1 = require("./masker");
5
+ const compressor_1 = require("./compressor");
6
+ const cache_1 = require("./cache");
7
+ const budget_1 = require("./budget");
8
+ /**
9
+ * Main AICostGuard Class
10
+ * 메인 AICostGuard 클래스
11
+ */
12
+ class AICostGuard {
13
+ constructor(options = {}) {
14
+ this.masker = new masker_1.PIIMasker(typeof options.maskPII === 'object' ? options.maskPII : { enabled: !!options.maskPII });
15
+ this.compressor = new compressor_1.PromptCompressor(typeof options.compress === 'object' ? options.compress : { enabled: !!options.compress });
16
+ this.cache = new cache_1.ResponseCache(typeof options.cache === 'object' ? options.cache : { enabled: options.cache !== false });
17
+ this.budget = new budget_1.BudgetManager({
18
+ maxTokenBudget: options.maxTokenBudget,
19
+ onBudgetExceeded: options.onBudgetExceeded,
20
+ });
21
+ }
22
+ /**
23
+ * Proxies an OpenAI-like SDK instance to intercept calls.
24
+ * OpenAI 스타일의 SDK 인스턴스를 프록시하여 호출을 가로챕니다.
25
+ *
26
+ * @param sdk The SDK instance (e.g., OpenAI) / SDK 인스턴스 (예: OpenAI)
27
+ * @returns Proxied SDK / 프록시된 SDK
28
+ */
29
+ wrap(sdk) {
30
+ const handler = {
31
+ get: (target, prop, receiver) => {
32
+ const value = Reflect.get(target, prop, receiver);
33
+ // Intercept chat.completions.create / chat.completions.create 호출 가로채기
34
+ if (prop === 'chat') {
35
+ return new Proxy(value, {
36
+ get: (chatTarget, chatProp) => {
37
+ const chatValue = Reflect.get(chatTarget, chatProp);
38
+ if (chatProp === 'completions') {
39
+ return new Proxy(chatValue, {
40
+ get: (compTarget, compProp) => {
41
+ const compValue = Reflect.get(compTarget, compProp);
42
+ if (compProp === 'create') {
43
+ return async (params) => {
44
+ return this.processCompletion(compValue.bind(compTarget), params);
45
+ };
46
+ }
47
+ return compValue;
48
+ }
49
+ });
50
+ }
51
+ return chatValue;
52
+ }
53
+ });
54
+ }
55
+ if (typeof value === 'function') {
56
+ return value.bind(target);
57
+ }
58
+ return value;
59
+ }
60
+ };
61
+ return new Proxy(sdk, handler);
62
+ }
63
+ /**
64
+ * Processes the completion request with optimization and security.
65
+ * 최적화 및 보안 처리를 거쳐 완성 요청을 수행합니다.
66
+ *
67
+ * @param originalFn The original create function / 기존 create 함수
68
+ * @param params Request parameters / 요청 파라미터
69
+ * @returns AI response / AI 응답
70
+ */
71
+ async processCompletion(originalFn, params) {
72
+ // 1. Check budget / 예산 확인
73
+ if (!this.budget.canSpend()) {
74
+ throw new Error('COST_GUARD: Daily token budget exceeded. / 일일 토큰 예산을 초과했습니다.');
75
+ }
76
+ // 2. Pre-process messages (Masking & Compression) / 메시지 전처리 (마스킹 및 압축)
77
+ const originalMessages = params.messages;
78
+ const processedMessages = originalMessages.map((msg) => ({
79
+ ...msg,
80
+ content: typeof msg.content === 'string'
81
+ ? this.compressor.compress(this.masker.mask(msg.content))
82
+ : msg.content
83
+ }));
84
+ // 3. Check Cache / 캐시 확인
85
+ const cachedResponse = this.cache.get(processedMessages);
86
+ if (cachedResponse) {
87
+ return cachedResponse;
88
+ }
89
+ // 4. Execute API Call / API 호출 실행
90
+ const requestParams = { ...params, messages: processedMessages };
91
+ const response = await originalFn(requestParams);
92
+ // 5. Track Usage & Cache / 사용량 추적 및 캐싱
93
+ if (response.usage && response.usage.total_tokens) {
94
+ this.budget.track(response.usage.total_tokens);
95
+ }
96
+ this.cache.set(processedMessages, response);
97
+ return response;
98
+ }
99
+ }
100
+ exports.AICostGuard = AICostGuard;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * PII Masking Module
3
+ * 개인정보 마스킹 모듈
4
+ */
5
+ export interface MaskerOptions {
6
+ enabled: boolean;
7
+ maskString?: string;
8
+ }
9
+ /**
10
+ * PII Masker Class
11
+ * 개인정보 마스킹 클래스
12
+ */
13
+ export declare class PIIMasker {
14
+ private patterns;
15
+ private options;
16
+ constructor(options: MaskerOptions);
17
+ /**
18
+ * Masks sensitive information in the given text.
19
+ * 주어진 텍스트 내의 민감 정보를 마스킹 처리합니다.
20
+ *
21
+ * @param text The input text to mask / 마스킹할 입력 텍스트
22
+ * @returns Masked text / 마스킹된 텍스트
23
+ */
24
+ mask(text: string): string;
25
+ }
package/dist/masker.js ADDED
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ /**
3
+ * PII Masking Module
4
+ * 개인정보 마스킹 모듈
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.PIIMasker = void 0;
8
+ /**
9
+ * PII Masker Class
10
+ * 개인정보 마스킹 클래스
11
+ */
12
+ class PIIMasker {
13
+ constructor(options) {
14
+ this.patterns = {
15
+ // Email / 이메일
16
+ email: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
17
+ // Phone Number (Korean & International) / 전화번호 (한국식 및 국제 규격)
18
+ phone: /(\d{2,3}[-\s]?\d{3,4}[-\s]?\d{4})|(\+?\d{1,3}[-\s]?\d{1,4}[-\s]?\d{4,10})/g,
19
+ // Identification Numbers (e.g., SSN, RRN) / 주민등록번호 등 식별 번호
20
+ idNumber: /\d{6}-\d{7}/g,
21
+ // Credit Card Numbers / 신용카드 번호
22
+ creditCard: /\d{4}-\d{4}-\d{4}-\d{4}/g,
23
+ };
24
+ this.options = {
25
+ enabled: options.enabled ?? true,
26
+ maskString: options.maskString ?? '[MASKED]',
27
+ };
28
+ }
29
+ /**
30
+ * Masks sensitive information in the given text.
31
+ * 주어진 텍스트 내의 민감 정보를 마스킹 처리합니다.
32
+ *
33
+ * @param text The input text to mask / 마스킹할 입력 텍스트
34
+ * @returns Masked text / 마스킹된 텍스트
35
+ */
36
+ mask(text) {
37
+ if (!this.options.enabled)
38
+ return text;
39
+ let maskedText = text;
40
+ const maskStr = this.options.maskString || '[MASKED]';
41
+ for (const pattern of Object.values(this.patterns)) {
42
+ maskedText = maskedText.replace(pattern, maskStr);
43
+ }
44
+ return maskedText;
45
+ }
46
+ }
47
+ exports.PIIMasker = PIIMasker;
package/jest.config.js ADDED
@@ -0,0 +1,5 @@
1
+ module.exports = {
2
+ preset: 'ts-jest',
3
+ testEnvironment: 'node',
4
+ testMatch: ['**/tests/**/*.test.ts'],
5
+ };
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "cost_guard_ai",
3
+ "version": "1.0.0",
4
+ "description": "Intelligent Prompt Compression and Security Middleware for LLMs / LLM을 위한 지능형 프롬프트 압축 및 보안 미들웨어",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "test": "jest",
10
+ "prepublishOnly": "npm run build"
11
+ },
12
+ "keywords": [
13
+ "ai",
14
+ "llm",
15
+ "cost-optimization",
16
+ "security",
17
+ "pii-masking",
18
+ "prompt-compression"
19
+ ],
20
+ "author": "Rheehose (Rhee Creative)",
21
+ "license": "MIT",
22
+ "devDependencies": {
23
+ "@types/jest": "^29.5.12",
24
+ "@types/node": "^20.11.24",
25
+ "jest": "^29.7.0",
26
+ "ts-jest": "^29.1.2",
27
+ "typescript": "^5.3.3",
28
+ "openai": "^4.28.1"
29
+ },
30
+ "dependencies": {
31
+ "node-cache": "^5.1.2"
32
+ }
33
+ }
package/src/budget.ts ADDED
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Budget Management Module
3
+ * 예산 관리 모듈
4
+ */
5
+
6
+ export interface BudgetOptions {
7
+ maxTokenBudget?: number; // Daily token budget / 일일 토큰 예산
8
+ onBudgetExceeded?: (current: number, limit: number) => void; // Callback when budget exceeded / 예산 초과 시 콜백
9
+ }
10
+
11
+ /**
12
+ * Budget Manager Class
13
+ * 예산 관리자 클래스
14
+ */
15
+ export class BudgetManager {
16
+ private usedTokens: number = 0;
17
+ private options: BudgetOptions;
18
+ private lastReset: Date;
19
+
20
+ constructor(options: BudgetOptions) {
21
+ this.options = options;
22
+ this.lastReset = new Date();
23
+ }
24
+
25
+ /**
26
+ * Resets usage daily if necessary.
27
+ * 필요한 경우 일일 사용량을 초기화합니다.
28
+ */
29
+ private checkDailyReset(): void {
30
+ const now = new Date();
31
+ if (now.getDate() !== this.lastReset.getDate()) {
32
+ this.usedTokens = 0;
33
+ this.lastReset = now;
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Checks if the budget allows further requests.
39
+ * 예산이 추가 요청을 허용하는지 확인합니다.
40
+ *
41
+ * @returns True if within budget / 예산 내에 있으면 true
42
+ */
43
+ public canSpend(): boolean {
44
+ this.checkDailyReset();
45
+ if (!this.options.maxTokenBudget) return true;
46
+ return this.usedTokens < this.options.maxTokenBudget;
47
+ }
48
+
49
+ /**
50
+ * Tracks token usage.
51
+ * 토큰 사용량을 추적합니다.
52
+ *
53
+ * @param tokens Number of tokens used / 사용된 토큰 수
54
+ */
55
+ public track(tokens: number): void {
56
+ this.usedTokens += tokens;
57
+ if (this.options.maxTokenBudget && this.usedTokens >= this.options.maxTokenBudget) {
58
+ if (this.options.onBudgetExceeded) {
59
+ this.options.onBudgetExceeded(this.usedTokens, this.options.maxTokenBudget);
60
+ }
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Gets current token usage.
66
+ * 현재 토큰 사용량을 가져옵니다.
67
+ *
68
+ * @returns Used tokens / 사용된 토큰 수
69
+ */
70
+ public getUsage(): number {
71
+ return this.usedTokens;
72
+ }
73
+ }
package/src/cache.ts ADDED
@@ -0,0 +1,65 @@
1
+ import NodeCache from 'node-cache';
2
+
3
+ /**
4
+ * Caching Module
5
+ * 캐싱 모듈
6
+ */
7
+
8
+ export interface CacheOptions {
9
+ enabled: boolean;
10
+ ttl?: number; // Time to live in seconds / 유지 시간(초)
11
+ }
12
+
13
+ /**
14
+ * Response Cache Class
15
+ * 응답 캐시 클래스
16
+ */
17
+ export class ResponseCache {
18
+ private cache: NodeCache;
19
+ private options: CacheOptions;
20
+
21
+ constructor(options: CacheOptions) {
22
+ this.options = {
23
+ enabled: options.enabled ?? true,
24
+ ttl: options.ttl ?? 3600,
25
+ };
26
+ this.cache = new NodeCache({ stdTTL: this.options.ttl });
27
+ }
28
+
29
+ /**
30
+ * Generates a key for the cache based on input messages.
31
+ * 입력 메시지를 기반으로 캐시 키를 생성합니다.
32
+ *
33
+ * @param messages The prompt messages / 프롬프트 메시지
34
+ * @returns Cache key / 캐시 키
35
+ */
36
+ private generateKey(messages: any[]): string {
37
+ return JSON.stringify(messages);
38
+ }
39
+
40
+ /**
41
+ * Retrieves a cached response if available.
42
+ * 캐싱된 응답이 있는 경우 이를 반환합니다.
43
+ *
44
+ * @param messages The prompt messages / 프롬프트 메시지
45
+ * @returns Cached data or null / 캐싱된 데이터 또는 null
46
+ */
47
+ public get(messages: any[]): any | null {
48
+ if (!this.options.enabled) return null;
49
+ const key = this.generateKey(messages);
50
+ return this.cache.get(key) || null;
51
+ }
52
+
53
+ /**
54
+ * Sets a value in the cache.
55
+ * 캐시에 값을 저장합니다.
56
+ *
57
+ * @param messages The prompt messages / 프롬프트 메시지
58
+ * @param response The AI response / AI 응답
59
+ */
60
+ public set(messages: any[], response: any): void {
61
+ if (!this.options.enabled) return;
62
+ const key = this.generateKey(messages);
63
+ this.cache.set(key, response);
64
+ }
65
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Prompt Compressor Module
3
+ * 프롬프트 압축 모듈
4
+ */
5
+
6
+ export interface CompressorOptions {
7
+ enabled: boolean;
8
+ level?: 'light' | 'medium' | 'aggressive';
9
+ }
10
+
11
+ /**
12
+ * Prompt Compressor Class
13
+ * 프롬프트 압축 클래스
14
+ */
15
+ export class PromptCompressor {
16
+ private options: CompressorOptions;
17
+
18
+ constructor(options: CompressorOptions) {
19
+ this.options = {
20
+ enabled: options.enabled ?? true,
21
+ level: options.level ?? 'light',
22
+ };
23
+ }
24
+
25
+ /**
26
+ * Compresses the prompt text by removing redundancies.
27
+ * 중복을 제거하여 프롬프트 텍스트를 압축합니다.
28
+ *
29
+ * @param text The input text to compress / 압축할 입력 텍스트
30
+ * @returns Compressed text / 압축된 텍스트
31
+ */
32
+ public compress(text: string): string {
33
+ if (!this.options.enabled) return text;
34
+
35
+ let compressed = text;
36
+
37
+ // 1. Remove multiple spaces and newlines / 다중 공백 및 줄바꿈 제거
38
+ compressed = compressed.replace(/\s+/g, ' ').trim();
39
+
40
+ // 2. Medium level: Remove common filler words (English) / 중간 단계: 일반적인 채움말 제거 (영어)
41
+ if (this.options.level === 'medium' || this.options.level === 'aggressive') {
42
+ const fillerWords = ['actually', 'basically', 'honestly', 'literally', 'really', 'very'];
43
+ const fillerRegex = new RegExp(`\\b(${fillerWords.join('|')})\\b`, 'gi');
44
+ compressed = compressed.replace(fillerRegex, '');
45
+ }
46
+
47
+ // 3. Aggressive level: Remove common Korean filler words / 공격적 단계: 한국어 채움말 제거
48
+ if (this.options.level === 'aggressive') {
49
+ const koFillers = ['진짜', '정말', '사실', '기본적으로', '솔직히'];
50
+ const koFillerRegex = new RegExp(`(${koFillers.join('|')})`, 'g');
51
+ compressed = compressed.replace(koFillerRegex, '');
52
+ }
53
+
54
+ // Clean up extra spaces caused by replacement / 치환으로 발생한 추가 공백 정리
55
+ return compressed.replace(/\s+/g, ' ').trim();
56
+ }
57
+ }
package/src/index.ts ADDED
@@ -0,0 +1,130 @@
1
+ import { PIIMasker, MaskerOptions } from './masker';
2
+ import { PromptCompressor, CompressorOptions } from './compressor';
3
+ import { ResponseCache, CacheOptions } from './cache';
4
+ import { BudgetManager, BudgetOptions } from './budget';
5
+
6
+ /**
7
+ * AICostGuard Options Interface
8
+ * AICostGuard 옵션 인터페이스
9
+ */
10
+ export interface AICostGuardOptions {
11
+ maxTokenBudget?: number;
12
+ maskPII?: boolean | MaskerOptions;
13
+ compress?: boolean | CompressorOptions;
14
+ cache?: boolean | CacheOptions;
15
+ onBudgetExceeded?: (current: number, limit: number) => void;
16
+ }
17
+
18
+ /**
19
+ * Main AICostGuard Class
20
+ * 메인 AICostGuard 클래스
21
+ */
22
+ export class AICostGuard {
23
+ private masker: PIIMasker;
24
+ private compressor: PromptCompressor;
25
+ private cache: ResponseCache;
26
+ private budget: BudgetManager;
27
+
28
+ constructor(options: AICostGuardOptions = {}) {
29
+ this.masker = new PIIMasker(
30
+ typeof options.maskPII === 'object' ? options.maskPII : { enabled: !!options.maskPII }
31
+ );
32
+ this.compressor = new PromptCompressor(
33
+ typeof options.compress === 'object' ? options.compress : { enabled: !!options.compress }
34
+ );
35
+ this.cache = new ResponseCache(
36
+ typeof options.cache === 'object' ? options.cache : { enabled: options.cache !== false }
37
+ );
38
+ this.budget = new BudgetManager({
39
+ maxTokenBudget: options.maxTokenBudget,
40
+ onBudgetExceeded: options.onBudgetExceeded,
41
+ });
42
+ }
43
+
44
+ /**
45
+ * Proxies an OpenAI-like SDK instance to intercept calls.
46
+ * OpenAI 스타일의 SDK 인스턴스를 프록시하여 호출을 가로챕니다.
47
+ *
48
+ * @param sdk The SDK instance (e.g., OpenAI) / SDK 인스턴스 (예: OpenAI)
49
+ * @returns Proxied SDK / 프록시된 SDK
50
+ */
51
+ public wrap<T extends any>(sdk: T): T {
52
+ const handler: ProxyHandler<any> = {
53
+ get: (target, prop, receiver) => {
54
+ const value = Reflect.get(target, prop, receiver);
55
+
56
+ // Intercept chat.completions.create / chat.completions.create 호출 가로채기
57
+ if (prop === 'chat') {
58
+ return new Proxy(value, {
59
+ get: (chatTarget, chatProp) => {
60
+ const chatValue = Reflect.get(chatTarget, chatProp);
61
+ if (chatProp === 'completions') {
62
+ return new Proxy(chatValue, {
63
+ get: (compTarget, compProp) => {
64
+ const compValue = Reflect.get(compTarget, compProp);
65
+ if (compProp === 'create') {
66
+ return async (params: any) => {
67
+ return this.processCompletion(compValue.bind(compTarget), params);
68
+ };
69
+ }
70
+ return compValue;
71
+ }
72
+ });
73
+ }
74
+ return chatValue;
75
+ }
76
+ });
77
+ }
78
+
79
+ if (typeof value === 'function') {
80
+ return value.bind(target);
81
+ }
82
+ return value;
83
+ }
84
+ };
85
+
86
+ return new Proxy(sdk, handler);
87
+ }
88
+
89
+ /**
90
+ * Processes the completion request with optimization and security.
91
+ * 최적화 및 보안 처리를 거쳐 완성 요청을 수행합니다.
92
+ *
93
+ * @param originalFn The original create function / 기존 create 함수
94
+ * @param params Request parameters / 요청 파라미터
95
+ * @returns AI response / AI 응답
96
+ */
97
+ private async processCompletion(originalFn: Function, params: any): Promise<any> {
98
+ // 1. Check budget / 예산 확인
99
+ if (!this.budget.canSpend()) {
100
+ throw new Error('COST_GUARD: Daily token budget exceeded. / 일일 토큰 예산을 초과했습니다.');
101
+ }
102
+
103
+ // 2. Pre-process messages (Masking & Compression) / 메시지 전처리 (마스킹 및 압축)
104
+ const originalMessages = params.messages;
105
+ const processedMessages = originalMessages.map((msg: any) => ({
106
+ ...msg,
107
+ content: typeof msg.content === 'string'
108
+ ? this.compressor.compress(this.masker.mask(msg.content))
109
+ : msg.content
110
+ }));
111
+
112
+ // 3. Check Cache / 캐시 확인
113
+ const cachedResponse = this.cache.get(processedMessages);
114
+ if (cachedResponse) {
115
+ return cachedResponse;
116
+ }
117
+
118
+ // 4. Execute API Call / API 호출 실행
119
+ const requestParams = { ...params, messages: processedMessages };
120
+ const response = await originalFn(requestParams);
121
+
122
+ // 5. Track Usage & Cache / 사용량 추적 및 캐싱
123
+ if (response.usage && response.usage.total_tokens) {
124
+ this.budget.track(response.usage.total_tokens);
125
+ }
126
+ this.cache.set(processedMessages, response);
127
+
128
+ return response;
129
+ }
130
+ }
package/src/masker.ts ADDED
@@ -0,0 +1,55 @@
1
+ /**
2
+ * PII Masking Module
3
+ * 개인정보 마스킹 모듈
4
+ */
5
+
6
+ export interface MaskerOptions {
7
+ enabled: boolean;
8
+ maskString?: string;
9
+ }
10
+
11
+ /**
12
+ * PII Masker Class
13
+ * 개인정보 마스킹 클래스
14
+ */
15
+ export class PIIMasker {
16
+ private patterns: Record<string, RegExp> = {
17
+ // Email / 이메일
18
+ email: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
19
+ // Phone Number (Korean & International) / 전화번호 (한국식 및 국제 규격)
20
+ phone: /(\d{2,3}[-\s]?\d{3,4}[-\s]?\d{4})|(\+?\d{1,3}[-\s]?\d{1,4}[-\s]?\d{4,10})/g,
21
+ // Identification Numbers (e.g., SSN, RRN) / 주민등록번호 등 식별 번호
22
+ idNumber: /\d{6}-\d{7}/g,
23
+ // Credit Card Numbers / 신용카드 번호
24
+ creditCard: /\d{4}-\d{4}-\d{4}-\d{4}/g,
25
+ };
26
+
27
+ private options: MaskerOptions;
28
+
29
+ constructor(options: MaskerOptions) {
30
+ this.options = {
31
+ enabled: options.enabled ?? true,
32
+ maskString: options.maskString ?? '[MASKED]',
33
+ };
34
+ }
35
+
36
+ /**
37
+ * Masks sensitive information in the given text.
38
+ * 주어진 텍스트 내의 민감 정보를 마스킹 처리합니다.
39
+ *
40
+ * @param text The input text to mask / 마스킹할 입력 텍스트
41
+ * @returns Masked text / 마스킹된 텍스트
42
+ */
43
+ public mask(text: string): string {
44
+ if (!this.options.enabled) return text;
45
+
46
+ let maskedText = text;
47
+ const maskStr = this.options.maskString || '[MASKED]';
48
+
49
+ for (const pattern of Object.values(this.patterns)) {
50
+ maskedText = maskedText.replace(pattern, maskStr);
51
+ }
52
+
53
+ return maskedText;
54
+ }
55
+ }
@@ -0,0 +1,95 @@
1
+ import { AICostGuard } from '../src/index';
2
+
3
+ /**
4
+ * Basic Test for AICostGuard
5
+ * AICostGuard 기본 테스트
6
+ */
7
+ describe('AICostGuard', () => {
8
+ let guard: AICostGuard;
9
+
10
+ beforeEach(() => {
11
+ guard = new AICostGuard({
12
+ maskPII: true,
13
+ compress: true,
14
+ maxTokenBudget: 1000
15
+ });
16
+ });
17
+
18
+ test('should mask PII in messages', async () => {
19
+ const mockOpenAI = {
20
+ chat: {
21
+ completions: {
22
+ create: jest.fn().mockResolvedValue({
23
+ choices: [{ message: { content: 'Hello' } }],
24
+ usage: { total_tokens: 10 }
25
+ })
26
+ }
27
+ }
28
+ };
29
+
30
+ const wrapped = guard.wrap(mockOpenAI);
31
+ await wrapped.chat.completions.create({
32
+ model: 'gpt-4',
33
+ messages: [{ role: 'user', content: 'My email is test@example.com and phone is 010-1234-5678' }]
34
+ });
35
+
36
+ const callArgs = mockOpenAI.chat.completions.create.mock.calls[0][0];
37
+ const content = callArgs.messages[0].content;
38
+
39
+ // Check if email and phone are masked / 이메일과 전화번호가 마스킹되었는지 확인
40
+ expect(content).toContain('[MASKED]');
41
+ expect(content).not.toContain('test@example.com');
42
+ expect(content).not.toContain('010-1234-5678');
43
+ });
44
+
45
+ test('should compress whitespace in messages', async () => {
46
+ const mockOpenAI = {
47
+ chat: {
48
+ completions: {
49
+ create: jest.fn().mockResolvedValue({
50
+ choices: [{ message: { content: 'Hello' } }],
51
+ usage: { total_tokens: 10 }
52
+ })
53
+ }
54
+ }
55
+ };
56
+
57
+ const wrapped = guard.wrap(mockOpenAI);
58
+ await wrapped.chat.completions.create({
59
+ model: 'gpt-4',
60
+ messages: [{ role: 'user', content: 'Hello world \n with spaces' }]
61
+ });
62
+
63
+ const callArgs = mockOpenAI.chat.completions.create.mock.calls[0][0];
64
+ const content = callArgs.messages[0].content;
65
+
66
+ // Check if extra spaces are removed / 여분의 공백이 제거되었는지 확인
67
+ expect(content).toBe('Hello world with spaces');
68
+ });
69
+
70
+ test('should block requests when budget is exceeded', async () => {
71
+ const smallGuard = new AICostGuard({ maxTokenBudget: 5 });
72
+ const mockOpenAI = {
73
+ chat: {
74
+ completions: {
75
+ create: jest.fn().mockResolvedValue({
76
+ choices: [{ message: { content: 'Hello' } }],
77
+ usage: { total_tokens: 10 }
78
+ })
79
+ }
80
+ }
81
+ };
82
+
83
+ const wrapped = smallGuard.wrap(mockOpenAI);
84
+
85
+ // First call should succeed / 첫 번째 호출은 성공해야 함
86
+ await wrapped.chat.completions.create({
87
+ messages: [{ role: 'user', content: 'Hi' }]
88
+ });
89
+
90
+ // Second call should fail as usage (10) > budget (5) / 사용량(10)이 예산(5)을 초과하므로 두 번째 호출은 실패해야 함
91
+ await expect(wrapped.chat.completions.create({
92
+ messages: [{ role: 'user', content: 'Hi' }]
93
+ })).rejects.toThrow('COST_GUARD: Daily token budget exceeded.');
94
+ });
95
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "CommonJS",
5
+ "lib": [
6
+ "ES2020",
7
+ "DOM"
8
+ ],
9
+ "declaration": true,
10
+ "outDir": "./dist",
11
+ "rootDir": "./src",
12
+ "strict": true,
13
+ "esModuleInterop": true,
14
+ "skipLibCheck": true,
15
+ "forceConsistentCasingInFileNames": true
16
+ },
17
+ "include": [
18
+ "src/**/*"
19
+ ],
20
+ "exclude": [
21
+ "node_modules",
22
+ "**/*.test.ts"
23
+ ]
24
+ }