@nahisaho/shikigami 1.1.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.
Files changed (80) hide show
  1. package/.github/prompts/shikigami-deep-research.prompt.md +32 -0
  2. package/.github/prompts/shikigami-framework-analysis.prompt.md +40 -0
  3. package/.github/prompts/shikigami-full-research.prompt.md +54 -0
  4. package/.github/prompts/shikigami-purpose-discovery.prompt.md +32 -0
  5. package/.github/prompts/shikigami-report-writing.prompt.md +36 -0
  6. package/.github/skills/shikigami-consulting-framework/SKILL.md +403 -0
  7. package/.github/skills/shikigami-consulting-framework/frameworks/README.md +173 -0
  8. package/.github/skills/shikigami-consulting-framework/frameworks/customer/nps.md +164 -0
  9. package/.github/skills/shikigami-consulting-framework/frameworks/customer/rfm.md +160 -0
  10. package/.github/skills/shikigami-consulting-framework/frameworks/decision-making/cost-benefit.md +168 -0
  11. package/.github/skills/shikigami-consulting-framework/frameworks/decision-making/decision-matrix.md +138 -0
  12. package/.github/skills/shikigami-consulting-framework/frameworks/decision-making/pros-cons.md +162 -0
  13. package/.github/skills/shikigami-consulting-framework/frameworks/decision-making/risk-matrix.md +159 -0
  14. package/.github/skills/shikigami-consulting-framework/frameworks/general/5w1h.md +152 -0
  15. package/.github/skills/shikigami-consulting-framework/frameworks/general/jtbd.md +176 -0
  16. package/.github/skills/shikigami-consulting-framework/frameworks/general/kpt.md +149 -0
  17. package/.github/skills/shikigami-consulting-framework/frameworks/general/okr.md +155 -0
  18. package/.github/skills/shikigami-consulting-framework/frameworks/general/smart.md +130 -0
  19. package/.github/skills/shikigami-consulting-framework/frameworks/innovation/aarrr.md +193 -0
  20. package/.github/skills/shikigami-consulting-framework/frameworks/innovation/business-model-canvas.md +182 -0
  21. package/.github/skills/shikigami-consulting-framework/frameworks/innovation/design-thinking.md +275 -0
  22. package/.github/skills/shikigami-consulting-framework/frameworks/innovation/lean-canvas.md +199 -0
  23. package/.github/skills/shikigami-consulting-framework/frameworks/innovation/scamper.md +188 -0
  24. package/.github/skills/shikigami-consulting-framework/frameworks/innovation/tam-sam-som.md +231 -0
  25. package/.github/skills/shikigami-consulting-framework/frameworks/innovation/value-proposition-canvas.md +194 -0
  26. package/.github/skills/shikigami-consulting-framework/frameworks/marketing/4c.md +179 -0
  27. package/.github/skills/shikigami-consulting-framework/frameworks/marketing/4p.md +161 -0
  28. package/.github/skills/shikigami-consulting-framework/frameworks/marketing/aidma-aisas.md +146 -0
  29. package/.github/skills/shikigami-consulting-framework/frameworks/marketing/customer-journey.md +155 -0
  30. package/.github/skills/shikigami-consulting-framework/frameworks/marketing/persona.md +182 -0
  31. package/.github/skills/shikigami-consulting-framework/frameworks/marketing/positioning-map.md +116 -0
  32. package/.github/skills/shikigami-consulting-framework/frameworks/marketing/stp.md +177 -0
  33. package/.github/skills/shikigami-consulting-framework/frameworks/organization/7s.md +154 -0
  34. package/.github/skills/shikigami-consulting-framework/frameworks/organization/raci.md +147 -0
  35. package/.github/skills/shikigami-consulting-framework/frameworks/problem-solving/5whys.md +142 -0
  36. package/.github/skills/shikigami-consulting-framework/frameworks/problem-solving/as-is-to-be.md +186 -0
  37. package/.github/skills/shikigami-consulting-framework/frameworks/problem-solving/fishbone.md +201 -0
  38. package/.github/skills/shikigami-consulting-framework/frameworks/problem-solving/issue-tree.md +178 -0
  39. package/.github/skills/shikigami-consulting-framework/frameworks/problem-solving/logic-tree.md +161 -0
  40. package/.github/skills/shikigami-consulting-framework/frameworks/problem-solving/mece.md +127 -0
  41. package/.github/skills/shikigami-consulting-framework/frameworks/problem-solving/sora-ame-kasa.md +176 -0
  42. package/.github/skills/shikigami-consulting-framework/frameworks/process/ecrs.md +168 -0
  43. package/.github/skills/shikigami-consulting-framework/frameworks/process/ooda.md +144 -0
  44. package/.github/skills/shikigami-consulting-framework/frameworks/process/pdca.md +113 -0
  45. package/.github/skills/shikigami-consulting-framework/frameworks/strategic/3c.md +118 -0
  46. package/.github/skills/shikigami-consulting-framework/frameworks/strategic/5forces.md +135 -0
  47. package/.github/skills/shikigami-consulting-framework/frameworks/strategic/ansoff-matrix.md +168 -0
  48. package/.github/skills/shikigami-consulting-framework/frameworks/strategic/bcg-matrix.md +134 -0
  49. package/.github/skills/shikigami-consulting-framework/frameworks/strategic/blue-ocean.md +184 -0
  50. package/.github/skills/shikigami-consulting-framework/frameworks/strategic/ge-matrix.md +158 -0
  51. package/.github/skills/shikigami-consulting-framework/frameworks/strategic/pest.md +106 -0
  52. package/.github/skills/shikigami-consulting-framework/frameworks/strategic/swot.md +90 -0
  53. package/.github/skills/shikigami-consulting-framework/frameworks/strategic/value-chain.md +192 -0
  54. package/.github/skills/shikigami-consulting-framework/frameworks/strategic/vrio.md +163 -0
  55. package/.github/skills/shikigami-consulting-framework/frameworks/thinking/prep.md +105 -0
  56. package/.github/skills/shikigami-consulting-framework/frameworks/thinking/pyramid.md +171 -0
  57. package/.github/skills/shikigami-consulting-framework/frameworks/thinking/so-what-why-so.md +175 -0
  58. package/.github/skills/shikigami-deep-research/SKILL.md +395 -0
  59. package/.github/skills/shikigami-planner/SKILL.md +267 -0
  60. package/.github/skills/shikigami-writing/SKILL.md +782 -0
  61. package/.vscode/mcp.json +9 -0
  62. package/AGENTS.md +310 -0
  63. package/CHANGELOG.md +109 -0
  64. package/README.md +144 -0
  65. package/mcp-server/README.md +80 -0
  66. package/mcp-server/package-lock.json +2123 -0
  67. package/mcp-server/package.json +38 -0
  68. package/mcp-server/shikigami.config.example.yaml +93 -0
  69. package/mcp-server/src/config/index.ts +8 -0
  70. package/mcp-server/src/config/loader.ts +246 -0
  71. package/mcp-server/src/config/types.ts +184 -0
  72. package/mcp-server/src/index.ts +418 -0
  73. package/mcp-server/src/tools/embedding.ts +279 -0
  74. package/mcp-server/src/tools/file-parser.ts +332 -0
  75. package/mcp-server/src/tools/search.ts +181 -0
  76. package/mcp-server/src/tools/visit.ts +168 -0
  77. package/mcp-server/tsconfig.json +19 -0
  78. package/package.json +82 -0
  79. package/scripts/init.js +181 -0
  80. package/scripts/postinstall.js +129 -0
