@nine-lab/nine-connector 0.1.1
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.
- package/README.md +67 -0
- package/package.json +30 -0
- package/prompts/query-generator.md +24 -0
- package/prompts/table-filter.md +21 -0
- package/src/ai/AIProcessor.js +41 -0
- package/src/ai/AIService.js +44 -0
- package/src/core/init.js +140 -0
- package/src/database/config/database.js +42 -0
- package/src/database/core/DatabaseManager.js +88 -0
- package/src/database/core/Dialects.js +59 -0
- package/src/database/core/PoolManager.js +49 -0
- package/src/drivers/mysql.js +0 -0
- package/src/index.js +133 -0
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
cat <<EOF > README.md
|
|
2
|
+
# @nine-lab/nine-connector ๐
|
|
3
|
+
|
|
4
|
+
์์ฐ์ด ๊ธฐ๋ฐ SQL ์์ฑ ์์ง(NineQuery)๊ณผ ์ค์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฌ์ด๋ฅผ ์ฐ๊ฒฐํ๋ **AI ์ ์ฉ DB ์ปค๋ฅํฐ ์๋น์ค**์
๋๋ค.
|
|
5
|
+
|
|
6
|
+
AI๊ฐ ์์ฑํ SQL์ ์์ ํ๊ฒ ์คํํ๊ณ , ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๊ตฌ์กฐ(Schema)๋ฅผ AI์๊ฒ ์ ๋ฌํ๊ธฐ ์ํ ํ์ค API ํ๊ฒฝ์ ์ ๊ณตํฉ๋๋ค.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## ๐ ์ฃผ์ ๊ธฐ๋ฅ
|
|
11
|
+
|
|
12
|
+
* **Multi-DB ์ง์**: MySQL, PostgreSQL, MariaDB์ ์ฆ์ ์ฐ๊ฒฐ ๊ฐ๋ฅํฉ๋๋ค.
|
|
13
|
+
* **์คํค๋ง ์๋ ์ถ์ถ**: AI๊ฐ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ ๋ ํ์ํ ํ
์ด๋ธ ๋ฐ ์ปฌ๋ผ ์ ๋ณด๋ฅผ API๋ก ์ ๊ณตํฉ๋๋ค.
|
|
14
|
+
* **๋ณด์ ๊ฐ๋**: ์ค์ง \`SELECT\` ์ฟผ๋ฆฌ๋ง ์คํํ ์ ์๋๋ก ์ค๊ณ๋์ด ๋ฐ์ดํฐ ๋ณ์กฐ๋ฅผ ์์ฒ ์ฐจ๋จํฉ๋๋ค.
|
|
15
|
+
* **BigInt ๋์**: ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ํฐ ์ซ์ํ(\`BigInt\`) ๋ฐ์ดํฐ๋ฅผ JSON์ผ๋ก ์์ ํ๊ฒ ๋ณํํ์ฌ ๋ฐํํฉ๋๋ค.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## ๐ ์ค์น ๋ฐ ์คํ ๋ฐฉ๋ฒ
|
|
20
|
+
|
|
21
|
+
### 1. ์ ์ญ(Global) ์ค์น
|
|
22
|
+
์ด๋ ๊ฒฝ๋ก์์๋ ๋ช
๋ น์ด๋ก ์ฆ์ ๊ธฐ๋ํ ์ ์๋๋ก ์ ์ญ ์ค์น๋ฅผ ๊ถ์ฅํฉ๋๋ค.
|
|
23
|
+
|
|
24
|
+
\`\`\`bash
|
|
25
|
+
npm install -g @nine-lab/nine-connector
|
|
26
|
+
\`\`\`
|
|
27
|
+
|
|
28
|
+
### 2. ํ๊ฒฝ ๋ณ์ ์ค์ (\`.env\`)
|
|
29
|
+
์๋ฒ ์คํ ์ , ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ์ ์ ๋ณด๋ฅผ ์ค์ ํด์ผ ํฉ๋๋ค. ์คํํ ๊ฒฝ๋ก์ \`.env\` ํ์ผ์ ์์ฑํ๊ฑฐ๋ ์์คํ
ํ๊ฒฝ ๋ณ์๋ฅผ ์ค์ ํ์ธ์.
|
|
30
|
+
|
|
31
|
+
\`\`\`env
|
|
32
|
+
# ์๋ฒ ํฌํธ (๊ธฐ๋ณธ๊ฐ: 3000)
|
|
33
|
+
SERVER_PORT=3000
|
|
34
|
+
|
|
35
|
+
# ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค์
|
|
36
|
+
DB_TYPE=mysql
|
|
37
|
+
DB_HOST=127.0.0.1
|
|
38
|
+
DB_PORT=3306
|
|
39
|
+
DB_USER=your_user
|
|
40
|
+
DB_PASS=your_password
|
|
41
|
+
DB_NAME=your_database
|
|
42
|
+
\`\`\`
|
|
43
|
+
|
|
44
|
+
### 3. ์๋ฒ ๊ธฐ๋
|
|
45
|
+
์ค์ ์ด ์๋ฃ๋์๋ค๋ฉด ํฐ๋ฏธ๋์์ ์๋ ๋ช
๋ น์ด๋ฅผ ์
๋ ฅํฉ๋๋ค.
|
|
46
|
+
|
|
47
|
+
\`\`\`bash
|
|
48
|
+
nine-connector
|
|
49
|
+
\`\`\`
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## ๐ API ๋ช
์ธ
|
|
54
|
+
|
|
55
|
+
### 1. ๋ฐ์ดํฐ๋ฒ ์ด์ค ์คํค๋ง ์กฐํ
|
|
56
|
+
AI ๋ชจ๋ธ์ด ํ์ฌ DB ๊ตฌ์กฐ๋ฅผ ํ์ตํ ์ ์๋๋ก ๋ฉํ๋ฐ์ดํฐ๋ฅผ ๋ฐํํฉ๋๋ค.
|
|
57
|
+
* **Endpoint:** \`GET /api/schema\`
|
|
58
|
+
|
|
59
|
+
### 2. SQL ์ฟผ๋ฆฌ ์คํ
|
|
60
|
+
AI๊ฐ ์์ฑํ SQL์ ์คํํฉ๋๋ค. ๋ณด์์ ์ํด **SELECT ๋ฌธ**๋ง ํ์ฉ๋ฉ๋๋ค.
|
|
61
|
+
* **Endpoint:** \`POST /api/query\`
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## ๐ ๋ผ์ด์ ์ค
|
|
66
|
+
MIT ยฉ **nine-lab**
|
|
67
|
+
EOF
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nine-lab/nine-connector",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "NineQuery AI Connector for Database",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"nine-query-connector": "src/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node src/index.js",
|
|
12
|
+
"dev": "node --watch src/index.js",
|
|
13
|
+
"build": "echo '๋น๋๊ฐ ํ์ํ ํ๋ก์ ํธ๋ผ๋ฉด ์ฌ๊ธฐ์ ๋น๋ ๋ช
๋ น ์์ฑ'",
|
|
14
|
+
"release": "npm version patch && npm run build && npm publish"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@langchain/core": "^1.1.44",
|
|
18
|
+
"@langchain/google-genai": "^2.1.30",
|
|
19
|
+
"@nine-lab/nine-util": "^0.9.35",
|
|
20
|
+
"cors": "^2.8.6",
|
|
21
|
+
"dotenv": "^16.4.5",
|
|
22
|
+
"express": "^4.22.1",
|
|
23
|
+
"mariadb": "^3.5.2",
|
|
24
|
+
"mysql2": "^3.9.7",
|
|
25
|
+
"pg": "^8.11.5"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
### SQL_GENERATE_PROMPT
|
|
2
|
+
๋น์ ์ SQL ์์ฑ๊ธฐ์
๋๋ค. ์ ๊ณต๋ ํ
์ด๋ธ์ ์ปฌ๋ผ ์ ๋ณด๋ฅผ ๋ฐํ์ผ๋ก ์ฌ์ฉ์์ ์ง๋ฌธ์ ๋ตํ๋ ์ต์ ์ SQL ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ์ธ์.
|
|
3
|
+
|
|
4
|
+
[์ ํ๋ ํ
์ด๋ธ ์ ๋ณด]
|
|
5
|
+
{detailed_schema}
|
|
6
|
+
|
|
7
|
+
[์ฌ์ฉ์ ์ง๋ฌธ]
|
|
8
|
+
"{question}"
|
|
9
|
+
|
|
10
|
+
[๋ฐ์ดํฐ๋ฒ ์ด์ค ํ์
]
|
|
11
|
+
{db_type}
|
|
12
|
+
|
|
13
|
+
[์๋ต ๊ท์น]
|
|
14
|
+
1. ๋ฐ๋์ JSON ํ์์ผ๋ก๋ง ์๋ตํ์ธ์.
|
|
15
|
+
2. SQL์ {db_type} ๋ฌธ๋ฒ์ ์๋ฒฝํ๊ฒ ๋ง์์ผ ํ๋ฉฐ, ์ฆ์ ์คํ ๊ฐ๋ฅํด์ผ ํฉ๋๋ค.
|
|
16
|
+
3. ์ฟผ๋ฆฌ๋ฌธ ์์ ๋งํฌ๋ค์ด ์ฝ๋ ๋ธ๋ก(```sql)์ ํฌํจํ์ง ๋ง์ธ์. ์ค์ง ์์ ๋ฌธ์์ด๋ก๋ง ์์ฑํ์ธ์.
|
|
17
|
+
4. ์ฌ์ฉ์๊ฐ ์ดํดํ๊ธฐ ์ฝ๋๋ก ์ฟผ๋ฆฌ์ ๋ํ ์ค๋ช
(explanation)์ ์น์ ํ๊ฒ ์์ฑํ์ธ์.
|
|
18
|
+
5. ์กด์ฌํ์ง ์๋ ์ปฌ๋ผ์ด๋ ํ
์ด๋ธ์ ์ ๋ ์ฌ์ฉํ์ง ๋ง์ธ์.
|
|
19
|
+
|
|
20
|
+
[์๋ต ํฌ๋งท]
|
|
21
|
+
{{
|
|
22
|
+
"sql": "SELECT ... FROM ... WHERE ...",
|
|
23
|
+
"explanation": "์ด ์ฟผ๋ฆฌ๋ ์ด๋ค ๋ฐ์ดํฐ๋ฅผ ์ด๋ป๊ฒ ์กฐํํ๋์ง์ ๋ํ ์ค๋ช
์
๋๋ค."
|
|
24
|
+
}}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
### TABLE_FILTER_PROMPT
|
|
2
|
+
๋น์ ์ SQL ์ ๋ฌธ๊ฐ์
๋๋ค. ์ฌ์ฉ์์ ์ง๋ฌธ์ ๋ถ์ํ์ฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์กฐํํด์ผ ํ ํ์ ํ
์ด๋ธ๋ค๋ง ์ ์ ํ์ธ์.
|
|
3
|
+
|
|
4
|
+
[์ ์ฒด ํ
์ด๋ธ ๋ชฉ๋ก]
|
|
5
|
+
{schema_summary}
|
|
6
|
+
|
|
7
|
+
[์ฌ์ฉ์ ์ง๋ฌธ]
|
|
8
|
+
"{question}"
|
|
9
|
+
|
|
10
|
+
[์๋ต ๊ท์น]
|
|
11
|
+
1. ๋ฐ๋์ JSON ํ์์ผ๋ก๋ง ์๋ตํ์ธ์.
|
|
12
|
+
2. ์ง๋ฌธ์ ํด๊ฒฐํ๊ธฐ ์ํด ๋ฐ๋์ ์ฐธ์กฐํด์ผ ํ๋ ํ
์ด๋ธ ์ด๋ฆ๋ง 'selected_tables' ๋ฐฐ์ด์ ๋ฃ์ผ์ธ์.
|
|
13
|
+
3. ๋ง์ฝ ์ง๋ฌธ์ด ๋ฐ์ดํฐ๋ฒ ์ด์ค ์กฐํ์ ๊ด๋ จ์ด ์๊ฑฐ๋, ์ ๊ณต๋ ํ
์ด๋ธ ์ ๋ณด๋ก ๋ตํ ์ ์๋ค๋ฉด 'is_executable'์ false๋ก ์ค์ ํ์ธ์.
|
|
14
|
+
4. 'reasoning' ํ๋์๋ ์ ํด๋น ํ
์ด๋ธ๋ค์ ์ ํํ๋์ง ๊ฐ๋ตํ๊ฒ ์ค๋ช
ํ์ธ์.
|
|
15
|
+
|
|
16
|
+
[์๋ต ํฌ๋งท]
|
|
17
|
+
{{
|
|
18
|
+
"reasoning": "์ ์ ์ด์ ๋ฅผ ์ฌ๊ธฐ์ ์์ฑ",
|
|
19
|
+
"selected_tables": ["table1", "table2"],
|
|
20
|
+
"is_executable": true
|
|
21
|
+
}}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
|
|
2
|
+
import { PromptTemplate } from "@langchain/core/prompts";
|
|
3
|
+
import { JsonOutputParser } from "@langchain/core/output_parsers";
|
|
4
|
+
|
|
5
|
+
export class AIProcessor {
|
|
6
|
+
#model;
|
|
7
|
+
#chains = {};
|
|
8
|
+
#parser;
|
|
9
|
+
|
|
10
|
+
constructor(apiKey, modelName = "gemini-2.5-flash") {
|
|
11
|
+
this.#model = new ChatGoogleGenerativeAI({
|
|
12
|
+
apiKey: apiKey,
|
|
13
|
+
model: modelName,
|
|
14
|
+
temperature: 0,
|
|
15
|
+
});
|
|
16
|
+
this.#parser = new JsonOutputParser();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
registerChain(key, promptText) {
|
|
20
|
+
const promptTemplate = PromptTemplate.fromTemplate(promptText);
|
|
21
|
+
this.#chains[key] = promptTemplate.pipe(this.#model).pipe(this.#parser);
|
|
22
|
+
console.log(`[AIProcessor] ${key} ์ฒด์ธ ๋ฑ๋ก ์๋ฃ.`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async filterTables(question, schemaSummary) {
|
|
26
|
+
if (!this.#chains['table-filter']) throw new Error("table-filter ์ฒด์ธ ๋ฏธ๋ฑ๋ก");
|
|
27
|
+
return await this.#chains['table-filter'].invoke({
|
|
28
|
+
question,
|
|
29
|
+
schema_summary: JSON.stringify(schemaSummary, null, 2)
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async generateSql(question, detailedSchema, dbType = 'mysql') {
|
|
34
|
+
if (!this.#chains['query-generator']) throw new Error("query-generator ์ฒด์ธ ๋ฏธ๋ฑ๋ก");
|
|
35
|
+
return await this.#chains['query-generator'].invoke({
|
|
36
|
+
question,
|
|
37
|
+
detailed_schema: JSON.stringify(detailedSchema, null, 2),
|
|
38
|
+
db_type: dbType
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ์์ฐ์ด ์ง์ ํ๋ก์ธ์ค๋ฅผ ๊ด๋ฆฌํ๋ ์๋น์ค
|
|
3
|
+
*/
|
|
4
|
+
export class AIService {
|
|
5
|
+
constructor(db, ai) {
|
|
6
|
+
this.db = db;
|
|
7
|
+
this.ai = ai;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async processNaturalLanguageQuery(question) {
|
|
11
|
+
// 1) ์ ์ฒด ์คํค๋ง ์์ฝ ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ
|
|
12
|
+
const schemaSummary = await this.db.getTableSchema();
|
|
13
|
+
|
|
14
|
+
// 2) AI: ํ์ํ ํ
์ด๋ธ ํํฐ๋ง
|
|
15
|
+
// AI ๊ฒฐ๊ณผ๊ฐ ๋ฌธ์์ด์ผ ๊ฒฝ์ฐ๋ฅผ ๋๋นํด ํ์ฑ ๋ก์ง์ด AIProcessor์ ์๋ค๋ฉด ์ฌ๊ธฐ์ ๋ณด์ ๊ฐ๋ฅ
|
|
16
|
+
const { tables } = await this.ai.filterTables(question, schemaSummary);
|
|
17
|
+
|
|
18
|
+
if (!tables || tables.length === 0) {
|
|
19
|
+
throw new Error("์ง๋ฌธ๊ณผ ๊ด๋ จ๋ ํ
์ด๋ธ์ ์ฐพ์ ์ ์์ต๋๋ค.");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 3) ์์ธ ์คํค๋ง ํํฐ๋ง ๋ฐ SQL ์์ฑ
|
|
23
|
+
const detailedSchema = schemaSummary.filter(t => tables.includes(t.tableName));
|
|
24
|
+
const { sql, explanation } = await this.ai.generateSql(
|
|
25
|
+
question,
|
|
26
|
+
detailedSchema,
|
|
27
|
+
process.env.DB_TYPE || 'mysql'
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
// 4) SQL ๊ฒ์ฆ
|
|
31
|
+
if (!sql.trim().toLowerCase().startsWith("select")) {
|
|
32
|
+
throw new Error("๋ณด์ ์ ์ฑ
์ SELECT ์ฟผ๋ฆฌ๋ง ์คํ ๊ฐ๋ฅํฉ๋๋ค.");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 5) ์ฟผ๋ฆฌ ์คํ
|
|
36
|
+
const rows = await this.db.query(sql);
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
sql,
|
|
40
|
+
explanation,
|
|
41
|
+
data: rows
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
package/src/core/init.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import readline from 'readline';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 1. .env ํ์ผ ์์ฑ ๋ด๋น ํจ์
|
|
7
|
+
*/
|
|
8
|
+
async function createEnvFile(rl) {
|
|
9
|
+
const questions = [
|
|
10
|
+
{ key: 'SERVER_PORT', label: '1. ์ปค๋ฅํฐ ์๋ฒ ํฌํธ', default: '3000' },
|
|
11
|
+
{ key: 'GEMINI_API_KEY', label: '2. Gemini API Key', default: '' },
|
|
12
|
+
{ key: 'GEMINI_MODEL', label: '3. ์ฌ์ฉํ ๋ชจ๋ธ', default: 'gemini-1.5-flash' },
|
|
13
|
+
{ key: 'DB_TYPE', label: '4. DB ์ข
๋ฅ (mysql, pg, mariadb)', default: 'mysql' },
|
|
14
|
+
{ key: 'DB_HOST', label: '5. DB ํธ์คํธ', default: '127.0.0.1' },
|
|
15
|
+
{ key: 'DB_PORT', label: '6. DB ํฌํธ', default: '3306' },
|
|
16
|
+
{ key: 'DB_USER', label: '7. DB ์ฌ์ฉ์ ๊ณ์ ', default: 'root' },
|
|
17
|
+
{ key: 'DB_PASS', label: '8. DB ๋น๋ฐ๋ฒํธ', default: '' },
|
|
18
|
+
{ key: 'DB_NAME', label: '9. ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ด๋ฆ', default: '' },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const envPath = path.join(process.cwd(), '.env');
|
|
22
|
+
|
|
23
|
+
if (fs.existsSync(envPath)) {
|
|
24
|
+
const overwrite = await new Promise((resolve) => {
|
|
25
|
+
rl.question('์ด๋ฏธ .env ํ์ผ์ด ์กด์ฌํฉ๋๋ค. ๋ฎ์ด์ฐ์๊ฒ ์ต๋๊น? (y/n): ', resolve);
|
|
26
|
+
});
|
|
27
|
+
if (overwrite.toLowerCase() !== 'y') {
|
|
28
|
+
console.log('๊ธฐ์กด .env ํ์ผ์ ์ ์งํฉ๋๋ค.');
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const answers = {};
|
|
34
|
+
for (const q of questions) {
|
|
35
|
+
const answer = await new Promise((resolve) => {
|
|
36
|
+
rl.question(`> ${q.label} [${q.default}]: `, resolve);
|
|
37
|
+
});
|
|
38
|
+
answers[q.key] = answer.trim() || q.default;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const envContent = Object.entries(answers)
|
|
42
|
+
.map(([key, val]) => `${key}=${val}`)
|
|
43
|
+
.join('\n');
|
|
44
|
+
|
|
45
|
+
fs.writeFileSync(envPath, envContent);
|
|
46
|
+
console.log('.env ํ์ผ ์์ฑ์ด ์๋ฃ๋์์ต๋๋ค.');
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 2. ํ๋กฌํํธ ํ์ผ(.md) ์์ฑ ๋ด๋น ํจ์
|
|
52
|
+
*/
|
|
53
|
+
function createPromptFiles() {
|
|
54
|
+
const promptDir = path.join(process.cwd(), 'prompts');
|
|
55
|
+
|
|
56
|
+
if (!fs.existsSync(promptDir)) {
|
|
57
|
+
fs.mkdirSync(promptDir, { recursive: true });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const prompts = {
|
|
61
|
+
'table-filter.md': `## TABLE_FILTER_PROMPT
|
|
62
|
+
๋น์ ์ SQL ์ ๋ฌธ๊ฐ์
๋๋ค. ์ฌ์ฉ์์ ์ง๋ฌธ์ ๋ถ์ํ์ฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์กฐํํด์ผ ํ ํ์ ํ
์ด๋ธ๋ค๋ง ์ ์ ํ์ธ์.
|
|
63
|
+
|
|
64
|
+
[์ ์ฒด ํ
์ด๋ธ ๋ชฉ๋ก]
|
|
65
|
+
{schema_summary}
|
|
66
|
+
|
|
67
|
+
[์ฌ์ฉ์ ์ง๋ฌธ]
|
|
68
|
+
"{question}"
|
|
69
|
+
|
|
70
|
+
[์๋ต ๊ท์น]
|
|
71
|
+
1. ๋ฐ๋์ JSON ํ์์ผ๋ก๋ง ์๋ตํ์ธ์.
|
|
72
|
+
2. ์ง๋ฌธ์ ํด๊ฒฐํ๊ธฐ ์ํด ๋ฐ๋์ ์ฐธ์กฐํด์ผ ํ๋ ํ
์ด๋ธ ์ด๋ฆ๋ง 'selected_tables' ๋ฐฐ์ด์ ๋ฃ์ผ์ธ์.
|
|
73
|
+
3. ๋ง์ฝ ์ง๋ฌธ์ด ๋ฐ์ดํฐ๋ฒ ์ด์ค ์กฐํ์ ๊ด๋ จ์ด ์๊ฑฐ๋, ์ ๊ณต๋ ํ
์ด๋ธ ์ ๋ณด๋ก ๋ตํ ์ ์๋ค๋ฉด 'is_executable'์ false๋ก ์ค์ ํ์ธ์.
|
|
74
|
+
4. 'reasoning' ํ๋์๋ ์ ํด๋น ํ
์ด๋ธ๋ค์ ์ ํํ๋์ง ๊ฐ๋ตํ๊ฒ ์ค๋ช
ํ์ธ์.
|
|
75
|
+
|
|
76
|
+
[์๋ต ํฌ๋งท]
|
|
77
|
+
{{
|
|
78
|
+
"reasoning": "์ ์ ์ด์ ๋ฅผ ์ฌ๊ธฐ์ ์์ฑ",
|
|
79
|
+
"selected_tables": ["table1", "table2"],
|
|
80
|
+
"is_executable": true
|
|
81
|
+
}}`,
|
|
82
|
+
|
|
83
|
+
'query-generator.md': `## SQL_GENERATE_PROMPT
|
|
84
|
+
๋น์ ์ SQL ์์ฑ๊ธฐ์
๋๋ค. ์ ๊ณต๋ ํ
์ด๋ธ์ ์ปฌ๋ผ ์ ๋ณด๋ฅผ ๋ฐํ์ผ๋ก ์ฌ์ฉ์์ ์ง๋ฌธ์ ๋ตํ๋ ์ต์ ์ SQL ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ์ธ์.
|
|
85
|
+
|
|
86
|
+
[์ ํ๋ ํ
์ด๋ธ ์ ๋ณด]
|
|
87
|
+
{detailed_schema}
|
|
88
|
+
|
|
89
|
+
[์ฌ์ฉ์ ์ง๋ฌธ]
|
|
90
|
+
"{question}"
|
|
91
|
+
|
|
92
|
+
[๋ฐ์ดํฐ๋ฒ ์ด์ค ํ์
]
|
|
93
|
+
{db_type}
|
|
94
|
+
|
|
95
|
+
[์๋ต ๊ท์น]
|
|
96
|
+
1. ๋ฐ๋์ JSON ํ์์ผ๋ก๋ง ์๋ตํ์ธ์.
|
|
97
|
+
2. SQL์ {db_type} ๋ฌธ๋ฒ์ ์๋ฒฝํ๊ฒ ๋ง์์ผ ํ๋ฉฐ, ์ฆ์ ์คํ ๊ฐ๋ฅํด์ผ ํฉ๋๋ค.
|
|
98
|
+
3. ์ฟผ๋ฆฌ๋ฌธ ์์ ๋งํฌ๋ค์ด ์ฝ๋ ๋ธ๋ก(\`\`\`sql)์ ํฌํจํ์ง ๋ง์ธ์. ์ค์ง ์์ ๋ฌธ์์ด๋ก๋ง ์์ฑํ์ธ์.
|
|
99
|
+
4. ์ฌ์ฉ์๊ฐ ์ดํดํ๊ธฐ ์ฝ๋๋ก ์ฟผ๋ฆฌ์ ๋ํ ์ค๋ช
(explanation)์ ์น์ ํ๊ฒ ์์ฑํ์ธ์.
|
|
100
|
+
5. ์กด์ฌํ์ง ์๋ ์ปฌ๋ผ์ด๋ ํ
์ด๋ธ์ ์ ๋ ์ฌ์ฉํ์ง ๋ง์ธ์.
|
|
101
|
+
|
|
102
|
+
[์๋ต ํฌ๋งท]
|
|
103
|
+
{{
|
|
104
|
+
"sql": "SELECT ... FROM ... WHERE ...",
|
|
105
|
+
"explanation": "์ด ์ฟผ๋ฆฌ๋ ์ด๋ค ๋ฐ์ดํฐ๋ฅผ ์ด๋ป๊ฒ ์กฐํํ๋์ง์ ๋ํ ์ค๋ช
์
๋๋ค."
|
|
106
|
+
}}`
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
Object.entries(prompts).forEach(([filename, content]) => {
|
|
110
|
+
const filePath = path.join(promptDir, filename);
|
|
111
|
+
fs.writeFileSync(filePath, content.trim());
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
console.log('ํ๋กฌํํธ ํ์ผ ์์ฑ์ด ์๋ฃ๋์์ต๋๋ค.');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* ๋ฉ์ธ ์คํ ํจ์
|
|
119
|
+
*/
|
|
120
|
+
export async function runInit() {
|
|
121
|
+
const rl = readline.createInterface({
|
|
122
|
+
input: process.stdin,
|
|
123
|
+
output: process.stdout
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
console.log('์ค์ ์ด๊ธฐํ๋ฅผ ์์ํฉ๋๋ค.');
|
|
128
|
+
|
|
129
|
+
const success = await createEnvFile(rl);
|
|
130
|
+
if (success) {
|
|
131
|
+
createPromptFiles();
|
|
132
|
+
console.log('๋ชจ๋ ์ด๊ธฐํ ์์
์ด ์๋ฃ๋์์ต๋๋ค.');
|
|
133
|
+
console.log('nine-connector ๋ฅผ ์
๋ ฅํ์ฌ ์๋ฒ๋ฅผ ์คํํ์ธ์.');
|
|
134
|
+
}
|
|
135
|
+
} catch (err) {
|
|
136
|
+
console.error('์ด๊ธฐํ ์ค ์ค๋ฅ ๋ฐ์:', err.message);
|
|
137
|
+
} finally {
|
|
138
|
+
rl.close();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as mariadb from 'mariadb';
|
|
2
|
+
|
|
3
|
+
// [์ค์] .env์์ ๊ฐ์ ธ์จ ๊ฐ๋ค์ ์๋ ๊ณต๋ฐฑ์ ์ ๊ฑฐํ๊ณ ํ์
์ ๋ง์ถฅ๋๋ค.
|
|
4
|
+
const dbConfig = {
|
|
5
|
+
host: process.env.DB_HOST?.trim(), // ํน์ ๋ชจ๋ฅผ ์ค๋ฐ๊ฟ/๊ณต๋ฐฑ ์ ๊ฑฐ
|
|
6
|
+
port: Number(process.env.DB_PORT?.trim()) || 3306, // ํ์คํ๊ฒ ์ซ์๋ก ๋ณํ
|
|
7
|
+
user: process.env.DB_USER?.trim(),
|
|
8
|
+
password: process.env.DB_PASS?.trim(), // ํน์๋ฌธ์๊ฐ ํฌํจ๋ ๊ฒฝ์ฐ trim ํ์
|
|
9
|
+
database: process.env.DB_NAME?.trim(),
|
|
10
|
+
connectionLimit: 10,
|
|
11
|
+
connectTimeout: 10000
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// ๋ก๊ทธ๋ก ์ค์ ๊ฐ์ด ํ๋์ฝ๋ฉ๊ณผ ์๋ฒฝํ ์ผ์นํ๋์ง ๋์กฐํด๋ณด์ธ์.
|
|
15
|
+
console.log("--- DB Config Check ---");
|
|
16
|
+
console.log("Host:", `[${dbConfig.host}]`); // ๋๊ดํธ ์์ ๊ณต๋ฐฑ์ด ์๋์ง ํ์ธ
|
|
17
|
+
console.log("Port Type:", typeof dbConfig.port); // number์ฌ์ผ ํจ
|
|
18
|
+
console.log("--- ---------------- ---");
|
|
19
|
+
|
|
20
|
+
const pool = mariadb.createPool(dbConfig);
|
|
21
|
+
|
|
22
|
+
export const getConnection = async () => {
|
|
23
|
+
try {
|
|
24
|
+
return await pool.getConnection();
|
|
25
|
+
} catch (err) {
|
|
26
|
+
console.error("โ DB ์ฐ๊ฒฐ ์คํจ:", err);
|
|
27
|
+
throw err;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default pool;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
const pool = mariadb.createPool({
|
|
35
|
+
host: "mariadb.artygentech.com",
|
|
36
|
+
port: 3306,
|
|
37
|
+
user: "root",
|
|
38
|
+
password: "aglab123!@#",
|
|
39
|
+
database: "aidemo",
|
|
40
|
+
connectionLimit: 10,
|
|
41
|
+
connectTimeout: 10000
|
|
42
|
+
}); */
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import PoolManager from './PoolManager.js';
|
|
2
|
+
import Dialects from './Dialects.js'; // ์ฟผ๋ฆฌ ์ฌ์ ์ํฌํธ
|
|
3
|
+
|
|
4
|
+
class DatabaseManager {
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this.config = config;
|
|
7
|
+
this.pool = null;
|
|
8
|
+
this.type = config.type?.toLowerCase();
|
|
9
|
+
this.dialect = Dialects[this.type];
|
|
10
|
+
|
|
11
|
+
if (!this.dialect) {
|
|
12
|
+
throw new Error(`โ Unsupported Dialect for: ${this.type}`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* DatabaseFactory์ init() ์ญํ ์ ์ํํฉ๋๋ค.
|
|
18
|
+
*/
|
|
19
|
+
async connect() {
|
|
20
|
+
try {
|
|
21
|
+
// Pool ์์ฑ์ ์ ์ ์ผ๋ก PoolManager์๊ฒ ๋งก๊น๋๋ค.
|
|
22
|
+
this.pool = PoolManager.createPool(this.config);
|
|
23
|
+
|
|
24
|
+
// ๋จ์ ์ฐ๊ฒฐ ํ์ธ ์ฟผ๋ฆฌ
|
|
25
|
+
await this.query('SELECT 1');
|
|
26
|
+
console.log(`โ
[${this.type}] Database connection successful.`);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error(`โ [${this.type}] Connection failed:`, error.message);
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* DB๋ณ ๊ฒฐ๊ณผ ํฌ๋งท ์ฐจ์ด๋ฅผ ์ฌ๊ธฐ์ ํด๊ฒฐํฉ๋๋ค. (๋ฐ์ดํฐ ๋ฐฐ์ด๋ง ๋ฐํ)
|
|
35
|
+
*/
|
|
36
|
+
// DatabaseManager.js ์์
|
|
37
|
+
async query(sql, params = []) {
|
|
38
|
+
if (!this.pool) throw new Error("Database not connected.");
|
|
39
|
+
|
|
40
|
+
const result = await this.pool.query(sql, params);
|
|
41
|
+
|
|
42
|
+
if (this.type === 'postgres') {
|
|
43
|
+
return result.rows;
|
|
44
|
+
} else {
|
|
45
|
+
// ๋ง์ฝ result[0]์ด ๋ ๋ฐฐ์ด์ด๋ผ๋ฉด(mysql2 ์ด์ค ๋ฐฐ์ด), 0๋ฒ ์ธ๋ฑ์ค๋ฅผ ๋ฐํ
|
|
46
|
+
// ์๋๋ผ๋ฉด result๋ฅผ ๊ทธ๋๋ก ๋ฐํ
|
|
47
|
+
return (Array.isArray(result) && Array.isArray(result[0]))
|
|
48
|
+
? result[0]
|
|
49
|
+
: result;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* AI ๊ฐ๊ณต์ ์ํด ์คํค๋ง ๋ฐ์ดํฐ๋ฅผ ๊ตฌ์กฐํํฉ๋๋ค.
|
|
55
|
+
* ๊ฒฐ๊ณผ ์์:
|
|
56
|
+
* {
|
|
57
|
+
* "orders": {
|
|
58
|
+
* "description": "์ฃผ๋ฌธ ๊ธฐ๋ณธ ์ ๋ณด ํ
์ด๋ธ",
|
|
59
|
+
* "columns": [{ "column": "id", "type": "int" }, ...]
|
|
60
|
+
* }
|
|
61
|
+
* }
|
|
62
|
+
*/
|
|
63
|
+
async getTableSchema() {
|
|
64
|
+
const rows = await this.query(this.dialect.schemaQuery);
|
|
65
|
+
|
|
66
|
+
return rows.reduce((acc, row) => {
|
|
67
|
+
const { table_name, table_comment, column_name, data_type } = row;
|
|
68
|
+
|
|
69
|
+
// ํ
์ด๋ธ์ด ์ฒ์ ๋ฑ์ฅํ๋ฉด ์ด๊ธฐํ
|
|
70
|
+
if (!acc[table_name]) {
|
|
71
|
+
acc[table_name] = {
|
|
72
|
+
description: table_comment || "", // ํ
์ด๋ธ ์ค๋ช
์ถ๊ฐ
|
|
73
|
+
columns: []
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ์ปฌ๋ผ ์ ๋ณด push
|
|
78
|
+
acc[table_name].columns.push({
|
|
79
|
+
column: column_name,
|
|
80
|
+
type: data_type
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return acc;
|
|
84
|
+
}, {});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export default DatabaseManager;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const Dialects = {
|
|
2
|
+
// MySQL ์ถ๊ฐ (MariaDB์ ๋์ผ)
|
|
3
|
+
mysql: {
|
|
4
|
+
schemaQuery: `
|
|
5
|
+
SELECT
|
|
6
|
+
c.TABLE_NAME as table_name,
|
|
7
|
+
t.TABLE_COMMENT as table_comment,
|
|
8
|
+
c.COLUMN_NAME as column_name,
|
|
9
|
+
c.DATA_TYPE as data_type
|
|
10
|
+
FROM information_schema.columns c
|
|
11
|
+
INNER JOIN information_schema.tables t
|
|
12
|
+
ON c.TABLE_NAME = t.TABLE_NAME
|
|
13
|
+
AND c.TABLE_SCHEMA = t.TABLE_SCHEMA
|
|
14
|
+
WHERE c.TABLE_SCHEMA = DATABASE()
|
|
15
|
+
ORDER BY c.TABLE_NAME, c.ORDINAL_POSITION`
|
|
16
|
+
},
|
|
17
|
+
mariadb: {
|
|
18
|
+
schemaQuery: `
|
|
19
|
+
SELECT
|
|
20
|
+
c.TABLE_NAME as table_name,
|
|
21
|
+
t.TABLE_COMMENT as table_comment,
|
|
22
|
+
c.COLUMN_NAME as column_name,
|
|
23
|
+
c.DATA_TYPE as data_type
|
|
24
|
+
FROM information_schema.columns c
|
|
25
|
+
INNER JOIN information_schema.tables t
|
|
26
|
+
ON c.TABLE_NAME = t.TABLE_NAME
|
|
27
|
+
AND c.TABLE_SCHEMA = t.TABLE_SCHEMA
|
|
28
|
+
WHERE c.TABLE_SCHEMA = DATABASE()
|
|
29
|
+
ORDER BY c.TABLE_NAME, c.ORDINAL_POSITION`
|
|
30
|
+
},
|
|
31
|
+
postgres: {
|
|
32
|
+
schemaQuery: `
|
|
33
|
+
SELECT
|
|
34
|
+
t.table_name,
|
|
35
|
+
obj_description(pgc.oid, 'pg_class') AS table_comment,
|
|
36
|
+
c.column_name,
|
|
37
|
+
c.data_type
|
|
38
|
+
FROM information_schema.tables t
|
|
39
|
+
JOIN information_schema.columns c ON t.table_name = c.table_name
|
|
40
|
+
JOIN pg_class pgc ON t.table_name = pgc.relname
|
|
41
|
+
WHERE t.table_schema = 'public'
|
|
42
|
+
ORDER BY t.table_name, c.ordinal_position`
|
|
43
|
+
},
|
|
44
|
+
oracle: {
|
|
45
|
+
schemaQuery: `
|
|
46
|
+
SELECT
|
|
47
|
+
t.TABLE_NAME as table_name,
|
|
48
|
+
tc.COMMENTS as table_comment,
|
|
49
|
+
c.COLUMN_NAME as column_name,
|
|
50
|
+
c.DATA_TYPE as data_type
|
|
51
|
+
FROM ALL_TABLES t
|
|
52
|
+
JOIN ALL_TAB_COLUMNS c ON t.TABLE_NAME = c.TABLE_NAME AND t.OWNER = c.OWNER
|
|
53
|
+
LEFT JOIN ALL_TAB_COMMENTS tc ON t.TABLE_NAME = tc.TABLE_NAME AND t.OWNER = tc.OWNER
|
|
54
|
+
WHERE t.OWNER = (SELECT USER FROM DUAL) -- ํ์ฌ ์ ์ํ ์คํค๋ง๋ง ์กฐํ
|
|
55
|
+
ORDER BY t.TABLE_NAME, c.COLUMN_ID`
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export default Dialects;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as mariadb from 'mariadb';
|
|
2
|
+
import pg from 'pg';
|
|
3
|
+
|
|
4
|
+
class PoolManager {
|
|
5
|
+
/**
|
|
6
|
+
* DB ํ์
์ ๋ง๋ ์ปค๋ฅ์
ํ์ ์์ฑํ์ฌ ๋ฐํํฉ๋๋ค.
|
|
7
|
+
*/
|
|
8
|
+
static createPool(config) {
|
|
9
|
+
const type = config?.type?.toLowerCase();
|
|
10
|
+
|
|
11
|
+
switch (type) {
|
|
12
|
+
case 'mariadb':
|
|
13
|
+
case 'mysql':
|
|
14
|
+
return this.#createMariaDBPool(config);
|
|
15
|
+
case 'postgres':
|
|
16
|
+
case 'postgresql':
|
|
17
|
+
return this.#createPostgresPool(config);
|
|
18
|
+
default:
|
|
19
|
+
throw new Error(`[PoolManager] Unsupported DB_TYPE: ${type}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static #createMariaDBPool(config) {
|
|
24
|
+
return mariadb.createPool({
|
|
25
|
+
host: config.host,
|
|
26
|
+
port: config.port,
|
|
27
|
+
user: config.user,
|
|
28
|
+
password: config.password,
|
|
29
|
+
database: config.database,
|
|
30
|
+
connectionLimit: 10,
|
|
31
|
+
connectTimeout: 10000
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static #createPostgresPool(config) {
|
|
36
|
+
const { Pool } = pg;
|
|
37
|
+
return new Pool({
|
|
38
|
+
host: config.host,
|
|
39
|
+
port: config.port,
|
|
40
|
+
user: config.user,
|
|
41
|
+
password: config.password,
|
|
42
|
+
database: config.database,
|
|
43
|
+
max: 10,
|
|
44
|
+
idleTimeoutMillis: 30000
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export default PoolManager;
|
|
File without changes
|
package/src/index.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import 'dotenv/config';
|
|
3
|
+
import express from 'express';
|
|
4
|
+
import cors from 'cors';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path'; // path ๋ชจ๋ ์ถ๊ฐ
|
|
7
|
+
import DatabaseManager from './database/core/DatabaseManager.js';
|
|
8
|
+
import { runInit } from './core/init.js';
|
|
9
|
+
import { AIProcessor } from './ai/AIProcessor.js';
|
|
10
|
+
import { AIService } from './ai/AIService.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* ์๋ฒ ์คํ ๋ก์ง
|
|
14
|
+
*/
|
|
15
|
+
async function bootstrap() {
|
|
16
|
+
// 1. ํ์ ํ๊ฒฝ ๋ณ์ ์ฒดํฌ
|
|
17
|
+
if (!process.env.DB_HOST || !process.env.DB_NAME) {
|
|
18
|
+
console.error("์ค์ ์ ๋ณด๊ฐ ๋ถ์กฑํฉ๋๋ค. 'nine-connector init'์ ๋จผ์ ์คํํด์ฃผ์ธ์.");
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const app = express();
|
|
23
|
+
const SERVER_PORT = process.env.SERVER_PORT || 3000;
|
|
24
|
+
|
|
25
|
+
// 2. DB ๋งค๋์ ์ด๊ธฐํ
|
|
26
|
+
const db = new DatabaseManager({
|
|
27
|
+
type: process.env.DB_TYPE,
|
|
28
|
+
host: process.env.DB_HOST,
|
|
29
|
+
port: Number(process.env.DB_PORT),
|
|
30
|
+
user: process.env.DB_USER,
|
|
31
|
+
password: process.env.DB_PASS,
|
|
32
|
+
database: process.env.DB_NAME
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// BigInt ์ง๋ ฌํ ์ค์
|
|
36
|
+
BigInt.prototype.toJSON = function() { return this.toString(); };
|
|
37
|
+
|
|
38
|
+
app.use(cors());
|
|
39
|
+
app.use(express.json());
|
|
40
|
+
|
|
41
|
+
// 3. AI ํ๋ก์ธ์ ์ด๊ธฐํ ๋ฐ ์ฒด์ธ ๋ฑ๋ก
|
|
42
|
+
const ai = new AIProcessor(process.env.GEMINI_API_KEY, process.env.GEMINI_MODEL);
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
// init.js์์ ์์ฑํ ๊ฒฝ๋ก์ ์ผ์น์ํต๋๋ค. (๋ณดํต ๋ฃจํธ์ prompts ํด๋)
|
|
46
|
+
const promptPath = path.join(process.cwd(), 'prompts');
|
|
47
|
+
|
|
48
|
+
const tableFilterPrompt = fs.readFileSync(
|
|
49
|
+
path.join(promptPath, 'table-filter.md'), 'utf-8'
|
|
50
|
+
);
|
|
51
|
+
const queryGenPrompt = fs.readFileSync(
|
|
52
|
+
path.join(promptPath, 'query-generator.md'), 'utf-8'
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
ai.registerChain('table-filter', tableFilterPrompt);
|
|
56
|
+
ai.registerChain('query-generator', queryGenPrompt);
|
|
57
|
+
|
|
58
|
+
console.log("๋ชจ๋ AI ์ฒด์ธ์ด ์ค๋น๋์์ต๋๋ค.");
|
|
59
|
+
} catch (err) {
|
|
60
|
+
console.error("์ฒด์ธ ๋ฑ๋ก ์ค ์ค๋ฅ ๋ฐ์ (ํ๋กฌํํธ ํ์ผ์ ํ์ธํ์ธ์):", err.message);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ์๋น์ค ์ธ์คํด์ค ์์ฑ
|
|
65
|
+
const aiService = new AIService(db, ai);
|
|
66
|
+
|
|
67
|
+
// [API] ์์ฐ์ด ์ง์ ์๋ํฌ์ธํธ
|
|
68
|
+
app.post('/api/ask', async (req, res) => {
|
|
69
|
+
const { question } = req.body;
|
|
70
|
+
if (!question) {
|
|
71
|
+
return res.status(400).json({ success: false, error: "์ง๋ฌธ์ ์
๋ ฅํด์ฃผ์ธ์." });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const result = await aiService.processNaturalLanguageQuery(question);
|
|
76
|
+
res.json({ success: true, ...result });
|
|
77
|
+
} catch (err) {
|
|
78
|
+
console.error("AI ์ง์ ์ฒ๋ฆฌ ์ค ์ค๋ฅ:", err);
|
|
79
|
+
res.status(500).json({
|
|
80
|
+
success: false,
|
|
81
|
+
error: err.message || "์ฟผ๋ฆฌ ์ฒ๋ฆฌ ์ค ์๋ฒ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค."
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
await db.connect();
|
|
88
|
+
console.log("๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ ์ฑ๊ณต");
|
|
89
|
+
|
|
90
|
+
// ์คํค๋ง ์กฐํ API
|
|
91
|
+
app.get('/api/schema', async (req, res) => {
|
|
92
|
+
try {
|
|
93
|
+
const schema = await db.getTableSchema();
|
|
94
|
+
res.json({ success: true, data: schema });
|
|
95
|
+
} catch (err) {
|
|
96
|
+
res.status(500).json({ success: false, error: "Failed to fetch schema" });
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// ์ฟผ๋ฆฌ ์คํ API
|
|
101
|
+
app.post('/api/query', async (req, res) => {
|
|
102
|
+
const { sql } = req.body;
|
|
103
|
+
if (!sql || !sql.trim().toLowerCase().startsWith("select")) {
|
|
104
|
+
return res.status(400).json({ success: false, error: "Only SELECT queries are allowed" });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const rows = await db.query(sql);
|
|
109
|
+
res.json({ success: true, data: rows });
|
|
110
|
+
} catch (err) {
|
|
111
|
+
res.status(500).json({ success: false, error: "Database query error" });
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
app.listen(SERVER_PORT, () => {
|
|
116
|
+
console.log(`NineQuery Connector running on http://localhost:${SERVER_PORT}`);
|
|
117
|
+
});
|
|
118
|
+
} catch (error) {
|
|
119
|
+
console.error("์๋ฒ ๊ธฐ๋ ์คํจ:", error.message);
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ----------------------
|
|
125
|
+
// ์คํ ๋ถ๊ธฐ ์ฒ๋ฆฌ
|
|
126
|
+
// ----------------------
|
|
127
|
+
const args = process.argv.slice(2);
|
|
128
|
+
|
|
129
|
+
if (args.includes('init')) {
|
|
130
|
+
runInit();
|
|
131
|
+
} else {
|
|
132
|
+
bootstrap();
|
|
133
|
+
}
|