Commit 019b42b5 authored by platyhouse's avatar platyhouse

# AI 도구 기능 개선 및 벡터 임베딩 스크립트 추가

## 새 스크립트: ptyAIGetVector

- ptyAIGetVector: 텍스트 벡터 임베딩 CLI 도구 추가
  - OpenAI, Google, Ollama, VoyageAI 프로바이더 지원
  - --dimensions 옵션으로 출력 벡터 차원 조절 (OpenAI text-embedding-3-* 전용)
  - --json 옵션으로 전체 요청/응답 JSON 출력
  - stdin 입력 지원 (파이프, 리다이렉션)

## AI 라이브러리 임베딩 기능 추가

- ptyLibrary_PHP/ai/chatgpt.api.common.model.php: getEmbedding() 메서드 추가 (OpenAI)
- ptyLibrary_PHP/ai/gemini.api.common.model.php: getEmbedding() 메서드 추가 (Google)
- ptyLibrary_PHP/ai/ollama.api.common.model.php: getEmbedding() 메서드 추가 (Ollama)
- ptyLibrary_PHP/ai/voyage.api.common.model.php: VoyageAI 임베딩 API 클라이언트 신규 추가
- ptyLibrary_PHP/ai/ptyAIConfig.php: VoyageAI 프로바이더 지원 및 getDefaultEmbeddingModel() 메서드 추가

## ptyAIGetMessage 개선

- ptyAIGetMessage: stdin 입력 지원 추가 (인자 없이 실행 시 stdin에서 읽기)
- ptyAIGetMessage: 도움말에 입력 방법 섹션 추가 (파이프, 리다이렉션, 클립보드)

## 문서 업데이트