@@ -0,0 +1,418 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * SHIKIGAMI MCP Server
4
+ *
5
+ * Deep Research tools with DuckDuckGo search and Jina AI page fetching.
6
+ * Implements REQ-DR-002, REQ-DR-003, REQ-CS-002, REQ-CS-003, REQ-CS-004 from requirements.
7
+ */
8
+
9
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
10
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
11
+ import {
12
+ CallToolRequestSchema,
13
+ ListToolsRequestSchema,
14
+ type CallToolRequest,
15
+ type Tool,
16
+ } from '@modelcontextprotocol/sdk/types.js';
17
+
18
+ import { searchDuckDuckGo, type SearchResult } from './tools/search.js';
19
+ import { visitPage, type PageContent } from './tools/visit.js';
20
+ import { parseFile, parseFiles, parseDirectory, type ParsedFile } from './tools/file-parser.js';
21
+ import {
22
+ embed,
23
+ embedBatch,
24
+ similarity,
25
+ semanticSearch,
26
+ type EmbeddingResult,
27
+ type SimilarityResult,
28
+ type SearchResult as SemanticSearchResult,
29
+ } from './tools/embedding.js';
30
+
31
+ // Tool definitions
32
+ const TOOLS: Tool[] = [
33
+ {
34
+ name: 'search',
35
+ description: `バッチWeb検索を実行(DuckDuckGo使用)。
36
+ 複数クエリを配列で指定可。各クエリのTop10結果を返却。
37
+ Deep Researchの情報収集フェーズで使用。`,
38
+ inputSchema: {
39
+ type: 'object',
40
+ properties: {
41
+ query: {
42
+ oneOf: [
43
+ { type: 'string', description: '単一の検索クエリ' },
44
+ {
45
+ type: 'array',
46
+ items: { type: 'string' },
47
+ minItems: 1,
48
+ description: '検索クエリのリスト(バッチ検索)',
49
+ },
50
+ ],
51
+ description: '検索クエリ(文字列または文字列配列)',
52
+ },
53
+ maxResults: {
54
+ type: 'number',
55
+ default: 10,
56
+ description: '各クエリあたりの最大結果数(デフォルト: 10)',
57
+ },
58
+ },
59
+ required: ['query'],
60
+ },
61
+ },
62
+ {
63
+ name: 'visit',
64
+ description: `Webページを訪問し、内容のテキストを抽出(Jina AI Reader使用)。
65
+ LLM用に最適化されたクリーンなテキストを返却。
66
+ Deep Researchの詳細調査フェーズで使用。`,
67
+ inputSchema: {
68
+ type: 'object',
69
+ properties: {
70
+ url: {
71
+ oneOf: [
72
+ { type: 'string', description: '単一のURL' },
73
+ {
74
+ type: 'array',
75
+ items: { type: 'string' },
76
+ description: 'URLのリスト(バッチ訪問)',
77
+ },
78
+ ],
79
+ description: '訪問するURL(文字列または文字列配列)',
80
+ },
81
+ goal: {
82
+ type: 'string',
83
+ description: 'このページ訪問の目的(抽出する情報の指針)',
84
+ },
85
+ },
86
+ required: ['url'],
87
+ },
88
+ },
89
+ {
90
+ name: 'parse_file',
91
+ description: `ローカルファイルを解析してテキストを抽出。
92
+ 対応形式: テキスト(txt,md,json,yaml,csv)、コード(ts,js,py等)。
93
+ ドキュメント調査やコード解析に使用。`,
94
+ inputSchema: {
95
+ type: 'object',
96
+ properties: {
97
+ path: {
98
+ oneOf: [
99
+ { type: 'string', description: '単一のファイルパス' },
100
+ {
101
+ type: 'array',
102
+ items: { type: 'string' },
103
+ description: 'ファイルパスのリスト(バッチ解析)',
104
+ },
105
+ ],
106
+ description: '解析するファイルパス(文字列または文字列配列)',
107
+ },
108
+ },
109
+ required: ['path'],
110
+ },
111
+ },
112
+ {
113
+ name: 'parse_directory',
114
+ description: `ディレクトリ内のファイルを再帰的に解析。
115
+ コードベース全体の理解や、プロジェクト構造の把握に使用。
116
+ node_modules, .git等は自動除外。`,
117
+ inputSchema: {
118
+ type: 'object',
119
+ properties: {
120
+ path: {
121
+ type: 'string',
122
+ description: '解析するディレクトリパス',
123
+ },
124
+ extensions: {
125
+ type: 'array',
126
+ items: { type: 'string' },
127
+ description: '解析する拡張子リスト(例: [".ts", ".js"])',
128
+ },
129
+ exclude: {
130
+ type: 'array',
131
+ items: { type: 'string' },
132
+ description: '除外パターン(デフォルト: node_modules, .git, dist, build)',
133
+ },
134
+ maxFiles: {
135
+ type: 'number',
136
+ default: 100,
137
+ description: '最大ファイル数(デフォルト: 100)',
138
+ },
139
+ },
140
+ required: ['path'],
141
+ },
142
+ },
143
+ {
144
+ name: 'embed',
145
+ description: `テキストの埋め込みベクトルを生成(Ollama/OpenAI使用)。
146
+ セマンティック検索や類似度計算の基盤。`,
147
+ inputSchema: {
148
+ type: 'object',
149
+ properties: {
150
+ text: {
151
+ oneOf: [
152
+ { type: 'string', description: '単一のテキスト' },
153
+ {
154
+ type: 'array',
155
+ items: { type: 'string' },
156
+ description: 'テキストのリスト(バッチ処理)',
157
+ },
158
+ ],
159
+ description: '埋め込みを生成するテキスト',
160
+ },
161
+ },
162
+ required: ['text'],
163
+ },
164
+ },
165
+ {
166
+ name: 'similarity',
167
+ description: `2つのテキスト間の意味的類似度を計算(0-1のスコア)。
168
+ コンテンツの関連性判定に使用。`,
169
+ inputSchema: {
170
+ type: 'object',
171
+ properties: {
172
+ textA: {
173
+ type: 'string',
174
+ description: '比較するテキストA',
175
+ },
176
+ textB: {
177
+ type: 'string',
178
+ description: '比較するテキストB',
179
+ },
180
+ },
181
+ required: ['textA', 'textB'],
182
+ },
183
+ },
184
+ {
185
+ name: 'semantic_search',
186
+ description: `クエリに最も類似したドキュメントを検索。
187
+ 関連情報の発見やコンテキスト取得に使用。`,
188
+ inputSchema: {
189
+ type: 'object',
190
+ properties: {
191
+ query: {
192
+ type: 'string',
193
+ description: '検索クエリ',
194
+ },
195
+ documents: {
196
+ type: 'array',
197
+ items: {
198
+ type: 'object',
199
+ properties: {
200
+ text: { type: 'string', description: 'ドキュメントテキスト' },
201
+ metadata: { type: 'object', description: 'メタデータ(任意)' },
202
+ },
203
+ required: ['text'],
204
+ },
205
+ description: '検索対象ドキュメントのリスト',
206
+ },
207
+ topK: {
208
+ type: 'number',
209
+ default: 5,
210
+ description: '返却する最大件数(デフォルト: 5)',
211
+ },
212
+ minScore: {
213
+ type: 'number',
214
+ default: 0,
215
+ description: '最小類似度閾値(デフォルト: 0)',
216
+ },
217
+ },
218
+ required: ['query', 'documents'],
219
+ },
220
+ },
221
+ ];
222
+
223
+ // Server setup
224
+ const server = new Server(
225
+ {
226
+ name: 'shikigami-mcp-server',
227
+ version: '1.0.0',
228
+ },
229
+ {
230
+ capabilities: {
231
+ tools: {},
232
+ },
233
+ }
234
+ );
235
+
236
+ // List tools handler
237
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
238
+ return { tools: TOOLS };
239
+ });
240
+
241
+ // Call tool handler
242
+ server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest) => {
243
+ const { name, arguments: args } = request.params;
244
+
245
+ try {
246
+ switch (name) {
247
+ case 'search': {
248
+ const queryInput = args?.query as string | string[];
249
+ const maxResults = (args?.maxResults as number) ?? 10;
250
+
251
+ const queries = Array.isArray(queryInput) ? queryInput : [queryInput];
252
+ const results: Record<string, SearchResult[]> = {};
253
+
254
+ for (const query of queries) {
255
+ results[query] = await searchDuckDuckGo(query, maxResults);
256
+ }
257
+
258
+ return {
259
+ content: [
260
+ {
261
+ type: 'text',
262
+ text: JSON.stringify(results, null, 2),
263
+ },
264
+ ],
265
+ };
266
+ }
267
+
268
+ case 'visit': {
269
+ const urlInput = args?.url as string | string[];
270
+ const goal = (args?.goal as string) ?? '';
271
+
272
+ const urls = Array.isArray(urlInput) ? urlInput : [urlInput];
273
+ const results: PageContent[] = [];
274
+
275
+ for (const url of urls) {
276
+ const content = await visitPage(url, goal);
277
+ results.push(content);
278
+ }
279
+
280
+ return {
281
+ content: [
282
+ {
283
+ type: 'text',
284
+ text: JSON.stringify(results, null, 2),
285
+ },
286
+ ],
287
+ };
288
+ }
289
+
290
+ case 'parse_file': {
291
+ const pathInput = args?.path as string | string[];
292
+
293
+ const paths = Array.isArray(pathInput) ? pathInput : [pathInput];
294
+ const results: ParsedFile[] = await parseFiles(paths);
295
+
296
+ return {
297
+ content: [
298
+ {
299
+ type: 'text',
300
+ text: JSON.stringify(results, null, 2),
301
+ },
302
+ ],
303
+ };
304
+ }
305
+
306
+ case 'parse_directory': {
307
+ const dirPath = args?.path as string;
308
+ const extensions = args?.extensions as string[] | undefined;
309
+ const exclude = args?.exclude as string[] | undefined;
310
+ const maxFiles = (args?.maxFiles as number) ?? 100;
311
+
312
+ const results: ParsedFile[] = await parseDirectory(dirPath, {
313
+ extensions,
314
+ exclude,
315
+ maxFiles,
316
+ });
317
+
318
+ return {
319
+ content: [
320
+ {
321
+ type: 'text',
322
+ text: JSON.stringify(results, null, 2),
323
+ },
324
+ ],
325
+ };
326
+ }
327
+
328
+ case 'embed': {
329
+ const textInput = args?.text as string | string[];
330
+
331
+ if (Array.isArray(textInput)) {
332
+ const results: EmbeddingResult[] = await embedBatch(textInput);
333
+ return {
334
+ content: [
335
+ {
336
+ type: 'text',
337
+ text: JSON.stringify(results, null, 2),
338
+ },
339
+ ],
340
+ };
341
+ } else {
342
+ const result: EmbeddingResult = await embed(textInput);
343
+ return {
344
+ content: [
345
+ {
346
+ type: 'text',
347
+ text: JSON.stringify(result, null, 2),
348
+ },
349
+ ],
350
+ };
351
+ }
352
+ }
353
+
354
+ case 'similarity': {
355
+ const textA = args?.textA as string;
356
+ const textB = args?.textB as string;
357
+
358
+ const result: SimilarityResult = await similarity(textA, textB);
359
+
360
+ return {
361
+ content: [
362
+ {
363
+ type: 'text',
364
+ text: JSON.stringify(result, null, 2),
365
+ },
366
+ ],
367
+ };
368
+ }
369
+
370
+ case 'semantic_search': {
371
+ const query = args?.query as string;
372
+ const documents = args?.documents as Array<{ text: string; metadata?: Record<string, unknown> }>;
373
+ const topK = (args?.topK as number) ?? 5;
374
+ const minScore = (args?.minScore as number) ?? 0;
375
+
376
+ const results: SemanticSearchResult[] = await semanticSearch(query, documents, {
377
+ topK,
378
+ minScore,
379
+ });
380
+
381
+ return {
382
+ content: [
383
+ {
384
+ type: 'text',
385
+ text: JSON.stringify(results, null, 2),
386
+ },
387
+ ],
388
+ };
389
+ }
390
+
391
+ default:
392
+ throw new Error(`Unknown tool: ${name}`);
393
+ }
394
+ } catch (error) {
395
+ const message = error instanceof Error ? error.message : String(error);
396
+ return {
397
+ content: [
398
+ {
399
+ type: 'text',
400
+ text: `Error: ${message}`,
401
+ },
402
+ ],
403
+ isError: true,
404
+ };
405
+ }
406
+ });
407
+
408
+ // Start server
409
+ async function main() {
410
+ const transport = new StdioServerTransport();
411
+ await server.connect(transport);
412
+ console.error('SHIKIGAMI MCP Server running on stdio');
413
+ }
414
+
415
+ main().catch((error) => {
416
+ console.error('Fatal error:', error);
417
+ process.exit(1);
418
+ });
@@ -0,0 +1,279 @@
1
+ /**
2
+ * Embedding Service
3
+ *
4
+ * Implements REQ-CS-002: セマンティック検索
5
+ *
6
+ * Supports:
7
+ * - Ollama (local, default)
8
+ * - OpenAI Embeddings API
9
+ *
10
+ * Used for semantic search, similarity matching, and context retrieval.
11
+ */
12
+
13
+ import { getConfig } from '../config/loader.js';
14
+
15
+ export interface EmbeddingResult {
16
+ /** 入力テキスト */
17
+ text: string;
18
+ /** 埋め込みベクトル */
19
+ embedding: number[];
20
+ /** モデル名 */
21
+ model: string;
22
+ /** ベクトル次元数 */
23
+ dimensions: number;
24
+ }
25
+
26
+ export interface SimilarityResult {
27
+ /** テキストA */
28
+ textA: string;
29
+ /** テキストB */
30
+ textB: string;
31
+ /** コサイン類似度 (0-1) */
32
+ similarity: number;
33
+ }
34
+
35
+ export interface SearchResult {
36
+ /** 検索対象テキスト */
37
+ text: string;
38
+ /** 類似度スコア */
39
+ score: number;
40
+ /** インデックス(元の配列での位置) */
41
+ index: number;
42
+ /** メタデータ(任意) */
43
+ metadata?: Record<string, unknown>;
44
+ }
45
+
46
+ /**
47
+ * コサイン類似度を計算
48
+ */
49
+ function cosineSimilarity(a: number[], b: number[]): number {
50
+ if (a.length !== b.length) {
51
+ throw new Error('Vectors must have the same dimensions');
52
+ }
53
+
54
+ let dotProduct = 0;
55
+ let normA = 0;
56
+ let normB = 0;
57
+
58
+ for (let i = 0; i < a.length; i++) {
59
+ dotProduct += a[i] * b[i];
60
+ normA += a[i] * a[i];
61
+ normB += b[i] * b[i];
62
+ }
63
+
64
+ const magnitude = Math.sqrt(normA) * Math.sqrt(normB);
65
+ if (magnitude === 0) return 0;
66
+
67
+ return dotProduct / magnitude;
68
+ }
69
+
70
+ /**
71
+ * Ollama Embedding API を呼び出し
72
+ */
73
+ async function embedWithOllama(
74
+ text: string,
75
+ model: string,
76
+ endpoint: string
77
+ ): Promise<number[]> {
78
+ const response = await fetch(`${endpoint}/api/embeddings`, {
79
+ method: 'POST',
80
+ headers: {
81
+ 'Content-Type': 'application/json',
82
+ },
83
+ body: JSON.stringify({
84
+ model,
85
+ prompt: text,
86
+ }),
87
+ });
88
+
89
+ if (!response.ok) {
90
+ throw new Error(`Ollama embedding failed: ${response.status} ${response.statusText}`);
91
+ }
92
+
93
+ const data = await response.json() as { embedding: number[] };
94
+ return data.embedding;
95
+ }
96
+
97
+ /**
98
+ * OpenAI Embedding API を呼び出し
99
+ */
100
+ async function embedWithOpenAI(
101
+ text: string,
102
+ model: string,
103
+ apiKey: string
104
+ ): Promise<number[]> {
105
+ const response = await fetch('https://api.openai.com/v1/embeddings', {
106
+ method: 'POST',
107
+ headers: {
108
+ 'Content-Type': 'application/json',
109
+ 'Authorization': `Bearer ${apiKey}`,
110
+ },
111
+ body: JSON.stringify({
112
+ model,
113
+ input: text,
114
+ }),
115
+ });
116
+
117
+ if (!response.ok) {
118
+ const errorText = await response.text();
119
+ throw new Error(`OpenAI embedding failed: ${response.status} ${errorText}`);
120
+ }
121
+
122
+ const data = await response.json() as {
123
+ data: Array<{ embedding: number[] }>;
124
+ };
125
+
126
+ return data.data[0].embedding;
127
+ }
128
+
129
+ /**
130
+ * テキストの埋め込みベクトルを生成
131
+ */
132
+ export async function embed(text: string): Promise<EmbeddingResult> {
133
+ const config = getConfig();
134
+ const embeddingConfig = config.embedding;
135
+
136
+ const provider = embeddingConfig?.provider ?? 'ollama';
137
+ const model = embeddingConfig?.model ?? 'nomic-embed-text';
138
+
139
+ let embedding: number[];
140
+
141
+ switch (provider) {
142
+ case 'ollama': {
143
+ const endpoint = embeddingConfig?.options?.endpoint ?? 'http://localhost:11434';
144
+ embedding = await embedWithOllama(text, model, endpoint);
145
+ break;
146
+ }
147
+
148
+ case 'openai': {
149
+ const apiKey = embeddingConfig?.options?.apiKey ?? process.env.OPENAI_API_KEY;
150
+ if (!apiKey) {
151
+ throw new Error('OpenAI API key is required for embedding');
152
+ }
153
+ embedding = await embedWithOpenAI(text, model, apiKey);
154
+ break;
155
+ }
156
+
157
+ default:
158
+ throw new Error(`Unsupported embedding provider: ${provider}`);
159
+ }
160
+
161
+ return {
162
+ text,
163
+ embedding,
164
+ model,
165
+ dimensions: embedding.length,
166
+ };
167
+ }
168
+
169
+ /**
170
+ * 複数テキストの埋め込みベクトルを生成
171
+ */
172
+ export async function embedBatch(texts: string[]): Promise<EmbeddingResult[]> {
173
+ const results: EmbeddingResult[] = [];
174
+
175
+ for (const text of texts) {
176
+ const result = await embed(text);
177
+ results.push(result);
178
+ }
179
+
180
+ return results;
181
+ }
182
+
183
+ /**
184
+ * 2つのテキストの類似度を計算
185
+ */
186
+ export async function similarity(textA: string, textB: string): Promise<SimilarityResult> {
187
+ const [embA, embB] = await Promise.all([embed(textA), embed(textB)]);
188
+
189
+ const sim = cosineSimilarity(embA.embedding, embB.embedding);
190
+
191
+ return {
192
+ textA,
193
+ textB,
194
+ similarity: sim,
195
+ };
196
+ }
197
+
198
+ /**
199
+ * クエリに最も類似したテキストを検索
200
+ */
201
+ export async function semanticSearch(
202
+ query: string,
203
+ documents: Array<{ text: string; metadata?: Record<string, unknown> }>,
204
+ options?: {
205
+ /** 返却する最大件数 */
206
+ topK?: number;
207
+ /** 最小類似度閾値 */
208
+ minScore?: number;
209
+ }
210
+ ): Promise<SearchResult[]> {
211
+ const topK = options?.topK ?? 5;
212
+ const minScore = options?.minScore ?? 0;
213
+
214
+ // クエリの埋め込みを生成
215
+ const queryEmbedding = await embed(query);
216
+
217
+ // 各ドキュメントの埋め込みを生成して類似度を計算
218
+ const results: SearchResult[] = [];
219
+
220
+ for (let i = 0; i < documents.length; i++) {
221
+ const doc = documents[i];
222
+ const docEmbedding = await embed(doc.text);
223
+ const score = cosineSimilarity(queryEmbedding.embedding, docEmbedding.embedding);
224
+
225
+ if (score >= minScore) {
226
+ results.push({
227
+ text: doc.text,
228
+ score,
229
+ index: i,
230
+ metadata: doc.metadata,
231
+ });
232
+ }
233
+ }
234
+
235
+ // スコア降順でソートしてtopK件を返却
236
+ results.sort((a, b) => b.score - a.score);
237
+
238
+ return results.slice(0, topK);
239
+ }
240
+
241
+ /**
242
+ * ベクトルを使った高速セマンティック検索
243
+ * (事前に埋め込みが計算済みの場合)
244
+ */
245
+ export function semanticSearchWithVectors(
246
+ queryEmbedding: number[],
247
+ documentEmbeddings: Array<{
248
+ embedding: number[];
249
+ text: string;
250
+ metadata?: Record<string, unknown>;
251
+ }>,
252
+ options?: {
253
+ topK?: number;
254
+ minScore?: number;
255
+ }
256
+ ): SearchResult[] {
257
+ const topK = options?.topK ?? 5;
258
+ const minScore = options?.minScore ?? 0;
259
+
260
+ const results: SearchResult[] = [];
261
+
262
+ for (let i = 0; i < documentEmbeddings.length; i++) {
263
+ const doc = documentEmbeddings[i];
264
+ const score = cosineSimilarity(queryEmbedding, doc.embedding);
265
+
266
+ if (score >= minScore) {
267
+ results.push({
268
+ text: doc.text,
269
+ score,
270
+ index: i,
271
+ metadata: doc.metadata,
272
+ });
273
+ }
274
+ }
275
+
276
+ results.sort((a, b) => b.score - a.score);
277
+
278
+ return results.slice(0, topK);
279
+ }