@things-factory/integration-qdrant 9.1.18 → 10.0.0-beta.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/integration-qdrant",
3
- "version": "9.1.18",
3
+ "version": "10.0.0-beta.1",
4
4
  "main": "dist-server/index.js",
5
5
  "browser": "dist-client/index.js",
6
6
  "things-factory": true,
@@ -27,7 +27,7 @@
27
27
  "migration:create": "node ../../node_modules/typeorm/cli.js migration:create ./server/migrations/migration"
28
28
  },
29
29
  "dependencies": {
30
- "@things-factory/integration-base": "^9.1.18"
30
+ "@things-factory/integration-base": "^10.0.0-beta.1"
31
31
  },
32
- "gitHead": "b3518bba09ed192cdbf4892cd9e0b388ed56a4c0"
32
+ "gitHead": "90f40bf160fa25edf4560d7893cfd576cf474411"
33
33
  }
@@ -1,11 +0,0 @@
1
- {
2
- "extends": "../../tsconfig-base.json",
3
- "compilerOptions": {
4
- "strict": true,
5
- "declaration": true,
6
- "module": "esnext",
7
- "outDir": "../dist-client",
8
- "baseUrl": "./"
9
- },
10
- "include": ["./**/*"]
11
- }
@@ -1 +0,0 @@
1
- import './qdrant.js'
@@ -1,55 +0,0 @@
1
- import fetch from 'node-fetch'
2
- import { Connection, ConnectionManager, Connector } from '@things-factory/integration-base'
3
-
4
- export class QdrantConnector implements Connector {
5
- async ready(connectionConfigs) {
6
- await Promise.all(connectionConfigs.map(this.connect.bind(this)))
7
-
8
- ConnectionManager.logger.info('Qdrant connections are ready')
9
- }
10
-
11
- // async checkConnectionInstance(domain, connectionName): Promise<boolean> {
12
- // try {
13
- // const connection = await ConnectionManager.getConnectionInstanceByName(domain, connectionName)
14
- // return !!connection
15
- // } catch (e) {
16
- // return false
17
- // }
18
- // }
19
-
20
- async connect(connection) {
21
- var { endpoint } = connection
22
-
23
- try {
24
- // Qdrant에 Ping 요청을 보내 연결 확인
25
- const response = await fetch(`${endpoint}/healthz`)
26
- if (!response.ok) throw new Error(`Qdrant connection failed: ${response.statusText}`)
27
-
28
- ConnectionManager.addConnectionInstance(connection, { endpoint })
29
-
30
- ConnectionManager.logger.info(`Qdrant connection(${connection.name}:${endpoint}) is connected`)
31
- } catch (ex) {
32
- ConnectionManager.logger.error(`Qdrant connection(${connection.name}:${endpoint}) failed to connect`, ex)
33
- throw ex
34
- }
35
- }
36
-
37
- async disconnect(connection: Connection) {
38
- ConnectionManager.removeConnectionInstance(connection)
39
- ConnectionManager.logger.info(`Qdrant connection(${connection.name}) is disconnected`)
40
- }
41
-
42
- get parameterSpec() {
43
- return [{ type: 'string', name: 'collection', label: 'qdrant.collection' }]
44
- }
45
-
46
- get taskPrefixes() {
47
- return ['qdrant']
48
- }
49
-
50
- get help() {
51
- return 'integration/connector/qdrant-connector'
52
- }
53
- }
54
-
55
- ConnectionManager.registerConnector('qdrant-connector', new QdrantConnector())
@@ -1,2 +0,0 @@
1
- import './connector/index.js'
2
- import './task/index.js'
@@ -1,3 +0,0 @@
1
- import './qdrant-search.js'
2
- import './qdrant-upsert.js'
3
- import './qdrant-fetch.js'
@@ -1,53 +0,0 @@
1
- import fetch from 'node-fetch'
2
- import { ConnectionManager, Context, TaskRegistry } from '@things-factory/integration-base'
3
- import { access } from '@things-factory/utils'
4
-
5
- async function qdrantFetch(step, { domain, data }: Context) {
6
- const {
7
- connection,
8
- params: { collection, resultAccessor }
9
- } = step
10
-
11
- let searchResults = access(resultAccessor, data)
12
- if (!searchResults || !Array.isArray(searchResults)) {
13
- throw new Error(`Invalid or missing search results`)
14
- }
15
-
16
- const qdrantConfig = await ConnectionManager.getConnectionInstanceByName(domain, connection)
17
- if (!qdrantConfig) {
18
- throw new Error(`No connection: ${connection}`)
19
- }
20
-
21
- let fetchedResults = []
22
- for (const result of searchResults) {
23
- const pointId = result.id
24
-
25
- try {
26
- const response = await fetch(`${qdrantConfig.endpoint}/collections/${collection}/points/${pointId}`, {
27
- method: 'GET',
28
- headers: { 'Content-Type': 'application/json' }
29
- })
30
-
31
- if (!response.ok) {
32
- console.warn(`⚠️ Failed to fetch point ID: ${pointId}`)
33
- continue
34
- }
35
-
36
- const pointData = await response.json()
37
- fetchedResults.push({ ...result, metadata: pointData.result })
38
- } catch (error) {
39
- console.error(`❌ Qdrant fetch error for ID ${pointId}: ${error.message}`)
40
- }
41
- }
42
-
43
- return { data: fetchedResults }
44
- }
45
-
46
- // ✅ 파라미터 정의
47
- qdrantFetch.parameterSpec = [
48
- { type: 'string', name: 'collection', label: 'qdrant.collection' },
49
- { type: 'string', name: 'resultAccessor', label: 'qdrant.result-accessor' }
50
- ]
51
-
52
- // ✅ 태스크 등록
53
- TaskRegistry.registerTaskHandler('qdrant-fetch', qdrantFetch)
@@ -1,53 +0,0 @@
1
- import fetch from 'node-fetch'
2
- import { ConnectionManager, Context, TaskRegistry } from '@things-factory/integration-base'
3
- import { access } from '@things-factory/utils'
4
-
5
- async function qdrantSearch(step, { domain, data }: Context) {
6
- const {
7
- connection,
8
- params: { collection, embeddingAccessor }
9
- } = step
10
-
11
- // ✅ `embeddingAccessor`를 사용해 벡터 가져오기
12
- let embedding = access(embeddingAccessor, data)
13
-
14
- if (!embedding || !Array.isArray(embedding)) {
15
- throw new Error(`Valid embedding vector is required for search.`)
16
- }
17
-
18
- const qdrantConfig = await ConnectionManager.getConnectionInstanceByName(domain, connection)
19
- if (!qdrantConfig) {
20
- throw new Error(`No connection: ${connection}`)
21
- }
22
-
23
- const payload = {
24
- vector: embedding,
25
- limit: 5
26
- }
27
-
28
- try {
29
- const response = await fetch(`${qdrantConfig.endpoint}/collections/${collection}/points/search`, {
30
- method: 'POST',
31
- headers: { 'Content-Type': 'application/json' },
32
- body: JSON.stringify(payload)
33
- })
34
-
35
- if (!response.ok) {
36
- throw new Error(`Failed to search data: ${response.statusText}`)
37
- }
38
-
39
- const results = await response.json()
40
- return { data: results }
41
- } catch (error) {
42
- throw new Error(`Qdrant search error: ${error.message}`)
43
- }
44
- }
45
-
46
- // ✅ 파라미터 정의
47
- qdrantSearch.parameterSpec = [
48
- { type: 'string', name: 'collection', label: 'qdrant.collection' },
49
- { type: 'string', name: 'embeddingAccessor', label: 'qdrant.embedding-accessor' }
50
- ]
51
-
52
- // ✅ 태스크 등록
53
- TaskRegistry.registerTaskHandler('qdrant-search', qdrantSearch)
@@ -1,149 +0,0 @@
1
- import fetch from 'node-fetch'
2
-
3
- import { ConnectionManager, Context, TaskRegistry } from '@things-factory/integration-base'
4
- import { access } from '@things-factory/utils'
5
- import crypto from 'crypto'
6
-
7
- // ✅ 네트워크 요청 타임아웃 추가
8
- async function fetchWithTimeout(url, options = {}, timeout = 5000) {
9
- const controller = new AbortController()
10
- const timeoutId = setTimeout(() => controller.abort(), timeout)
11
-
12
- try {
13
- const response = await fetch(url, { ...options, signal: controller.signal })
14
- clearTimeout(timeoutId)
15
- return response
16
- } catch (error) {
17
- console.error('❌ ERROR: Fetch failed (Timeout or Network issue)', error)
18
- throw new Error('Fetch failed: ' + error.message)
19
- }
20
- }
21
-
22
- // ✅ 파일 경로를 UUID 변환
23
- function generateUUIDFromPath(filePath) {
24
- return crypto.createHash('md5').update(filePath).digest('hex')
25
- }
26
-
27
- // ✅ Qdrant 컬렉션 존재 여부 확인
28
- async function checkCollectionExists(endpoint, collection) {
29
- const response = await fetchWithTimeout(`${endpoint}/collections/${collection}`, { method: 'GET' })
30
- return response.ok
31
- }
32
-
33
- // ✅ Qdrant 컬렉션 자동 생성
34
- async function createCollectionIfNotExists(endpoint, collection) {
35
- const exists = await checkCollectionExists(endpoint, collection)
36
- if (exists) return
37
-
38
- const createPayload = {
39
- vectors: { size: 384, distance: 'Cosine' } // ✅ 벡터 크기 384로 설정
40
- }
41
-
42
- const response = await fetchWithTimeout(`${endpoint}/collections/${collection}`, {
43
- method: 'PUT',
44
- headers: { 'Content-Type': 'application/json' },
45
- body: JSON.stringify(createPayload)
46
- })
47
-
48
- if (!response.ok) {
49
- throw new Error(`Failed to create collection: ${response.statusText}`)
50
- }
51
- }
52
-
53
- // ✅ Qdrant 데이터 업서트 태스크
54
- async function qdrantUpsert(step, { domain, data }: Context) {
55
- const {
56
- connection,
57
- params: { collection, embeddingDataAccessor, analysisDataAccessor }
58
- } = step
59
-
60
- let embeddingData = access(embeddingDataAccessor, data)
61
- let analysisData = access(analysisDataAccessor, data)
62
-
63
- if (!embeddingData || !Array.isArray(embeddingData) || embeddingData.length === 0) {
64
- throw new Error('Embedding data is missing or empty')
65
- }
66
-
67
- if (!analysisData || !Array.isArray(analysisData) || analysisData.length === 0) {
68
- throw new Error('Analysis result is missing or not in correct format')
69
- }
70
-
71
- const qdrantConfig = await ConnectionManager.getConnectionInstanceByName(domain, connection)
72
- if (!qdrantConfig) {
73
- throw new Error(`No connection: ${connection}`)
74
- }
75
-
76
- console.log('🔍 DEBUG: Qdrant Endpoint', qdrantConfig.endpoint)
77
-
78
- const safeCollection = collection.replace(/[^a-zA-Z0-9-_]/g, '_')
79
- await createCollectionIfNotExists(qdrantConfig.endpoint, safeCollection)
80
-
81
- const analysisMap = new Map(analysisData.map(item => [item.filePath, item.ast]))
82
-
83
- console.log(
84
- '🔍 DEBUG: Vector Length Check',
85
- embeddingData.map(e => e.embedding.length)
86
- )
87
-
88
- // ✅ 벡터 길이 384인지 확인
89
- if (embeddingData.some(e => e.embedding.length !== 384)) {
90
- throw new Error('🚨 ERROR: One or more embeddings have incorrect size! Expected 384.')
91
- }
92
-
93
- const points = embeddingData.map(item => {
94
- const filePath = item.file
95
- const pointId = generateUUIDFromPath(filePath)
96
- const ast = analysisMap.get(filePath) || { functions: [], classes: [], code: '' }
97
-
98
- return {
99
- id: pointId,
100
- vector: item.embedding,
101
- payload: {
102
- filePath,
103
- functions: ast.functions,
104
- classes: ast.classes,
105
- code: ast.code,
106
- type: item.type
107
- }
108
- }
109
- })
110
-
111
- console.log('🔍 DEBUG: Number of points', points.length)
112
-
113
- if (points.length === 0) {
114
- throw new Error('No valid points to insert into Qdrant')
115
- }
116
-
117
- const payload = { points }
118
-
119
- try {
120
- const response = await fetchWithTimeout(`${qdrantConfig.endpoint}/collections/${safeCollection}/points`, {
121
- method: 'PUT',
122
- headers: { 'Content-Type': 'application/json' },
123
- body: JSON.stringify(payload)
124
- })
125
-
126
- const responseText = await response.text()
127
- console.log('🔍 DEBUG: Qdrant Response Text', responseText)
128
-
129
- if (!response.ok) {
130
- throw new Error(`Failed to upsert data: ${responseText}`)
131
- }
132
- } catch (error) {
133
- console.error(`❌ Qdrant upsert error: ${error.message}`)
134
- throw new Error(`Qdrant upsert failed`)
135
- }
136
-
137
- return { data: points }
138
- }
139
-
140
- // ✅ 파라미터 스펙 업데이트
141
- qdrantUpsert.parameterSpec = [
142
- { type: 'string', name: 'collection', label: 'qdrant.collection' },
143
- { type: 'string', name: 'embeddingDataAccessor', label: 'qdrant.embedding-accessor' },
144
- { type: 'string', name: 'analysisDataAccessor', label: 'qdrant.analysis-accessor' }
145
- ]
146
-
147
- qdrantUpsert.help = 'integration/task/qdrant-upsert'
148
-
149
- TaskRegistry.registerTaskHandler('qdrant-upsert', qdrantUpsert)
package/server/index.ts DELETED
@@ -1 +0,0 @@
1
- import './engine'
@@ -1,10 +0,0 @@
1
- {
2
- "extends": "../../tsconfig-base.json",
3
- "compilerOptions": {
4
- "strict": false,
5
- "module": "commonjs",
6
- "outDir": "../dist-server",
7
- "baseUrl": "./"
8
- },
9
- "include": ["./**/*"]
10
- }