- CLAUDE.md: stdin 입력 지원 패턴 가이드라인 추가
- CLAUDE.md: ptyAIGetVector 스크립트 문서화
- CLAUDE.md: getEmbedding() 메서드 및 지원 프로바이더/모델 문서화
- README.md: ptyAIGetVector 스크립트 추가 및 사용 예시
parent 8a5fd185
...@@ -254,6 +254,50 @@ if (isset($options['edit'])) { ...@@ -254,6 +254,50 @@ if (isset($options['edit'])) {
} }
``` ```
### stdin 입력 지원 패턴
긴 텍스트를 입력받는 스크립트(쿼리, 메시지, 임베딩 텍스트 등)는 stdin 입력을 지원합니다.
**구현 패턴:**
```php
// 텍스트 입력: 인자 또는 stdin
if (empty($positionalArgs)) {
// stdin에서 읽기
if (posix_isatty(STDIN)) {
fwrite(STDERR, "입력하세요 (Ctrl+D로 완료):\n");
}
$input = file_get_contents('php://stdin');
$input = trim($input);
if (empty($input)) {
fwrite(STDERR, "Error: 입력이 필요합니다.\n");
fwrite(STDERR, "도움말: {$argv[0]} --help\n");
exit(1);
}
} else {
$input = $positionalArgs[0];
}
```
**도움말 출력 패턴:**
```php
echo "사용법: {$argv[0]} [\"텍스트\"] [옵션]\n";
echo "\n";
echo "설명...\n";
echo "텍스트 인자가 없으면 stdin에서 읽습니다 (Ctrl+D로 완료).\n";
// ...
echo "입력 방법:\n";
echo " {$argv[0]} --옵션 # stdin에서 입력 (Ctrl+D로 완료)\n";
echo " {$argv[0]} --옵션 < file.txt # 파일에서 입력 (리다이렉션)\n";
echo " cat file.txt | {$argv[0]} --옵션 # 파일에서 입력 (파이프)\n";
echo " {$argv[0]} \"\$(pbpaste)\" --옵션 # 클립보드에서 입력 (macOS)\n";
echo " {$argv[0]} \"\$(xclip -o)\" --옵션 # 클립보드에서 입력 (Linux)\n";
```
**적용된 스크립트:**
- `ptyAIGetMessage` - AI 메시지 전송
- `ptyAIGetVector` - 벡터 임베딩
- `ptyMysqlQuery` - SQL 쿼리 실행
### x_ 접두사 무시 패턴 (선택) ### x_ 접두사 무시 패턴 (선택)
데이터베이스/테이블/인덱스 등에서 `x_` 접두사 항목을 무시하는 옵션: 데이터베이스/테이블/인덱스 등에서 `x_` 접두사 항목을 무시하는 옵션:
...@@ -1084,11 +1128,12 @@ try { ...@@ -1084,11 +1128,12 @@ try {
## ptyAIGetMessage - AI API CLI 도구 ## ptyAIGetMessage - AI API CLI 도구
다양한 AI API(Claude, ChatGPT, Gemini, Ollama)를 통해 메시지를 전송하고 응답을 받는 CLI 도구입니다. 다양한 AI API(Claude, ChatGPT, Gemini, Ollama)를 통해 메시지를 전송하고 응답을 받는 CLI 도구입니다.
메시지 인자가 없으면 stdin에서 읽습니다 (Ctrl+D로 완료).
### 기본 사용법 ### 기본 사용법
```bash ```bash
./ptyAIGetMessage.php "메시지" [옵션] ./ptyAIGetMessage ["메시지"] [옵션]
``` ```
### CLI 옵션 ### CLI 옵션
...@@ -1106,22 +1151,41 @@ try { ...@@ -1106,22 +1151,41 @@ try {
```bash ```bash
# 기본 사용 # 기본 사용
./ptyAIGetMessage.php "안녕하세요" ./ptyAIGetMessage "안녕하세요"
# 특정 섹션 사용 # 특정 섹션 사용
./ptyAIGetMessage.php "안녕" --ai=openai ./ptyAIGetMessage "안녕" --ai=openai
# 모델 오버라이드 # 모델 오버라이드
./ptyAIGetMessage.php "안녕" --ai=anthropic --model=claude-3-haiku-20240307 ./ptyAIGetMessage "안녕" --ai=anthropic --model=claude-3-haiku-20240307
# Claude 베타 기능 # Claude 베타 기능
./ptyAIGetMessage.php "안녕" --ai=claude --anthropic-beta=context-1m-2025-08-07 ./ptyAIGetMessage "안녕" --ai=claude --anthropic-beta=context-1m-2025-08-07
# 상세 정보 출력 (curl 요청/응답 포함) # 상세 정보 출력 (curl 요청/응답 포함)
./ptyAIGetMessage.php "안녕" --ai=openai --verbose ./ptyAIGetMessage "안녕" --ai=openai --verbose
# JSON 출력 # JSON 출력
./ptyAIGetMessage.php "안녕" --ai=google --json ./ptyAIGetMessage "안녕" --ai=google --json
```
### 입력 방법
```bash
# stdin에서 입력 (Ctrl+D로 완료)
./ptyAIGetMessage --ai=openai
# 파일에서 입력 (리다이렉션)
./ptyAIGetMessage --ai=openai < prompt.txt
# 파일에서 입력 (파이프)
cat prompt.txt | ./ptyAIGetMessage --ai=openai
# 클립보드에서 입력 (macOS)
./ptyAIGetMessage "$(pbpaste)" --ai=openai
# 클립보드에서 입력 (Linux)
./ptyAIGetMessage "$(xclip -o)" --ai=openai
``` ```
### 설정 파일: ~/.ptyAIConfig.ini ### 설정 파일: ~/.ptyAIConfig.ini
...@@ -1240,6 +1304,34 @@ $client->setAnthropicBeta($feature); // anthropic-beta 헤더 설정 ...@@ -1240,6 +1304,34 @@ $client->setAnthropicBeta($feature); // anthropic-beta 헤더 설정
$client->setApiUrl($url); // API URL 설정 $client->setApiUrl($url); // API URL 설정
``` ```
### 임베딩 메서드 (getEmbedding)
OpenAI, Google, Ollama, VoyageAI 클라이언트에서 텍스트 임베딩을 얻을 수 있습니다.
```php
// OpenAI (dimensions 옵션 지원)
$client = ptyAIConfig::createClient('openai');
$result = $client->getEmbedding($text, 'text-embedding-3-small', 512);
// Google Gemini
$client = ptyAIConfig::createClient('google');
$result = $client->getEmbedding($text, 'text-embedding-004');
// Ollama
$client = ptyAIConfig::createClient('ollama');
$result = $client->getEmbedding($text, 'nomic-embed-text');
// VoyageAI (inputType 옵션 지원: 'query' 또는 'document')
$client = ptyAIConfig::createClient('voyageai');
$result = $client->getEmbedding($text, 'voyage-3-large', 'query');
// 결과
$embedding = $result['embedding']; // 벡터 배열
$usage = $result['usage']; // 토큰 사용량 (OpenAI, VoyageAI)
```
**참고:** `anthropic`(Claude)는 임베딩 API를 지원하지 않습니다.
### 토큰 사용량 응답 위치 ### 토큰 사용량 응답 위치
| Provider | 입력 토큰 | 출력 토큰 | | Provider | 입력 토큰 | 출력 토큰 |
...@@ -1249,6 +1341,124 @@ $client->setApiUrl($url); // API URL 설정 ...@@ -1249,6 +1341,124 @@ $client->setApiUrl($url); // API URL 설정
| google | `usageMetadata.promptTokenCount` | `usageMetadata.candidatesTokenCount` | | google | `usageMetadata.promptTokenCount` | `usageMetadata.candidatesTokenCount` |
| ollama | `prompt_eval_count` | `eval_count` | | ollama | `prompt_eval_count` | `eval_count` |
## ptyAIGetVector - 벡터 임베딩 CLI 도구
텍스트를 벡터 임베딩으로 변환하는 CLI 도구입니다.
텍스트 인자가 없으면 stdin에서 읽습니다 (Ctrl+D로 완료).
### 기본 사용법
```bash
./ptyAIGetVector ["텍스트"] [옵션]
```
### CLI 옵션
| 옵션 | 설명 | 기본값 |
|------|------|--------|
| `--ai=섹션명` | INI 파일 섹션 | `default` |
| `--model=모델명` | 임베딩 모델 오버라이드 | 프로바이더별 기본값 |
| `--dimensions=N` | 출력 벡터 차원 수 (OpenAI text-embedding-3-* 전용) | - |
| `--verbose` | 상세 정보 출력 | - |
| `--json` | 요청/응답 전체를 JSON으로 출력 | - |
| `--edit` | 설정 파일을 에디터로 열기 | - |
| `--help` | 도움말 출력 | - |
### 지원 프로바이더 및 모델
| Provider | 기본 모델 | 차원 | 비고 |
|----------|----------|------|------|
| `openai` | text-embedding-3-small | 1536 | 저렴, dimensions 옵션 지원 |
| `openai` | text-embedding-3-large | 3072 | 고성능, dimensions 옵션 지원 |
| `openai` | text-embedding-ada-002 | 1536 | 레거시 |
| `google` | text-embedding-004 | 768 | Gemini |
| `ollama` | nomic-embed-text | 768 | 로컬 |
| `ollama` | mxbai-embed-large | 1024 | 로컬, 고성능 |
| `ollama` | all-minilm | 384 | 경량 |
| `voyageai` | voyage-3-large | 1024 | 고성능 |
| `voyageai` | voyage-3 | 1024 | 표준 |
| `voyageai` | voyage-3-lite | 512 | 경량 |
| `voyageai` | voyage-code-3 | 1024 | 코드 특화 |
**주의:** `anthropic`(Claude)는 임베딩 API를 지원하지 않습니다.
### 사용 예시
```bash
# 기본 설정으로 임베딩
./ptyAIGetVector "안녕하세요"
# OpenAI 사용
./ptyAIGetVector "Hello" --ai=openai
# 고성능 모델 사용
./ptyAIGetVector "Hello" --ai=openai --model=text-embedding-3-large
# 차원 축소 (512차원)
./ptyAIGetVector "Hello" --ai=openai --dimensions=512
# Ollama 로컬 임베딩
./ptyAIGetVector "테스트" --ai=ollama --verbose
# VoyageAI 사용
./ptyAIGetVector "테스트" --ai=voyageai
# 전체 JSON 출력
./ptyAIGetVector "테스트" --json
```
### 입력 방법
```bash
# stdin에서 입력 (Ctrl+D로 완료)
./ptyAIGetVector --ai=voyageai
# 파일에서 입력 (리다이렉션)
./ptyAIGetVector --ai=voyageai < document.txt
# 파일에서 입력 (파이프)
cat document.txt | ./ptyAIGetVector --ai=voyageai
# 클립보드에서 입력 (macOS)
./ptyAIGetVector "$(pbpaste)" --ai=voyageai
# 클립보드에서 입력 (Linux)
./ptyAIGetVector "$(xclip -o)" --ai=voyageai
```
### 출력 형식
**기본 출력:**
```json
[0.123, -0.456, 0.789, ...]
```
**--json 출력:**
```json
{
"request": {
"provider": "openai",
"model": "text-embedding-3-small",
"text": "안녕하세요"
},
"response": {
"embedding": [0.123, -0.456, ...],
"dimensions": 1536,
"elapsed_ms": 234,
"tokens": 5
}
}
```
### API 엔드포인트
| Provider | Endpoint |
|----------|----------|
| openai | `POST https://api.openai.com/v1/embeddings` |
| google | `POST https://generativelanguage.googleapis.com/v1beta/models/{model}:embedContent` |
| ollama | `POST http://localhost:11434/api/embeddings` |
| voyageai | `POST https://api.voyageai.com/v1/embeddings` |
## ptyGit* 스크립트 ## ptyGit* 스크립트
Git 관련 CLI 도구 모음입니다. Git 관련 CLI 도구 모음입니다.
......
...@@ -145,6 +145,7 @@ apiUrl=http://localhost:11434 ...@@ -145,6 +145,7 @@ apiUrl=http://localhost:11434
| 스크립트 | 설명 | | 스크립트 | 설명 |
|---------|------| |---------|------|
| `ptyAIGetMessage` | AI API 메시지 전송 (Claude, GPT, Gemini, Ollama) | | `ptyAIGetMessage` | AI API 메시지 전송 (Claude, GPT, Gemini, Ollama) |
| `ptyAIGetVector` | 텍스트 벡터 임베딩 (OpenAI, Gemini, Ollama, VoyageAI) |
| `ptyAIStatistics` | AI 사용량 통계 | | `ptyAIStatistics` | AI 사용량 통계 |
### Git 도구 ### Git 도구
...@@ -242,6 +243,18 @@ ptyAIGetMessage "Hello" --ai=openai ...@@ -242,6 +243,18 @@ ptyAIGetMessage "Hello" --ai=openai
# JSON 출력 # JSON 출력
ptyAIGetMessage "테스트" --json ptyAIGetMessage "테스트" --json
# 벡터 임베딩 (OpenAI)
ptyAIGetVector "안녕하세요" --ai=openai
# 벡터 임베딩 + 차원 지정
ptyAIGetVector "Hello" --ai=openai --dimensions=512
# Ollama 로컬 임베딩
ptyAIGetVector "테스트" --ai=ollama
# VoyageAI 임베딩
ptyAIGetVector "테스트" --ai=voyageai
``` ```
### 크론 + 중복 실행 제어 ### 크론 + 중복 실행 제어
......
...@@ -37,9 +37,12 @@ if (isset($options['edit'])) { ...@@ -37,9 +37,12 @@ if (isset($options['edit'])) {
exit(0); exit(0);
} }
// 도움말 또는 필수 인자 확인 // 도움말
if (empty($positionalArgs) || isset($options['help'])) { if (isset($options['help'])) {
fwrite(STDERR, "사용법: {$argv[0]} \"메시지\" [옵션]\n"); fwrite(STDERR, "사용법: {$argv[0]} [\"메시지\"] [옵션]\n");
fwrite(STDERR, "\n");
fwrite(STDERR, "AI API를 통해 메시지를 전송하고 응답을 받습니다.\n");
fwrite(STDERR, "메시지 인자가 없으면 stdin에서 읽습니다 (Ctrl+D로 완료).\n");
fwrite(STDERR, "\n"); fwrite(STDERR, "\n");
fwrite(STDERR, "옵션:\n"); fwrite(STDERR, "옵션:\n");
fwrite(STDERR, " --ai=섹션명 INI 파일 섹션 (기본값: default)\n"); fwrite(STDERR, " --ai=섹션명 INI 파일 섹션 (기본값: default)\n");
...@@ -59,6 +62,13 @@ if (empty($positionalArgs) || isset($options['help'])) { ...@@ -59,6 +62,13 @@ if (empty($positionalArgs) || isset($options['help'])) {
fwrite(STDERR, "anthropic-beta 예시:\n"); fwrite(STDERR, "anthropic-beta 예시:\n");
fwrite(STDERR, " context-1m-2025-08-07, interleaved-thinking-2025-05-14, max-tokens-3-5-sonnet-2024-07-15\n"); fwrite(STDERR, " context-1m-2025-08-07, interleaved-thinking-2025-05-14, max-tokens-3-5-sonnet-2024-07-15\n");
fwrite(STDERR, "\n"); fwrite(STDERR, "\n");
fwrite(STDERR, "입력 방법:\n");
fwrite(STDERR, " {$argv[0]} --ai=openai # stdin에서 입력 (Ctrl+D로 완료)\n");
fwrite(STDERR, " {$argv[0]} --ai=openai < prompt.txt # 파일에서 입력 (리다이렉션)\n");
fwrite(STDERR, " cat prompt.txt | {$argv[0]} --ai=openai # 파일에서 입력 (파이프)\n");
fwrite(STDERR, " {$argv[0]} \"\$(pbpaste)\" --ai=openai # 클립보드에서 입력 (macOS)\n");
fwrite(STDERR, " {$argv[0]} \"\$(xclip -o)\" --ai=openai # 클립보드에서 입력 (Linux)\n");
fwrite(STDERR, "\n");
// 현재 설정 파일 내용 표시 // 현재 설정 파일 내용 표시
$configPath = ptyAIConfig::getConfigPath(); $configPath = ptyAIConfig::getConfigPath();
...@@ -93,10 +103,25 @@ if (empty($positionalArgs) || isset($options['help'])) { ...@@ -93,10 +103,25 @@ if (empty($positionalArgs) || isset($options['help'])) {
fwrite(STDERR, "\n"); fwrite(STDERR, "\n");
fwrite(STDERR, ptyAIConfig::getConfigExample() . "\n"); fwrite(STDERR, ptyAIConfig::getConfigExample() . "\n");
} }
exit(isset($options['help']) ? 0 : 1); exit(0);
} }
$message = $positionalArgs[0]; // 메시지 입력: 인자 또는 stdin
if (empty($positionalArgs)) {
// stdin에서 읽기
if (posix_isatty(STDIN)) {
fwrite(STDERR, "메시지를 입력하세요 (Ctrl+D로 완료):\n");
}
$message = file_get_contents('php://stdin');
$message = trim($message);
if (empty($message)) {
fwrite(STDERR, "Error: 메시지가 필요합니다.\n");
fwrite(STDERR, "도움말: {$argv[0]} --help\n");
exit(1);
}
} else {
$message = $positionalArgs[0];
}
try { try {
// AI 클라이언트 연결 // AI 클라이언트 연결
......
#!/usr/bin/env php
<?php
/**
* ptyAIGetVector
*
* AI API를 통해 텍스트의 벡터 임베딩을 얻는 CLI 도구
*
* 설정 파일: ~/.ptyAIConfig.ini
*
* Usage: ./ptyAIGetVector "텍스트" [--ai=섹션명] [--model=모델명]
*
* 지원 provider:
* - openai: text-embedding-3-small, text-embedding-3-large, text-embedding-ada-002
* - google: text-embedding-004
* - ollama: nomic-embed-text, mxbai-embed-large, all-minilm
*
* 참고: anthropic(Claude)는 임베딩 API를 제공하지 않습니다.
*/
namespace platyFramework;
require_once __DIR__ . '/ptyLibrary_PHP/cli/ptyCliOptionParser.php';
require_once __DIR__ . '/ptyLibrary_PHP/ai/ptyAIConfig.php';
// 인자 파싱
$parsed = ptyCliOptionParser::parse($argv);
$positionalArgs = $parsed['positional'];
$options = $parsed['options'];
$aiSection = isset($options['ai']) ? $options['ai'] : 'default';
$modelOverride = isset($options['model']) ? $options['model'] : null;
$verbose = isset($options['verbose']);
$jsonOutput = isset($options['json']);
$dimensions = isset($options['dimensions']) ? intval($options['dimensions']) : null;
// --edit 옵션: vi로 설정 파일 열기
if (isset($options['edit'])) {
$editor = getenv('EDITOR') ? getenv('EDITOR') : 'vi';
$configPath = ptyAIConfig::getConfigPath();
passthru("$editor " . escapeshellarg($configPath));
exit(0);
}
// 도움말
if (isset($options['help'])) {
printHelp($argv[0]);
exit(0);
}
// 텍스트 입력: 인자 또는 stdin
if (empty($positionalArgs)) {
// stdin에서 읽기
if (posix_isatty(STDIN)) {
fwrite(STDERR, "텍스트를 입력하세요 (Ctrl+D로 완료):\n");
}
$text = file_get_contents('php://stdin');
$text = trim($text);
if (empty($text)) {
fwrite(STDERR, "Error: 텍스트가 필요합니다.\n");
fwrite(STDERR, "도움말: {$argv[0]} --help\n");
exit(1);
}
} else {
$text = $positionalArgs[0];
}
try {
// AI 설정 로드
$config = ptyAIConfig::load($aiSection);
$provider = $config['provider'];
// anthropic은 임베딩 API 미지원
if ($provider === 'anthropic') {
fwrite(STDERR, "Error: anthropic(Claude)는 임베딩 API를 지원하지 않습니다.\n");
fwrite(STDERR, "openai, google, ollama 섹션을 사용하세요.\n");
exit(1);
}
// 클라이언트 생성
$client = ptyAIConfig::createClient($aiSection);
// 디버그 모드 설정
if ($verbose && method_exists($client, 'setDebug')) {
$client->setDebug(true);
}
// 임베딩 모델 결정
$model = $modelOverride ? $modelOverride : ptyAIConfig::getDefaultEmbeddingModel($provider);
if ($verbose) {
fwrite(STDERR, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
fwrite(STDERR, "Provider: {$provider}\n");
fwrite(STDERR, "Model: {$model}\n");
fwrite(STDERR, "Section: {$aiSection}\n");
if ($dimensions) {
fwrite(STDERR, "Dimensions: {$dimensions}\n");
}
fwrite(STDERR, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
fwrite(STDERR, "Text: " . mb_substr($text, 0, 100) . (mb_strlen($text) > 100 ? '...' : '') . "\n");
fwrite(STDERR, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
fwrite(STDERR, "API 호출 중...\n");
}
$startTime = microtime(true);
// 임베딩 API 호출 (클래스 메서드 사용)
if ($provider === 'openai') {
$result = $client->getEmbedding($text, $model, $dimensions);
} else {
$result = $client->getEmbedding($text, $model);
}
$elapsed = round((microtime(true) - $startTime) * 1000);
if ($result === false) {
fwrite(STDERR, "Error: 임베딩 API 호출에 실패했습니다.\n");
exit(1);
}
$embedding = $result['embedding'];
$response = $result['response'];
$usage = isset($result['usage']) ? $result['usage'] : null;
if ($verbose) {
fwrite(STDERR, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
fwrite(STDERR, "응답 시간: {$elapsed}ms\n");
fwrite(STDERR, "벡터 차원: " . count($embedding) . "\n");
if ($usage) {
fwrite(STDERR, "토큰 사용량: {$usage} tokens\n");
}
fwrite(STDERR, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
}
// 출력
if ($jsonOutput) {
$output = array(
'request' => array(
'provider' => $provider,
'model' => $model,
'text' => $text,
),
'response' => array(
'embedding' => $embedding,
'dimensions' => count($embedding),
'elapsed_ms' => $elapsed,
),
);
if ($dimensions) {
$output['request']['dimensions'] = $dimensions;
}
if ($usage) {
$output['response']['tokens'] = $usage;
}
echo json_encode($output, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n";
} else {
// 벡터만 출력 (JSON 배열)
echo json_encode($response) . "\n";
}
} catch (\Exception $e) {
fwrite(STDERR, "Error: " . $e->getMessage() . "\n");
exit(1);
}
/**
* 도움말 출력
*/
function printHelp($scriptName)
{
fwrite(STDERR, "사용법: {$scriptName} [\"텍스트\"] [옵션]\n");
fwrite(STDERR, "\n");
fwrite(STDERR, "텍스트를 벡터 임베딩으로 변환합니다.\n");
fwrite(STDERR, "텍스트 인자가 없으면 stdin에서 읽습니다 (Ctrl+D로 완료).\n");
fwrite(STDERR, "\n");
fwrite(STDERR, "옵션:\n");
fwrite(STDERR, " --ai=섹션명 INI 파일 섹션 (기본값: default)\n");
fwrite(STDERR, " --model=모델명 임베딩 모델 오버라이드\n");
fwrite(STDERR, " --dimensions=N 출력 벡터 차원 수 (OpenAI text-embedding-3-* 전용)\n");
fwrite(STDERR, " --verbose 상세 정보 출력\n");
fwrite(STDERR, " --json 요청/응답 전체를 JSON으로 출력\n");
fwrite(STDERR, " --edit 설정 파일을 에디터로 열기\n");
fwrite(STDERR, " --help 도움말 출력\n");
fwrite(STDERR, "\n");
fwrite(STDERR, "지원 프로바이더 및 모델:\n");
fwrite(STDERR, " openai:\n");
fwrite(STDERR, " - text-embedding-3-small (기본값, 1536차원, 저렴)\n");
fwrite(STDERR, " - text-embedding-3-large (3072차원, 고성능)\n");
fwrite(STDERR, " - text-embedding-ada-002 (1536차원, 레거시)\n");
fwrite(STDERR, "\n");
fwrite(STDERR, " google:\n");
fwrite(STDERR, " - text-embedding-004 (기본값, 768차원)\n");
fwrite(STDERR, "\n");
fwrite(STDERR, " ollama:\n");
fwrite(STDERR, " - nomic-embed-text (기본값, 768차원)\n");
fwrite(STDERR, " - mxbai-embed-large (1024차원)\n");
fwrite(STDERR, " - all-minilm (384차원, 경량)\n");
fwrite(STDERR, "\n");
fwrite(STDERR, " voyageai:\n");
fwrite(STDERR, " - voyage-3-large (기본값, 1024차원, 고성능)\n");
fwrite(STDERR, " - voyage-3 (1024차원)\n");
fwrite(STDERR, " - voyage-3-lite (512차원, 경량)\n");
fwrite(STDERR, " - voyage-code-3 (1024차원, 코드 특화)\n");
fwrite(STDERR, "\n");
fwrite(STDERR, "주의: anthropic(Claude)는 임베딩 API를 지원하지 않습니다.\n");
fwrite(STDERR, "\n");
fwrite(STDERR, "예시:\n");
fwrite(STDERR, " {$scriptName} \"안녕하세요\" # 기본 설정으로 임베딩\n");
fwrite(STDERR, " {$scriptName} \"Hello\" --ai=openai # OpenAI 사용\n");
fwrite(STDERR, " {$scriptName} \"Hello\" --ai=openai --model=text-embedding-3-large\n");
fwrite(STDERR, " {$scriptName} \"Hello\" --ai=openai --dimensions=512 # 차원 축소\n");
fwrite(STDERR, " {$scriptName} \"테스트\" --ai=ollama --verbose # Ollama + 상세 로그\n");
fwrite(STDERR, " {$scriptName} \"테스트\" --ai=voyageai # VoyageAI 사용\n");
fwrite(STDERR, " {$scriptName} \"테스트\" --json # 전체 JSON 출력\n");
fwrite(STDERR, "\n");
fwrite(STDERR, "입력 방법:\n");
fwrite(STDERR, " {$scriptName} --ai=voyageai # stdin에서 입력 (Ctrl+D로 완료)\n");
fwrite(STDERR, " {$scriptName} --ai=voyageai < file.txt # 파일에서 입력 (리다이렉션)\n");
fwrite(STDERR, " cat file.txt | {$scriptName} --ai=voyageai # 파일에서 입력 (파이프)\n");
fwrite(STDERR, " {$scriptName} \"\$(pbpaste)\" --ai=voyageai # 클립보드에서 입력 (macOS)\n");
fwrite(STDERR, " {$scriptName} \"\$(xclip -o)\" --ai=voyageai # 클립보드에서 입력 (Linux)\n");
fwrite(STDERR, "\n");
fwrite(STDERR, "설정 파일: ~/.ptyAIConfig.ini\n");
fwrite(STDERR, ptyAIConfig::getConfigExample() . "\n");
}
...@@ -281,4 +281,114 @@ class ChatGPTAPIModel extends model ...@@ -281,4 +281,114 @@ class ChatGPTAPIModel extends model
$response = $this->get($message); $response = $this->get($message);
return $this->extractText($response); return $this->extractText($response);
} }
/**
* 텍스트 임베딩 API 호출
* @param string $text 임베딩할 텍스트
* @param string $model 임베딩 모델 (기본값: text-embedding-3-small)
* @param int|null $dimensions 출력 차원 수 (text-embedding-3-* 전용)
* @return array|false ['embedding' => [...], 'usage' => N] 또는 false
*/
public function getEmbedding($text, $model = null, $dimensions = null)
{
if ($model === null) {
$model = 'text-embedding-3-small';
}
$url = 'https://api.openai.com/v1/embeddings';
$data = array(
'input' => $text,
'model' => $model,
);
// text-embedding-3-* 모델은 dimensions 지원
if ($dimensions !== null && strpos($model, 'text-embedding-3') === 0) {
$data['dimensions'] = $dimensions;
}
$headers = array(
'Content-Type: application/json',
'Authorization: Bearer ' . $this->apiKey,
);
// 디버그 출력
if ($this->debug) {
fwrite(STDERR, "━━━━━━━━━━━━ CURL REQUEST ━━━━━━━━━━━━\n");
fwrite(STDERR, "URL: {$url}\n");
fwrite(STDERR, "Method: POST\n");
fwrite(STDERR, "Headers:\n");
foreach ($headers as $header) {
if (strpos($header, 'Authorization:') === 0) {
$key = substr($this->apiKey, 0, 12) . '...';
fwrite(STDERR, " Authorization: Bearer {$key}\n");
} else {
fwrite(STDERR, " {$header}\n");
}
}
fwrite(STDERR, "Body:\n");
fwrite(STDERR, " " . json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n");
fwrite(STDERR, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
}
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
// 디버그 출력 (응답)
if ($this->debug) {
fwrite(STDERR, "━━━━━━━━━━━━ CURL RESPONSE ━━━━━━━━━━━━\n");
fwrite(STDERR, "HTTP Code: {$httpCode}\n");
if ($error) {
fwrite(STDERR, "cURL Error: {$error}\n");
}
fwrite(STDERR, "Response:\n");
$prettyResponse = json_decode($response, true);
if ($prettyResponse) {
if (isset($prettyResponse['data'][0]['embedding'])) {
$prettyResponse['data'][0]['embedding'] = '[' . count($prettyResponse['data'][0]['embedding']) . ' dimensions...]';
}
fwrite(STDERR, " " . json_encode($prettyResponse, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n");
} else {
fwrite(STDERR, " {$response}\n");
}
fwrite(STDERR, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
}
if ($error) {
error_log("OpenAI Embedding API cURL Error: " . $error);
return false;
}
if ($httpCode !== 200) {
error_log("OpenAI Embedding API HTTP Error: " . $httpCode . " - " . $response);
return false;
}
$result = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
error_log("OpenAI Embedding API JSON Decode Error: " . json_last_error_msg());
return false;
}
if (!isset($result['data'][0]['embedding'])) {
return false;
}
return array(
'response' => $result,
'embedding' => $result['data'][0]['embedding'],
'usage' => isset($result['usage']['total_tokens']) ? $result['usage']['total_tokens'] : null,
'_raw' => $result,
);
}
} }
\ No newline at end of file
...@@ -214,4 +214,72 @@ class GeminiAPIModel extends model ...@@ -214,4 +214,72 @@ class GeminiAPIModel extends model
$response = $this->get($message); $response = $this->get($message);
return $this->extractText($response); return $this->extractText($response);
} }
/**
* 텍스트 임베딩 API 호출
* @param string $text 임베딩할 텍스트
* @param string $model 임베딩 모델 (기본값: text-embedding-004)
* @return array|false ['embedding' => [...], 'usage' => null] 또는 false
*/
public function getEmbedding($text, $model = null)
{
if ($model === null) {
$model = 'text-embedding-004';
}
$url = "{$this->apiBaseUrl}/{$model}:embedContent?key={$this->apiKey}";
$data = array(
'model' => "models/{$model}",
'content' => array(
'parts' => array(
array('text' => $text)
)
)
);
$headers = array(
'Content-Type: application/json',
);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($error) {
error_log("Gemini Embedding API cURL Error: " . $error);
return false;
}
if ($httpCode !== 200) {
error_log("Gemini Embedding API HTTP Error: " . $httpCode . " - " . $response);
return false;
}
$result = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
error_log("Gemini Embedding API JSON Decode Error: " . json_last_error_msg());
return false;
}
if (!isset($result['embedding']['values'])) {
return false;
}
return array(
'response' => $result,
'embedding' => $result['embedding']['values'],
'usage' => null, // Gemini doesn't return token usage for embeddings
'_raw' => $result,
);
}
} }
...@@ -265,4 +265,103 @@ class OllamaAPIModel extends model ...@@ -265,4 +265,103 @@ class OllamaAPIModel extends model
$response = $this->get($message); $response = $this->get($message);
return $this->extractText($response); return $this->extractText($response);
} }
/**
* 텍스트 임베딩 API 호출
* @param string $text 임베딩할 텍스트
* @param string $model 임베딩 모델 (기본값: nomic-embed-text)
* @return array|false ['embedding' => [...], 'usage' => null] 또는 false
*/
public function getEmbedding($text, $model = null)
{
if ($model === null) {
$model = 'nomic-embed-text';
}
$url = $this->apiUrl . '/api/embeddings';
$data = array(
'model' => $model,
'prompt' => $text,
);
$headers = array(
'Content-Type: application/json',
);
// 디버그 출력
if ($this->debug) {
fwrite(STDERR, "━━━━━━━━━━━━ CURL REQUEST ━━━━━━━━━━━━\n");
fwrite(STDERR, "URL: {$url}\n");
fwrite(STDERR, "Method: POST\n");
fwrite(STDERR, "Headers:\n");
foreach ($headers as $header) {
fwrite(STDERR, " {$header}\n");
}
fwrite(STDERR, "Body:\n");
fwrite(STDERR, " " . json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n");
fwrite(STDERR, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
}
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 300);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
// 디버그 출력 (응답)
if ($this->debug) {
fwrite(STDERR, "━━━━━━━━━━━━ CURL RESPONSE ━━━━━━━━━━━━\n");
fwrite(STDERR, "HTTP Code: {$httpCode}\n");
if ($error) {
fwrite(STDERR, "cURL Error: {$error}\n");
}
fwrite(STDERR, "Response:\n");
$prettyResponse = json_decode($response, true);
if ($prettyResponse) {
if (isset($prettyResponse['embedding'])) {
$prettyResponse['embedding'] = '[' . count($prettyResponse['embedding']) . ' dimensions...]';
}
fwrite(STDERR, " " . json_encode($prettyResponse, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n");
} else {
fwrite(STDERR, " {$response}\n");
}
fwrite(STDERR, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
}
if ($error) {
error_log("Ollama Embedding API cURL Error: " . $error);
return false;
}
if ($httpCode !== 200) {
error_log("Ollama Embedding API HTTP Error: " . $httpCode . " - " . $response);
return false;
}
$result = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
error_log("Ollama Embedding API JSON Decode Error: " . json_last_error_msg());
return false;
}
if (!isset($result['embedding'])) {
return false;
}
return array(
'response' => $result,
'embedding' => $result['embedding'],
'usage' => null, // Ollama doesn't return token usage for embeddings
'_raw' => $result,
);
}
} }
...@@ -17,6 +17,7 @@ require_once __DIR__ . "/claude.api.common.model.php"; ...@@ -17,6 +17,7 @@ require_once __DIR__ . "/claude.api.common.model.php";
require_once __DIR__ . "/chatgpt.api.common.model.php"; require_once __DIR__ . "/chatgpt.api.common.model.php";
require_once __DIR__ . "/gemini.api.common.model.php"; require_once __DIR__ . "/gemini.api.common.model.php";
require_once __DIR__ . "/ollama.api.common.model.php"; require_once __DIR__ . "/ollama.api.common.model.php";
require_once __DIR__ . "/voyage.api.common.model.php";
/** /**
* AI 설정 로더 클래스 * AI 설정 로더 클래스
...@@ -92,7 +93,7 @@ class ptyAIConfig ...@@ -92,7 +93,7 @@ class ptyAIConfig
throw new \Exception("AI 설정 [{$section}] 섹션에 필수 필드 'apiKey'가 없습니다."); throw new \Exception("AI 설정 [{$section}] 섹션에 필수 필드 'apiKey'가 없습니다.");
} }
} }
$validProviders = ['anthropic', 'openai', 'google', 'ollama']; $validProviders = ['anthropic', 'openai', 'google', 'ollama', 'voyageai'];
if (!in_array($provider, $validProviders)) { if (!in_array($provider, $validProviders)) {
throw new \Exception("AI 설정 [{$section}] 섹션의 provider '{$provider}'는 지원되지 않습니다.\n지원되는 provider: " . implode(', ', $validProviders)); throw new \Exception("AI 설정 [{$section}] 섹션의 provider '{$provider}'는 지원되지 않습니다.\n지원되는 provider: " . implode(', ', $validProviders));
} }
...@@ -122,8 +123,23 @@ class ptyAIConfig ...@@ -122,8 +123,23 @@ class ptyAIConfig
'openai' => 'gpt-4o', 'openai' => 'gpt-4o',
'google' => 'gemini-2.0-flash-exp', 'google' => 'gemini-2.0-flash-exp',
'ollama' => 'llama3', 'ollama' => 'llama3',
'voyageai' => 'voyage-3-large', // 임베딩 전용
]; ];
return $defaults[$provider] ?? null; return isset($defaults[$provider]) ? $defaults[$provider] : null;
}
/**
* provider별 기본 임베딩 모델 반환
*/
public static function getDefaultEmbeddingModel($provider)
{
$defaults = [
'openai' => 'text-embedding-3-small',
'google' => 'text-embedding-004',
'ollama' => 'nomic-embed-text',
'voyageai' => 'voyage-3-large',
];
return isset($defaults[$provider]) ? $defaults[$provider] : null;
} }
/** /**
...@@ -165,6 +181,11 @@ class ptyAIConfig ...@@ -165,6 +181,11 @@ class ptyAIConfig
} }
return $client; return $client;
case 'voyageai':
$client = new VoyageAPIModel($config['apiKey']);
$client->setModel($config['model']);
return $client;
default: default:
throw new \Exception("지원되지 않는 provider: {$config['provider']}"); throw new \Exception("지원되지 않는 provider: {$config['provider']}");
} }
...@@ -221,9 +242,15 @@ provider=ollama ...@@ -221,9 +242,15 @@ provider=ollama
model=llama3 model=llama3
apiUrl=http://localhost:11434 apiUrl=http://localhost:11434
# 지원 provider: anthropic, openai, google, ollama [voyageai]
provider=voyageai
model=voyage-3-large
apiKey=your_voyageai_api_key
# 지원 provider: anthropic, openai, google, ollama, voyageai
# anthropic-beta: Claude 베타 기능 사용 시 헤더값 (예: context-1m-2025-08-07) # anthropic-beta: Claude 베타 기능 사용 시 헤더값 (예: context-1m-2025-08-07)
# apiUrl: ollama 서버 URL (기본값: http://localhost:11434) # apiUrl: ollama 서버 URL (기본값: http://localhost:11434)
# voyageai: 임베딩 전용 API (voyage-3-large, voyage-3, voyage-3-lite, voyage-code-3 등)
EOT; EOT;
} }
} }
<?php
namespace platyFramework;
require_once(__DIR__ . "/../ptycommon/model.php");
/**
* VoyageAI API Model
* VoyageAI 임베딩 API를 사용하기 위한 클래스
*
* 참고: VoyageAI는 임베딩 전용 API입니다. 텍스트 생성(get/getSimple)은 지원하지 않습니다.
*/
class VoyageAPIModel extends model
{
private $apiKey;
private $model = 'voyage-3-large';
private $apiUrl = 'https://api.voyageai.com/v1/embeddings';
private $debug = false;
/**
* 생성자
* @param string $apiKey VoyageAI API 키
*/
public function __construct($apiKey = null)
{
parent::__construct();
$this->apiKey = $apiKey;
}
/**
* API 키 설정
* @param string $apiKey
* @return $this
*/
public function setAPIKey($apiKey)
{
$this->apiKey = $apiKey;
return $this;
}
/**
* 모델 설정
* @param string $model 사용할 VoyageAI 모델명
* @return $this
*/
public function setModel($model)
{
$this->model = $model;
return $this;
}
/**
* 디버그 모드 설정
* @param bool $debug 디버그 모드 활성화 여부
* @return $this
*/
public function setDebug($debug)
{
$this->debug = $debug;
return $this;
}
/**
* 텍스트 생성 API (미지원)
* VoyageAI는 임베딩 전용 API이므로 텍스트 생성을 지원하지 않습니다.
*
* @param string $message
* @return false
*/
public function get($message)
{
error_log("VoyageAI는 텍스트 생성을 지원하지 않습니다. getEmbedding()을 사용하세요.");
return false;
}
/**
* 간단한 텍스트 응답 (미지원)
* @param string $message
* @return false
*/
public function getSimple($message)
{
return false;
}
/**
* 응답에서 텍스트 추출 (미지원)
* @param array $response
* @return false
*/
public function extractText($response)
{
return false;
}
/**
* 텍스트 임베딩 API 호출
* @param string|array $text 임베딩할 텍스트 (단일 문자열 또는 배열)
* @param string $model 임베딩 모델 (기본값: voyage-3-large)
* @param string|null $inputType 입력 유형 (query, document, null)
* @return array|false ['embedding' => [...], 'usage' => N] 또는 false
*/
public function getEmbedding($text, $model = null, $inputType = null)
{
if ($model === null) {
$model = $this->model;
}
// 단일 문자열을 배열로 변환
$input = is_array($text) ? $text : array($text);
$data = array(
'input' => $input,
'model' => $model,
);
// input_type 추가 (query: 검색 쿼리, document: 저장할 문서)
if ($inputType !== null) {
$data['input_type'] = $inputType;
}
$headers = array(
'Content-Type: application/json',
'Authorization: Bearer ' . $this->apiKey,
);
// 디버그 출력
if ($this->debug) {
fwrite(STDERR, "━━━━━━━━━━━━ CURL REQUEST ━━━━━━━━━━━━\n");
fwrite(STDERR, "URL: {$this->apiUrl}\n");
fwrite(STDERR, "Method: POST\n");
fwrite(STDERR, "Headers:\n");
foreach ($headers as $header) {
if (strpos($header, 'Authorization:') === 0) {
$key = substr($this->apiKey, 0, 12) . '...';
fwrite(STDERR, " Authorization: Bearer {$key}\n");
} else {
fwrite(STDERR, " {$header}\n");
}
}
fwrite(STDERR, "Body:\n");
fwrite(STDERR, " " . json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n");
fwrite(STDERR, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
}
$ch = curl_init($this->apiUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
// 디버그 출력 (응답)
if ($this->debug) {
fwrite(STDERR, "━━━━━━━━━━━━ CURL RESPONSE ━━━━━━━━━━━━\n");
fwrite(STDERR, "HTTP Code: {$httpCode}\n");
if ($error) {
fwrite(STDERR, "cURL Error: {$error}\n");
}
fwrite(STDERR, "Response:\n");
$prettyResponse = json_decode($response, true);
if ($prettyResponse) {
if (isset($prettyResponse['data'][0]['embedding'])) {
$prettyResponse['data'][0]['embedding'] = '[' . count($prettyResponse['data'][0]['embedding']) . ' dimensions...]';
}
fwrite(STDERR, " " . json_encode($prettyResponse, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n");
} else {
fwrite(STDERR, " {$response}\n");
}
fwrite(STDERR, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
}
if ($error) {
error_log("VoyageAI Embedding API cURL Error: " . $error);
return false;
}
if ($httpCode !== 200) {
error_log("VoyageAI Embedding API HTTP Error: " . $httpCode . " - " . $response);
return false;
}
$result = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
error_log("VoyageAI Embedding API JSON Decode Error: " . json_last_error_msg());
return false;
}
if (!isset($result['data'][0]['embedding'])) {
return false;
}
return array(
'response' => $result,
'embedding' => $result['data'][0]['embedding'],
'usage' => isset($result['usage']['total_tokens']) ? $result['usage']['total_tokens'] : null,
'_raw' => $result,
);
}
/**
* 여러 텍스트의 임베딩을 한 번에 얻기
* @param array $texts 임베딩할 텍스트 배열
* @param string $model 임베딩 모델
* @param string|null $inputType 입력 유형
* @return array|false ['embeddings' => [[...], [...]], 'usage' => N] 또는 false
*/
public function getEmbeddings($texts, $model = null, $inputType = null)
{
if ($model === null) {
$model = $this->model;
}
$data = array(
'input' => $texts,
'model' => $model,
);
if ($inputType !== null) {
$data['input_type'] = $inputType;
}
$headers = array(
'Content-Type: application/json',
'Authorization: Bearer ' . $this->apiKey,
);
$ch = curl_init($this->apiUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($error) {
error_log("VoyageAI Embedding API cURL Error: " . $error);
return false;
}
if ($httpCode !== 200) {
error_log("VoyageAI Embedding API HTTP Error: " . $httpCode . " - " . $response);
return false;
}
$result = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
error_log("VoyageAI Embedding API JSON Decode Error: " . json_last_error_msg());
return false;
}
if (!isset($result['data'])) {
return false;
}
$embeddings = array();
foreach ($result['data'] as $item) {
$embeddings[] = $item['embedding'];
}
return array(
'response' => $result,
'embeddings' => $embeddings,
'usage' => isset($result['usage']['total_tokens']) ? $result['usage']['total_tokens'] : null,
'_raw' => $result,
);
}
}
...@@ -30,10 +30,10 @@ $showMarkdown = isset($options['markdown']); ...@@ -30,10 +30,10 @@ $showMarkdown = isset($options['markdown']);
// 1. --help: 최우선 처리 // 1. --help: 최우선 처리
// ============================================================ // ============================================================
if (isset($options['help'])) { if (isset($options['help'])) {
echo "사용법: {$argv[0]} \"SQL\" [옵션]\n"; echo "사용법: {$argv[0]} [\"SQL\"] [옵션]\n";
echo "\n"; echo "\n";
echo "인자:\n"; echo "MySQL 쿼리를 실행하고 결과를 출력합니다.\n";
echo " SQL 실행할 SQL 쿼리\n"; echo "SQL 인자가 없으면 stdin에서 읽습니다 (Ctrl+D로 완료).\n";
echo "\n"; echo "\n";
echo "옵션:\n"; echo "옵션:\n";
echo " --mysql=섹션명 INI 파일 섹션 (기본값: default)\n"; echo " --mysql=섹션명 INI 파일 섹션 (기본값: default)\n";
...@@ -59,6 +59,13 @@ if (isset($options['help'])) { ...@@ -59,6 +59,13 @@ if (isset($options['help'])) {
echo " {$argv[0]} \"SHOW TABLES\"\n"; echo " {$argv[0]} \"SHOW TABLES\"\n";
echo " {$argv[0]} \"DESCRIBE users\"\n"; echo " {$argv[0]} \"DESCRIBE users\"\n";
echo "\n"; echo "\n";
echo "입력 방법:\n";
echo " {$argv[0]} --mysql=production # stdin에서 입력 (Ctrl+D로 완료)\n";
echo " {$argv[0]} < query.sql # 파일에서 입력 (리다이렉션)\n";
echo " cat query.sql | {$argv[0]} # 파일에서 입력 (파이프)\n";
echo " {$argv[0]} \"\$(pbpaste)\" # 클립보드에서 입력 (macOS)\n";
echo " {$argv[0]} \"\$(xclip -o)\" # 클립보드에서 입력 (Linux)\n";
echo "\n";
echo "설정 파일: ~/.ptyMysqlConfig.ini\n"; echo "설정 파일: ~/.ptyMysqlConfig.ini\n";
echo ptyMysqlConfig::getConfigExample() . "\n"; echo ptyMysqlConfig::getConfigExample() . "\n";
exit(0); exit(0);
...@@ -75,16 +82,24 @@ if (isset($options['edit'])) { ...@@ -75,16 +82,24 @@ if (isset($options['edit'])) {
} }
// ============================================================ // ============================================================
// 3. 필수 인자 검증 // 3. SQL 입력: 인자 또는 stdin
// ============================================================ // ============================================================
if (count($positionalArgs) < 1) { if (count($positionalArgs) < 1) {
// stdin에서 읽기
if (posix_isatty(STDIN)) {
fwrite(STDERR, "SQL을 입력하세요 (Ctrl+D로 완료):\n");
}
$sql = file_get_contents('php://stdin');
$sql = trim($sql);
if (empty($sql)) {
echo "Error: SQL 쿼리가 필요합니다.\n"; echo "Error: SQL 쿼리가 필요합니다.\n";
echo "도움말: {$argv[0]} --help\n"; echo "도움말: {$argv[0]} --help\n";
exit(1); exit(1);
}
} else {
$sql = $positionalArgs[0];
} }
$sql = $positionalArgs[0];
// ANSI 색상 코드 // ANSI 색상 코드
$RED = "\033[1;31m"; $RED = "\033[1;31m";
$GREEN = "\033[1;32m"; $GREEN = "\033[1;32m";
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment