strapi-content-embeddings 0.1.9 → 0.2.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.
@@ -441,7 +441,8 @@ const GetEmbeddingSchema = zod.z.object({
441
441
  const CreateEmbeddingSchema = zod.z.object({
442
442
  title: zod.z.string().min(1, "Title is required"),
443
443
  content: zod.z.string().min(1, "Content is required"),
444
- metadata: zod.z.record(zod.z.any()).optional()
444
+ metadata: zod.z.record(zod.z.any()).optional(),
445
+ autoChunk: zod.z.boolean().optional().describe("Automatically split large content into chunks")
445
446
  });
446
447
  const ToolSchemas = {
447
448
  semantic_search: SemanticSearchSchema,
@@ -463,28 +464,12 @@ function validateToolInput(toolName, input) {
463
464
  return result.data;
464
465
  }
465
466
  const semanticSearchTool = {
466
- name: "semantic_search",
467
- description: 'TRIGGER: Use when user types "/rag" or asks to search embeddings/content. Search for semantically similar content using vector embeddings. Returns the most relevant documents matching your query based on meaning, not just keywords.',
468
- inputSchema: {
469
- type: "object",
470
- properties: {
471
- query: {
472
- type: "string",
473
- description: "The search query text to find similar content"
474
- },
475
- limit: {
476
- type: "number",
477
- description: "Maximum number of results to return (default: 5, max: 20)",
478
- default: 5
479
- }
480
- },
481
- required: ["query"]
482
- }
483
- };
484
- async function handleSemanticSearch(strapi, args) {
485
- const { query, limit = 5 } = args;
486
- const maxLimit = Math.min(limit, 20);
487
- try {
467
+ name: "semanticSearch",
468
+ description: "Search for semantically similar content using vector embeddings. Finds relevant documents by meaning, not just keywords.",
469
+ schema: SemanticSearchSchema,
470
+ execute: async (args, strapi) => {
471
+ const { query, limit = 5 } = args;
472
+ const maxLimit = Math.min(limit, 20);
488
473
  const pluginManager2 = strapi.contentEmbeddingsManager;
489
474
  if (!pluginManager2) {
490
475
  throw new Error("Content embeddings plugin not initialized");
@@ -497,49 +482,49 @@ async function handleSemanticSearch(strapi, args) {
497
482
  score: doc.score || null
498
483
  }));
499
484
  return {
500
- content: [
501
- {
502
- type: "text",
503
- text: JSON.stringify(
504
- {
505
- query,
506
- resultCount: formattedResults.length,
507
- results: formattedResults
508
- },
509
- null,
510
- 2
511
- )
512
- }
513
- ]
485
+ query,
486
+ resultCount: formattedResults.length,
487
+ results: formattedResults
514
488
  };
515
- } catch (error) {
516
- throw new Error(
517
- `Semantic search failed: ${error instanceof Error ? error.message : String(error)}`
518
- );
519
- }
520
- }
521
- const ragQueryTool = {
522
- name: "rag_query",
523
- description: 'TRIGGER: Use when user types "/rag" followed by a question. Ask a question and get an AI-generated answer based on your embedded content. Uses RAG (Retrieval-Augmented Generation) to find relevant documents and generate a contextual response. This is the PRIMARY tool for /rag queries.',
489
+ },
490
+ publicSafe: true
491
+ };
492
+ const semanticSearchMcpTool = {
493
+ name: "semantic_search",
494
+ description: semanticSearchTool.description,
524
495
  inputSchema: {
525
496
  type: "object",
526
497
  properties: {
527
498
  query: {
528
499
  type: "string",
529
- description: "The question or query to answer using embedded content"
500
+ description: "The search query text to find similar content"
530
501
  },
531
- includeSourceDocuments: {
532
- type: "boolean",
533
- description: "Include the source documents used to generate the answer (default: true)",
534
- default: true
502
+ limit: {
503
+ type: "number",
504
+ description: "Maximum number of results to return (default: 5, max: 20)",
505
+ default: 5
535
506
  }
536
507
  },
537
508
  required: ["query"]
538
509
  }
539
510
  };
540
- async function handleRagQuery(strapi, args) {
541
- const { query, includeSourceDocuments = true } = args;
542
- try {
511
+ async function handleSemanticSearch(strapi, args) {
512
+ const result = await semanticSearchTool.execute(args, strapi);
513
+ return {
514
+ content: [
515
+ {
516
+ type: "text",
517
+ text: JSON.stringify(result, null, 2)
518
+ }
519
+ ]
520
+ };
521
+ }
522
+ const ragQueryTool = {
523
+ name: "ragQuery",
524
+ description: "Ask a question and get an AI-generated answer grounded in embedded content. Uses retrieval-augmented generation (RAG) with vector search.",
525
+ schema: RagQuerySchema,
526
+ execute: async (args, strapi) => {
527
+ const { query, includeSourceDocuments = true } = args;
543
528
  const embeddingsService = strapi.plugin("strapi-content-embeddings").service("embeddings");
544
529
  const result = await embeddingsService.queryEmbeddings(query);
545
530
  const response = {
@@ -554,48 +539,47 @@ async function handleRagQuery(strapi, args) {
554
539
  }));
555
540
  response.sourceCount = result.sourceDocuments.length;
556
541
  }
557
- return {
558
- content: [
559
- {
560
- type: "text",
561
- text: JSON.stringify(response, null, 2)
562
- }
563
- ]
564
- };
565
- } catch (error) {
566
- throw new Error(
567
- `RAG query failed: ${error instanceof Error ? error.message : String(error)}`
568
- );
569
- }
570
- }
571
- const listEmbeddingsTool = {
572
- name: "list_embeddings",
573
- description: "List all embeddings stored in the database. Returns metadata without the full content to avoid context overflow.",
542
+ return response;
543
+ },
544
+ publicSafe: true
545
+ };
546
+ const ragQueryMcpTool = {
547
+ name: "rag_query",
548
+ description: ragQueryTool.description,
574
549
  inputSchema: {
575
550
  type: "object",
576
551
  properties: {
577
- page: {
578
- type: "number",
579
- description: "Page number (starts at 1)",
580
- default: 1
581
- },
582
- pageSize: {
583
- type: "number",
584
- description: "Number of items per page (max: 50)",
585
- default: 25
586
- },
587
- search: {
552
+ query: {
588
553
  type: "string",
589
- description: "Search filter for title"
554
+ description: "The question or query to answer using embedded content"
555
+ },
556
+ includeSourceDocuments: {
557
+ type: "boolean",
558
+ description: "Include the source documents used to generate the answer (default: true)",
559
+ default: true
590
560
  }
591
561
  },
592
- required: []
562
+ required: ["query"]
593
563
  }
594
564
  };
595
- async function handleListEmbeddings(strapi, args) {
596
- const { page = 1, pageSize = 25, search } = args;
597
- const limit = Math.min(pageSize, 50);
598
- try {
565
+ async function handleRagQuery(strapi, args) {
566
+ const result = await ragQueryTool.execute(args, strapi);
567
+ return {
568
+ content: [
569
+ {
570
+ type: "text",
571
+ text: JSON.stringify(result, null, 2)
572
+ }
573
+ ]
574
+ };
575
+ }
576
+ const listEmbeddingsTool = {
577
+ name: "listEmbeddings",
578
+ description: "List all embedded documents with pagination. Returns metadata and content preview without full text.",
579
+ schema: ListEmbeddingsSchema,
580
+ execute: async (args, strapi) => {
581
+ const { page = 1, pageSize = 25, search } = args;
582
+ const limit = Math.min(pageSize, 50);
599
583
  const embeddingsService = strapi.plugin("strapi-content-embeddings").service("embeddings");
600
584
  const filters = {};
601
585
  if (search) {
@@ -618,65 +602,63 @@ async function handleListEmbeddings(strapi, args) {
618
602
  updatedAt: emb.updatedAt
619
603
  }));
620
604
  return {
621
- content: [
622
- {
623
- type: "text",
624
- text: JSON.stringify(
625
- {
626
- embeddings: embeddings2,
627
- pagination: result.pagination || {
628
- page,
629
- pageSize: limit,
630
- total: embeddings2.length
631
- }
632
- },
633
- null,
634
- 2
635
- )
636
- }
637
- ]
605
+ embeddings: embeddings2,
606
+ pagination: result.pagination || {
607
+ page,
608
+ pageSize: limit,
609
+ total: embeddings2.length
610
+ }
638
611
  };
639
- } catch (error) {
640
- throw new Error(
641
- `Failed to list embeddings: ${error instanceof Error ? error.message : String(error)}`
642
- );
643
- }
644
- }
645
- const getEmbeddingTool = {
646
- name: "get_embedding",
647
- description: "Get a specific embedding by its document ID. Returns the full content and metadata.",
612
+ },
613
+ publicSafe: true
614
+ };
615
+ const listEmbeddingsMcpTool = {
616
+ name: "list_embeddings",
617
+ description: listEmbeddingsTool.description,
648
618
  inputSchema: {
649
619
  type: "object",
650
620
  properties: {
651
- documentId: {
652
- type: "string",
653
- description: "The document ID of the embedding to retrieve"
621
+ page: {
622
+ type: "number",
623
+ description: "Page number (starts at 1)",
624
+ default: 1
654
625
  },
655
- includeContent: {
656
- type: "boolean",
657
- description: "Include the full content text (default: true)",
658
- default: true
626
+ pageSize: {
627
+ type: "number",
628
+ description: "Number of items per page (max: 50)",
629
+ default: 25
630
+ },
631
+ search: {
632
+ type: "string",
633
+ description: "Search filter for title"
659
634
  }
660
635
  },
661
- required: ["documentId"]
636
+ required: []
662
637
  }
663
638
  };
664
- async function handleGetEmbedding(strapi, args) {
665
- const { documentId, includeContent = true } = args;
666
- try {
639
+ async function handleListEmbeddings(strapi, args) {
640
+ const result = await listEmbeddingsTool.execute(args, strapi);
641
+ return {
642
+ content: [
643
+ {
644
+ type: "text",
645
+ text: JSON.stringify(result, null, 2)
646
+ }
647
+ ]
648
+ };
649
+ }
650
+ const getEmbeddingTool = {
651
+ name: "getEmbedding",
652
+ description: "Get a specific embedded document by its document ID. Returns full content and metadata.",
653
+ schema: GetEmbeddingSchema,
654
+ execute: async (args, strapi) => {
655
+ const { documentId, includeContent = true } = args;
667
656
  const embeddingsService = strapi.plugin("strapi-content-embeddings").service("embeddings");
668
657
  const embedding2 = await embeddingsService.getEmbedding(documentId);
669
658
  if (!embedding2) {
670
659
  return {
671
- content: [
672
- {
673
- type: "text",
674
- text: JSON.stringify({
675
- error: true,
676
- message: `Embedding not found with documentId: ${documentId}`
677
- })
678
- }
679
- ]
660
+ error: true,
661
+ message: `Embedding not found with documentId: ${documentId}`
680
662
  };
681
663
  }
682
664
  const result = {
@@ -693,49 +675,46 @@ async function handleGetEmbedding(strapi, args) {
693
675
  if (includeContent) {
694
676
  result.content = embedding2.content;
695
677
  }
696
- return {
697
- content: [
698
- {
699
- type: "text",
700
- text: JSON.stringify(result, null, 2)
701
- }
702
- ]
703
- };
704
- } catch (error) {
705
- throw new Error(
706
- `Failed to get embedding: ${error instanceof Error ? error.message : String(error)}`
707
- );
708
- }
709
- }
710
- const createEmbeddingTool = {
711
- name: "create_embedding",
712
- description: "Create a new embedding from text content. The content will be vectorized and stored for semantic search. For large content (over 4000 characters), enable autoChunk to automatically split into multiple embeddings.",
678
+ return result;
679
+ },
680
+ publicSafe: true
681
+ };
682
+ const getEmbeddingMcpTool = {
683
+ name: "get_embedding",
684
+ description: getEmbeddingTool.description,
713
685
  inputSchema: {
714
686
  type: "object",
715
687
  properties: {
716
- title: {
717
- type: "string",
718
- description: "A descriptive title for the embedding"
719
- },
720
- content: {
688
+ documentId: {
721
689
  type: "string",
722
- description: "The text content to embed (will be vectorized)"
723
- },
724
- metadata: {
725
- type: "object",
726
- description: "Optional metadata to associate with the embedding (tags, source, etc.)"
690
+ description: "The document ID of the embedding to retrieve"
727
691
  },
728
- autoChunk: {
692
+ includeContent: {
729
693
  type: "boolean",
730
- description: "Automatically split large content into chunks (default: false). When enabled, content over 4000 characters will be split into multiple embeddings with overlap for context preservation."
694
+ description: "Include the full content text (default: true)",
695
+ default: true
731
696
  }
732
697
  },
733
- required: ["title", "content"]
698
+ required: ["documentId"]
734
699
  }
735
700
  };
736
- async function handleCreateEmbedding(strapi, args) {
737
- const { title, content, metadata, autoChunk } = args;
738
- try {
701
+ async function handleGetEmbedding(strapi, args) {
702
+ const result = await getEmbeddingTool.execute(args, strapi);
703
+ return {
704
+ content: [
705
+ {
706
+ type: "text",
707
+ text: JSON.stringify(result, null, 2)
708
+ }
709
+ ]
710
+ };
711
+ }
712
+ const createEmbeddingTool = {
713
+ name: "createEmbedding",
714
+ description: "Create a new embedding from text content for future semantic search. Large content can be auto-chunked into multiple embeddings.",
715
+ schema: CreateEmbeddingSchema,
716
+ execute: async (args, strapi) => {
717
+ const { title, content, metadata, autoChunk } = args;
739
718
  const embeddingsService = strapi.plugin("strapi-content-embeddings").service("embeddings");
740
719
  if (autoChunk) {
741
720
  const result = await embeddingsService.createChunkedEmbedding({
@@ -748,34 +727,23 @@ async function handleCreateEmbedding(strapi, args) {
748
727
  }
749
728
  });
750
729
  return {
751
- content: [
752
- {
753
- type: "text",
754
- text: JSON.stringify(
755
- {
756
- success: true,
757
- message: result.wasChunked ? `Content chunked into ${result.totalChunks} embeddings` : "Embedding created successfully (no chunking needed)",
758
- wasChunked: result.wasChunked,
759
- totalChunks: result.totalChunks,
760
- primaryEmbedding: {
761
- id: result.entity.id,
762
- documentId: result.entity.documentId,
763
- title: result.entity.title,
764
- embeddingId: result.entity.embeddingId
765
- },
766
- chunks: result.chunks.map((chunk) => ({
767
- documentId: chunk.documentId,
768
- title: chunk.title,
769
- contentLength: chunk.content?.length || 0
770
- })),
771
- contentLength: content.length,
772
- estimatedTokens: Math.ceil(content.length / 4)
773
- },
774
- null,
775
- 2
776
- )
777
- }
778
- ]
730
+ success: true,
731
+ message: result.wasChunked ? `Content chunked into ${result.totalChunks} embeddings` : "Embedding created successfully (no chunking needed)",
732
+ wasChunked: result.wasChunked,
733
+ totalChunks: result.totalChunks,
734
+ primaryEmbedding: {
735
+ id: result.entity.id,
736
+ documentId: result.entity.documentId,
737
+ title: result.entity.title,
738
+ embeddingId: result.entity.embeddingId
739
+ },
740
+ chunks: result.chunks.map((chunk) => ({
741
+ documentId: chunk.documentId,
742
+ title: chunk.title,
743
+ contentLength: chunk.content?.length || 0
744
+ })),
745
+ contentLength: content.length,
746
+ estimatedTokens: Math.ceil(content.length / 4)
779
747
  };
780
748
  }
781
749
  const embedding2 = await embeddingsService.createEmbedding({
@@ -788,42 +756,65 @@ async function handleCreateEmbedding(strapi, args) {
788
756
  }
789
757
  });
790
758
  return {
791
- content: [
792
- {
793
- type: "text",
794
- text: JSON.stringify(
795
- {
796
- success: true,
797
- message: "Embedding created successfully",
798
- embedding: {
799
- id: embedding2.id,
800
- documentId: embedding2.documentId,
801
- title: embedding2.title,
802
- embeddingId: embedding2.embeddingId,
803
- contentLength: content.length,
804
- metadata: embedding2.metadata,
805
- createdAt: embedding2.createdAt
806
- },
807
- hint: content.length > 4e3 ? "Content is large. Consider using autoChunk: true for better search results." : void 0
808
- },
809
- null,
810
- 2
811
- )
812
- }
813
- ]
759
+ success: true,
760
+ message: "Embedding created successfully",
761
+ embedding: {
762
+ id: embedding2.id,
763
+ documentId: embedding2.documentId,
764
+ title: embedding2.title,
765
+ embeddingId: embedding2.embeddingId,
766
+ contentLength: content.length,
767
+ metadata: embedding2.metadata,
768
+ createdAt: embedding2.createdAt
769
+ },
770
+ hint: content.length > 4e3 ? "Content is large. Consider using autoChunk: true for better search results." : void 0
814
771
  };
815
- } catch (error) {
816
- throw new Error(
817
- `Failed to create embedding: ${error instanceof Error ? error.message : String(error)}`
818
- );
772
+ },
773
+ publicSafe: false
774
+ };
775
+ const createEmbeddingMcpTool = {
776
+ name: "create_embedding",
777
+ description: createEmbeddingTool.description,
778
+ inputSchema: {
779
+ type: "object",
780
+ properties: {
781
+ title: {
782
+ type: "string",
783
+ description: "A descriptive title for the embedding"
784
+ },
785
+ content: {
786
+ type: "string",
787
+ description: "The text content to embed (will be vectorized)"
788
+ },
789
+ metadata: {
790
+ type: "object",
791
+ description: "Optional metadata to associate with the embedding (tags, source, etc.)"
792
+ },
793
+ autoChunk: {
794
+ type: "boolean",
795
+ description: "Automatically split large content into chunks (default: false). When enabled, content over 4000 characters will be split into multiple embeddings with overlap for context preservation."
796
+ }
797
+ },
798
+ required: ["title", "content"]
819
799
  }
800
+ };
801
+ async function handleCreateEmbedding(strapi, args) {
802
+ const result = await createEmbeddingTool.execute(args, strapi);
803
+ return {
804
+ content: [
805
+ {
806
+ type: "text",
807
+ text: JSON.stringify(result, null, 2)
808
+ }
809
+ ]
810
+ };
820
811
  }
821
- const tools = [
822
- semanticSearchTool,
823
- ragQueryTool,
824
- listEmbeddingsTool,
825
- getEmbeddingTool,
826
- createEmbeddingTool
812
+ const tools$1 = [
813
+ semanticSearchMcpTool,
814
+ ragQueryMcpTool,
815
+ listEmbeddingsMcpTool,
816
+ getEmbeddingMcpTool,
817
+ createEmbeddingMcpTool
827
818
  ];
828
819
  const toolHandlers = {
829
820
  semantic_search: handleSemanticSearch,
@@ -883,7 +874,7 @@ function createMcpServer(strapi) {
883
874
  }
884
875
  );
885
876
  server.setRequestHandler(types_js.ListToolsRequestSchema, async () => {
886
- return { tools };
877
+ return { tools: tools$1 };
887
878
  });
888
879
  server.setRequestHandler(types_js.CallToolRequestSchema, async (request) => {
889
880
  return handleToolCall(strapi, request);
@@ -2070,12 +2061,12 @@ const embeddings = ({ strapi }) => ({
2070
2061
  return chunks.length;
2071
2062
  },
2072
2063
  /**
2073
- * Update a single chunk's content and embedding
2074
- * Updates only the specified chunk without affecting other chunks in the group
2064
+ * Update embeddings with automatic chunking support
2065
+ * Handles re-chunking when content changes and exceeds chunk size
2075
2066
  */
2076
2067
  async updateChunkedEmbedding(id, data) {
2077
- const { title, content, metadata } = data.data;
2078
- this.getConfig();
2068
+ const { title, content, metadata, autoChunk } = data.data;
2069
+ const config2 = this.getConfig();
2079
2070
  const currentEntry = await strapi.documents(CONTENT_TYPE_UID$1).findOne({
2080
2071
  documentId: id
2081
2072
  });
@@ -2083,61 +2074,64 @@ const embeddings = ({ strapi }) => ({
2083
2074
  throw new Error(`Embedding with id ${id} not found`);
2084
2075
  }
2085
2076
  const currentMetadata = currentEntry.metadata;
2086
- const contentChanged = content !== void 0 && content !== currentEntry.content;
2087
- console.log(`[updateChunkedEmbedding] Updating single chunk ${id}, contentChanged: ${contentChanged}`);
2088
- const updateData = {};
2089
- if (title !== void 0) {
2090
- const currentTitle = currentEntry.title || "";
2091
- const partMatch = currentTitle.match(/\s*\[Part \d+\/\d+\]$/);
2092
- updateData.title = partMatch ? `${title}${partMatch[0]}` : title;
2093
- }
2094
- if (content !== void 0) {
2095
- updateData.content = content;
2096
- }
2097
- if (metadata !== void 0) {
2098
- updateData.metadata = {
2099
- ...currentMetadata,
2100
- ...metadata
2101
- };
2102
- if (contentChanged) {
2103
- updateData.metadata.estimatedTokens = estimateTokens(updateData.content || currentEntry.content);
2104
- }
2105
- }
2106
- const updatedEntity = await strapi.documents(CONTENT_TYPE_UID$1).update({
2107
- documentId: id,
2108
- data: updateData
2109
- });
2110
- if (pluginManager.isInitialized() && (contentChanged || title !== void 0)) {
2111
- try {
2112
- console.log(`[updateChunkedEmbedding] Updating embedding in Neon for chunk ${id}`);
2113
- await pluginManager.deleteEmbedding(id);
2114
- const newContent = updateData.content || currentEntry.content;
2115
- const result = await pluginManager.createEmbedding({
2116
- id,
2117
- title: updatedEntity.title || currentEntry.title,
2077
+ const parentDocumentId = currentMetadata?.parentDocumentId || id;
2078
+ const newContent = content ?? currentEntry.content;
2079
+ const newTitle = title ?? currentMetadata?.originalTitle ?? currentEntry.title;
2080
+ const shouldChunk = autoChunk ?? config2.autoChunk;
2081
+ const chunkSize = config2.chunkSize || 4e3;
2082
+ const contentNeedsChunking = shouldChunk && needsChunking(newContent, chunkSize);
2083
+ const existingChunks = await this.findRelatedChunks(id);
2084
+ let originalRelated;
2085
+ const firstChunk = existingChunks.find(
2086
+ (c) => c.metadata?.chunkIndex === 0 || c.documentId === parentDocumentId
2087
+ );
2088
+ if (firstChunk?.related) {
2089
+ originalRelated = firstChunk.related;
2090
+ }
2091
+ const deletedCount = await this.deleteRelatedChunks(id);
2092
+ console.log(`Deleted ${deletedCount} existing chunk(s) for update`);
2093
+ const preservedMetadata = { ...metadata };
2094
+ delete preservedMetadata?.isChunk;
2095
+ delete preservedMetadata?.chunkIndex;
2096
+ delete preservedMetadata?.totalChunks;
2097
+ delete preservedMetadata?.startOffset;
2098
+ delete preservedMetadata?.endOffset;
2099
+ delete preservedMetadata?.originalTitle;
2100
+ delete preservedMetadata?.parentDocumentId;
2101
+ delete preservedMetadata?.estimatedTokens;
2102
+ if (contentNeedsChunking) {
2103
+ return await this.createChunkedEmbedding({
2104
+ data: {
2105
+ title: newTitle.replace(/\s*\[Part \d+\/\d+\]$/, ""),
2106
+ // Remove old part suffix
2118
2107
  content: newContent,
2119
2108
  collectionType: currentEntry.collectionType || "standalone",
2120
- fieldName: currentEntry.fieldName || "content"
2121
- });
2122
- await strapi.documents(CONTENT_TYPE_UID$1).update({
2123
- documentId: id,
2124
- data: {
2125
- embeddingId: result.embeddingId,
2126
- embedding: result.embedding
2127
- }
2128
- });
2129
- console.log(`[updateChunkedEmbedding] Successfully updated embedding for chunk ${id}`);
2130
- } catch (error) {
2131
- console.error(`Failed to update vector store for ${id}:`, error);
2132
- }
2109
+ fieldName: currentEntry.fieldName || "content",
2110
+ metadata: preservedMetadata,
2111
+ related: originalRelated,
2112
+ autoChunk: true
2113
+ }
2114
+ });
2115
+ } else {
2116
+ const entity = await this.createEmbedding({
2117
+ data: {
2118
+ title: newTitle.replace(/\s*\[Part \d+\/\d+\]$/, ""),
2119
+ // Remove old part suffix
2120
+ content: newContent,
2121
+ collectionType: currentEntry.collectionType || "standalone",
2122
+ fieldName: currentEntry.fieldName || "content",
2123
+ metadata: preservedMetadata,
2124
+ related: originalRelated,
2125
+ autoChunk: false
2126
+ }
2127
+ });
2128
+ return {
2129
+ entity,
2130
+ chunks: [entity],
2131
+ totalChunks: 1,
2132
+ wasChunked: false
2133
+ };
2133
2134
  }
2134
- const allChunks = await this.findRelatedChunks(id);
2135
- return {
2136
- entity: updatedEntity,
2137
- chunks: allChunks,
2138
- totalChunks: allChunks.length,
2139
- wasChunked: allChunks.length > 1
2140
- };
2141
2135
  },
2142
2136
  async updateEmbedding(id, data) {
2143
2137
  const { title, content: rawContent, metadata, autoChunk } = data.data;
@@ -2531,9 +2525,22 @@ const sync = ({ strapi }) => ({
2531
2525
  }
2532
2526
  }
2533
2527
  });
2528
+ const tools = [
2529
+ semanticSearchTool,
2530
+ ragQueryTool,
2531
+ listEmbeddingsTool,
2532
+ getEmbeddingTool,
2533
+ createEmbeddingTool
2534
+ ];
2535
+ const aiTools = ({ strapi }) => ({
2536
+ getTools() {
2537
+ return tools;
2538
+ }
2539
+ });
2534
2540
  const services = {
2535
2541
  embeddings,
2536
- sync
2542
+ sync,
2543
+ "ai-tools": aiTools
2537
2544
  };
2538
2545
  const index = {
2539
2546
  register,