diff --git a/docs.json b/docs.json index 42375b044..427c62d43 100644 --- a/docs.json +++ b/docs.json @@ -224,7 +224,8 @@ "en/plugin/dev/components/event-listener", "en/plugin/dev/components/command", "en/plugin/dev/components/tool", - "en/plugin/dev/components/knowledge-retriever" + "en/plugin/dev/components/knowledge-engine", + "en/plugin/dev/components/parser" ] }, "en/plugin/dev/style", @@ -428,7 +429,8 @@ "zh/plugin/dev/components/event-listener", "zh/plugin/dev/components/command", "zh/plugin/dev/components/tool", - "zh/plugin/dev/components/knowledge-retriever" + "zh/plugin/dev/components/knowledge-engine", + "zh/plugin/dev/components/parser" ] }, "zh/plugin/dev/style", @@ -622,7 +624,8 @@ "ja/plugin/dev/components/event-listener", "ja/plugin/dev/components/command", "ja/plugin/dev/components/tool", - "ja/plugin/dev/components/knowledge-retriever" + "ja/plugin/dev/components/knowledge-engine", + "ja/plugin/dev/components/parser" ] }, "ja/plugin/dev/style", diff --git a/en/plugin/dev/apis/common.mdx b/en/plugin/dev/apis/common.mdx index f820d7c08..80d84f899 100644 --- a/en/plugin/dev/apis/common.mdx +++ b/en/plugin/dev/apis/common.mdx @@ -307,4 +307,192 @@ async def get_config_file(self, file_key: str) -> bytes: file_bytes = await self.plugin.get_config_file("key") ``` -Use this in conjunction with configuration fields of type [``file or array[file``](/en/plugin/dev/basic-info.html#type-file). \ No newline at end of file +Use this in conjunction with configuration fields of type [``file or array[file``](/en/plugin/dev/basic-info.html#type-file). + +## RAG API + +These APIs are available for `KnowledgeEngine` components to access the LangBot host's embedding models, vector database, and file storage. Access method: + +- In `KnowledgeEngine` component classes: Internal methods of the `self.plugin` object. + +### Invoke Embedding Model + +Generate text embeddings using the host's configured embedding model. + +```python +async def invoke_embedding( + self, + embedding_model_uuid: str, + texts: list[str], +) -> list[list[float]]: + """Generate embeddings using host's embedding model + + Args: + embedding_model_uuid: Embedding model UUID + texts: List of texts to embed + + Returns: + List of embedding vectors, one per input text + """ + +# Usage example +vectors = await self.plugin.invoke_embedding("model_uuid", ["Hello", "World"]) +``` + +### Vector Upsert + +Upsert vectors to the host's vector database. + +```python +async def vector_upsert( + self, + collection_id: str, + vectors: list[list[float]], + ids: list[str], + metadata: list[dict[str, Any]] | None = None, + documents: list[str] | None = None, +) -> None: + """Upsert vectors + + Args: + collection_id: Target collection ID + vectors: List of vectors + ids: List of unique IDs for vectors + metadata: Optional list of metadata dicts + documents: Optional raw text documents. Required for full-text + and hybrid search in backends that support them. + """ + +# Usage example +await self.plugin.vector_upsert( + collection_id="kb_uuid", + vectors=[[0.1, 0.2, ...], [0.3, 0.4, ...]], + ids=["chunk_0", "chunk_1"], + metadata=[{"document_id": "doc1"}, {"document_id": "doc1"}], + documents=["chunk text 0", "chunk text 1"], +) +``` + +### Vector Search + +Search similar vectors in the host's vector database. + +```python +async def vector_search( + self, + collection_id: str, + query_vector: list[float], + top_k: int = 5, + filters: dict[str, Any] | None = None, + search_type: str = "vector", + query_text: str = "", +) -> list[dict[str, Any]]: + """Vector search + + Args: + collection_id: Target collection ID + query_vector: Query vector for similarity search + top_k: Number of results to return + filters: Optional metadata filters + search_type: One of 'vector', 'full_text', 'hybrid' + query_text: Raw query text, used for full_text and hybrid search + + Returns: + List of search results (dict with id, score, metadata, etc.) + """ + +# Usage example +results = await self.plugin.vector_search( + collection_id="kb_uuid", + query_vector=[0.1, 0.2, ...], + top_k=5, + search_type="hybrid", + query_text="search query", +) +# Return format: [{"id": "chunk_0", "score": 0.123, "metadata": {"document_id": "doc1", ...}}, ...] +``` + + +Each result returned by `vector_search` is a dict containing `id` (vector ID), `score` (distance score), and `metadata` (metadata provided during upsert). If you need text content in retrieval results, store the text in metadata during ingestion. + + +### Vector Delete + +Delete vectors from the host's vector database. + +```python +async def vector_delete( + self, + collection_id: str, + file_ids: list[str] | None = None, + filters: dict[str, Any] | None = None, +) -> int: + """Vector delete + + Args: + collection_id: Target collection ID + file_ids: File IDs whose vectors should be deleted + filters: Optional metadata filters for deletion + + Returns: + Number of deleted items + """ + +# Usage example +deleted = await self.plugin.vector_delete( + collection_id="kb_uuid", + file_ids=["doc_001"], +) +``` + + +The `filters` parameter supports Chroma-style `where` syntax for metadata filtering. Multiple top-level keys are AND-ed. Supported operators: `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin`. + +```python +# Implicit $eq +results = await self.plugin.vector_search( + collection_id="kb_uuid", + query_vector=[0.1, 0.2, ...], + filters={"file_id": "abc"}, +) + +# Comparison operator +results = await self.plugin.vector_search( + collection_id="kb_uuid", + query_vector=[0.1, 0.2, ...], + filters={"created_at": {"$gte": 1700000000}}, +) + +# In-list operator +results = await self.plugin.vector_search( + collection_id="kb_uuid", + query_vector=[0.1, 0.2, ...], + filters={"file_type": {"$in": ["pdf", "docx"]}}, +) + +# Delete by filter +deleted = await self.plugin.vector_delete( + collection_id="kb_uuid", + filters={"file_type": {"$eq": "pdf"}}, +) +``` + +**Note:** Chroma, Qdrant, and SeekDB store full metadata and can filter on any field. Milvus and pgvector only store `text`, `file_id`, and `chunk_uuid` — filters on other fields will be silently ignored. + + +Get uploaded file content from the host's storage. + +```python +async def get_knowledge_file_stream(self, storage_path: str) -> bytes: + """Get file content + + Args: + storage_path: File storage path (from FileObject.storage_path) + + Returns: + File content as bytes + """ + +# Usage example +file_bytes = await self.plugin.get_knowledge_file_stream(context.file_object.storage_path) +``` \ No newline at end of file diff --git a/en/plugin/dev/basic-info.mdx b/en/plugin/dev/basic-info.mdx index a8b7dcc4f..1ce2e173d 100644 --- a/en/plugin/dev/basic-info.mdx +++ b/en/plugin/dev/basic-info.mdx @@ -56,6 +56,10 @@ spec: zh_Hans: 如果不填的话,图片可能会下载失败 default: '' # Configuration item default value, optional. required: false # Whether the configuration item is required, optional. + show_if: # Conditional rendering (shows when conditions are met), optional. + field: mode + operator: eq + value: mode1 - name: mode type: select ... @@ -63,6 +67,21 @@ spec: The supported types and fields for configuration items are as follows: +### Conditional Rendering (`show_if`) + +All types of configuration items support conditional rendering using the `show_if` field. You can decide whether the current field is rendered based on the value of another field in the same (or an associated) form. + +```yaml +- name: advanced_setting + type: string + show_if: + field: mode # The name of the dependent field + operator: eq # The operator, supports 'eq' (equals), 'neq' (not equals), 'in' (in a list) + value: advanced # The value to check against +``` + +> **Note:** Cross-form cascading relationships (e.g. `creation_schema` values reading from `retrieval_schema` values and vice versa in a Knowledge Engine) are supported in the latest LangBot frontend. + ### type: string String type. diff --git a/en/plugin/dev/components/add.mdx b/en/plugin/dev/components/add.mdx index 79bb15179..52bd58a04 100644 --- a/en/plugin/dev/components/add.mdx +++ b/en/plugin/dev/components/add.mdx @@ -7,7 +7,8 @@ Plugins consist of one or more components that provide different functionalities - Event Listeners (EventListener): Listen to events during pipeline execution and modify context or pipelines. - Commands (Command): Triggered by user command messages starting with `!` (or other configured prefixes). - Tools (Tool): Called by LLMs during execution of LangBot's built-in Local Agent. -- Knowledge Retrievers (KnowledgeRetriever): Provide external knowledge base retrieval capabilities for LangBot, supporting integration with various RAG systems. +- Knowledge Engines (KnowledgeEngine): Provide knowledge base indexing and retrieval capabilities for LangBot, managing document ingestion, retrieval, and deletion. +- Parsers (Parser): Parse binary files such as PDF, Word, etc. into structured text before document ingestion, for use by Knowledge Engines. ## Adding Components @@ -71,4 +72,5 @@ For detailed usage of each component, please refer to: - [EventListener](./event-listener) - [Command](./command) - [Tool](./tool) -- [KnowledgeRetriever](./knowledge-retriever) +- [KnowledgeEngine](./knowledge-engine) +- [Parser](./parser) diff --git a/en/plugin/dev/components/knowledge-engine.mdx b/en/plugin/dev/components/knowledge-engine.mdx new file mode 100644 index 000000000..8c8ccd008 --- /dev/null +++ b/en/plugin/dev/components/knowledge-engine.mdx @@ -0,0 +1,478 @@ +--- +title: "Component: Knowledge Engine" +--- + +The Knowledge Engine component allows plugins to provide full knowledge base indexing and retrieval capabilities for LangBot. When users create a knowledge base in LangBot, they can choose a Knowledge Engine provided by a plugin to manage document ingestion, retrieval, and deletion. Plugins can also bridge advanced knowledge base services such as Dify, RAGFlow, FastGPT, and more. + +## Adding a Knowledge Engine Component + +A single plugin can add any number of Knowledge Engines. Execute the command `lbp comp KnowledgeEngine` in the plugin directory and follow the prompts to enter the Knowledge Engine configuration. + +```bash +➜ MyRAGPlugin > lbp comp KnowledgeEngine +Generating component KnowledgeEngine... +Knowledge Engine name: simple_rag +Knowledge Engine description: A simple Knowledge Engine with built-in chunking and retrieval +Component KnowledgeEngine generated successfully. +``` + +This will generate `simple_rag.yaml` and `simple_rag.py` files in the `components/knowledge_engine/` directory. The `.yaml` file defines the Knowledge Engine's basic information and configuration schemas, and the `.py` file is the handler for this engine: + +```bash +➜ MyRAGPlugin > tree +... +├── components +│ ├── __init__.py +│ └── knowledge_engine +│ ├── __init__.py +│ ├── simple_rag.py +│ └── simple_rag.yaml +... +``` + +## Manifest File: Knowledge Engine + +```yaml +apiVersion: v1 # Do not modify +kind: KnowledgeEngine # Do not modify +metadata: + name: simple_rag # Knowledge Engine name, used to identify this engine + label: + en_US: Simple Knowledge Engine # Engine display name, shown in LangBot's UI, supports multiple languages + zh_Hans: 简易 RAG 引擎 + ja_JP: シンプル RAG エンジン + description: + en_US: 'A simple Knowledge Engine with built-in chunking and retrieval' + zh_Hans: '内置分块和检索能力的简易 RAG 引擎' + ja_JP: 'チャンキングと検索を内蔵したシンプルなRAGエンジン' +spec: + creation_schema: # Configuration parameters users fill in when creating a knowledge base + - name: chunk_size + label: + en_US: Chunk Size + zh_Hans: 分块大小 + type: integer + required: false + default: 500 + - name: chunk_overlap + label: + en_US: Chunk Overlap + zh_Hans: 分块重叠 + type: integer + required: false + default: 50 + retrieval_schema: # Configurable parameters during retrieval + - name: score_threshold + label: + en_US: Score Threshold + zh_Hans: 分数阈值 + type: float + required: false + default: 0.5 +execution: + python: + path: simple_rag.py # Engine handler, do not modify + attr: SimpleRag # Class name of the engine handler, consistent with the class name in simple_rag.py +``` + +For configuration item format reference, see: [Plugin Manifest Configuration Format](/en/plugin/dev/basic-info) + +### creation_schema and retrieval_schema + +Unlike the old `KnowledgeRetriever` component which used a single `spec.config`, KnowledgeEngine uses two separate schemas: + +- **creation_schema**: Parameters users fill in when creating a knowledge base. These are passed to the plugin via `creation_settings` during ingestion and retrieval. +- **retrieval_schema**: Parameters users can adjust when querying the knowledge base. These are passed to the plugin via `retrieval_settings`. + +## Capability Declaration + +KnowledgeEngine can declare its supported capabilities. LangBot uses these capability declarations to determine UI behavior and available operations: + +```python +from langbot_plugin.api.definition.components.knowledge_engine.engine import KnowledgeEngine, KnowledgeEngineCapability + +class SimpleRag(KnowledgeEngine): + + @classmethod + def get_capabilities(cls) -> list[str]: + return [ + KnowledgeEngineCapability.DOC_INGESTION, # Supports document upload and processing + ] +``` + +Available capability constants: + +| Capability | Description | +|-----------|-------------| +| `DOC_INGESTION` | Supports document upload and processing. When declared, LangBot shows a "Documents" tab in the knowledge base details. If the plugin is used to bridge other knowledge base services or does not require users to upload documents, this capability declaration can be omitted. | +| `DOC_PARSING` | Supports native document parsing (file-to-text extraction). When declared, users can choose to use the Knowledge Engine's built-in parsing when uploading documents, without needing an external [Parser plugin](./parser). If not declared, an external Parser plugin must be installed to upload documents. | + + +Other retrieval behaviors (such as reranking, hybrid search, etc.) are controlled by the plugin's `retrieval_schema` and do not need capability flags. + + +## Plugin Handler + +The following code will be generated by default (`components/knowledge_engine/.py`). You need to implement the three core methods: `ingest`, `retrieve`, and `delete_document`. Complete code can be found in the `SimpleKnowledgeEngine` example in [langbot-plugin-demo](https://github.com/langbot-app/langbot-plugin-demo). + +```python +from langbot_plugin.api.definition.components.knowledge_engine.engine import KnowledgeEngine, KnowledgeEngineCapability +from langbot_plugin.api.entities.builtin.rag.models import ( + IngestionContext, + IngestionResult, +) +from langbot_plugin.api.entities.builtin.rag.context import ( + RetrievalContext, + RetrievalResponse, + RetrievalResultEntry, +) +from langbot_plugin.api.entities.builtin.rag.enums import DocumentStatus +from langbot_plugin.api.entities.builtin.provider.message import ContentElement + + +class SimpleRag(KnowledgeEngine): + + @classmethod + def get_capabilities(cls) -> list[str]: + return [KnowledgeEngineCapability.DOC_INGESTION] + + async def on_knowledge_base_create(self, kb_id: str, config: dict) -> None: + """Callback when a knowledge base is created, can be used to initialize resources""" + pass + + async def on_knowledge_base_delete(self, kb_id: str) -> None: + """Callback when a knowledge base is deleted, can be used to clean up resources""" + pass + + async def ingest(self, context: IngestionContext) -> IngestionResult: + """Ingest a document into the knowledge base""" + + # 1. Get file content + file_bytes = await self.plugin.get_knowledge_file_stream(context.file_object.storage_path) + + # 2. Parse document and split into chunks + text = file_bytes.decode('utf-8') + chunk_size = context.creation_settings.get('chunk_size', 500) + chunk_overlap = context.creation_settings.get('chunk_overlap', 50) + chunks = self._split_text(text, chunk_size, chunk_overlap) + + # 3. Generate embeddings using host embedding model + embedding_model_uuid = context.creation_settings.get('embedding_model_uuid', '') + vectors = await self.plugin.invoke_embedding(embedding_model_uuid, chunks) + + # 4. Write to host vector database + collection_id = context.get_collection_id() + ids = [f"{context.file_object.metadata.document_id}_{i}" for i in range(len(chunks))] + metadata = [{"document_id": context.file_object.metadata.document_id, "chunk_index": i, "text": chunks[i]} for i in range(len(chunks))] + await self.plugin.vector_upsert(collection_id, vectors, ids, metadata) + + return IngestionResult( + document_id=context.file_object.metadata.document_id, + status=DocumentStatus.COMPLETED, + chunks_created=len(chunks), + ) + + async def retrieve(self, context: RetrievalContext) -> RetrievalResponse: + """Retrieve relevant content from the knowledge base""" + + # 1. Generate query vector + embedding_model_uuid = context.creation_settings.get('embedding_model_uuid', '') + query_vectors = await self.plugin.invoke_embedding(embedding_model_uuid, [context.query]) + query_vector = query_vectors[0] + + # 2. Vector search + collection_id = context.get_collection_id() + results = await self.plugin.vector_search(collection_id, query_vector, top_k=context.retrieval_settings.get('top_k', 5)) + + # 3. Convert to retrieval results + entries = [] + for r in results: + entry = RetrievalResultEntry( + id=r.get('id', ''), + content=[ContentElement.from_text(r.get('metadata', {}).get('text', ''))], + metadata=r.get('metadata', {}), + distance=r.get('score', 0.0), + ) + entries.append(entry) + + return RetrievalResponse( + results=entries, + total_found=len(entries), + metadata={}, + ) + + async def delete_document(self, kb_id: str, document_id: str) -> bool: + """Delete a document and its associated data from the knowledge base""" + collection_id = kb_id + deleted = await self.plugin.vector_delete(collection_id, file_ids=[document_id]) + return deleted > 0 +``` + +### Lifecycle Hooks + +KnowledgeEngine provides two lifecycle hooks that are called when knowledge bases are created and deleted: + +```python +async def on_knowledge_base_create(self, kb_id: str, config: dict) -> None: + """Callback when a knowledge base using this engine is created + + Args: + kb_id: Knowledge base UUID + config: User-provided configuration (fields from creation_schema) + """ + +async def on_knowledge_base_delete(self, kb_id: str) -> None: + """Callback when a knowledge base using this engine is deleted + + Args: + kb_id: Knowledge base UUID + """ +``` + +### Document Ingestion + +The `ingest` method is called when a user uploads a document to the knowledge base: + +```python +async def ingest(self, context: IngestionContext) -> IngestionResult: +``` + +**IngestionContext** contains the following information: + +```python +class IngestionContext(pydantic.BaseModel): + file_object: FileObject # File object to ingest + knowledge_base_id: str # Target knowledge base ID + collection_id: str | None # Vector collection ID (falls back to knowledge_base_id) + creation_settings: dict # Configuration from knowledge base creation + parsed_content: ParseResult | None # Pre-parsed content from external Parser plugin (if any) +``` + +**FileObject** contains file metadata: + +```python +class FileObject(pydantic.BaseModel): + metadata: FileMetadata # Contains filename, file_size, mime_type, document_id, knowledge_base_id, etc. + storage_path: str # File path in the storage system +``` + +**IngestionResult** should return the ingestion result: + +```python +class IngestionResult(pydantic.BaseModel): + document_id: str # Document ID + status: DocumentStatus # Processing status: COMPLETED / FAILED + chunks_created: int = 0 # Number of chunks created + error_message: str | None = None # Error message on failure + metadata: dict = {} # Additional metadata +``` + +### Knowledge Retrieval + +The `retrieve` method is called when the knowledge base is queried: + +```python +async def retrieve(self, context: RetrievalContext) -> RetrievalResponse: +``` + +**RetrievalContext** contains the following information: + +```python +class RetrievalContext(pydantic.BaseModel): + query: str # Query text + knowledge_base_id: str | None # Knowledge base ID + collection_id: str | None # Vector collection ID + retrieval_settings: dict # Retrieval configuration (fields from retrieval_schema) + creation_settings: dict # Knowledge base creation configuration + filters: dict # Metadata filter conditions (Chroma-style where syntax) +``` + + +The LangBot host automatically injects a default `top_k` value (default 5) into `retrieval_settings` before passing it to the plugin. Plugins can access it via `context.retrieval_settings.get('top_k', 5)`. + +The `filters` field contains Chroma-style `where` filter conditions extracted from `retrieval_settings`. When the caller provides `filters` in the retrieval settings (e.g., filtering by time range, file type, or custom metadata fields), the host populates this field so plugins can apply them during retrieval. If no filters are provided, this field is an empty dict. + + +**RetrievalResponse** should return the retrieval results: + +```python +class RetrievalResponse(pydantic.BaseModel): + results: list[RetrievalResultEntry] # List of retrieval results + total_found: int # Total number of matches + metadata: dict # Additional metadata +``` + +**RetrievalResultEntry** represents a single retrieval result: + +```python +class RetrievalResultEntry(pydantic.BaseModel): + id: str # Result ID + content: list[ContentElement] # Content, create using ContentElement.from_text() + metadata: dict # Metadata + distance: float # Distance score (smaller is more relevant) + score: float | None = None # Similarity score (larger is more relevant) +``` + +### Document Deletion + +The `delete_document` method is called when a user deletes a document from the knowledge base: + +```python +async def delete_document(self, kb_id: str, document_id: str) -> bool: + """Delete a document and its associated data + + Args: + kb_id: Knowledge base ID + document_id: Document ID + + Returns: + Whether the deletion was successful + """ +``` + +## Host RAG APIs + +KnowledgeEngine components can call LangBot host-provided RAG APIs via `self.plugin`, including embedding model invocation, vector database operations, and file retrieval. + +### Invoke Embedding Model + +```python +async def invoke_embedding( + self, + embedding_model_uuid: str, + texts: list[str], +) -> list[list[float]]: + """Generate embeddings using host's embedding model + + Args: + embedding_model_uuid: Embedding model UUID + texts: List of texts to embed + + Returns: + List of embedding vectors, one per input text + """ + +# Usage example +vectors = await self.plugin.invoke_embedding("model_uuid", ["Hello", "World"]) +``` + +### Vector Upsert + +```python +async def vector_upsert( + self, + collection_id: str, + vectors: list[list[float]], + ids: list[str], + metadata: list[dict] | None = None, +) -> None: + """Upsert vectors to host's vector database + + Args: + collection_id: Target collection ID + vectors: List of vectors + ids: List of unique IDs for vectors + metadata: Optional list of metadata dicts + """ + +# Usage example +await self.plugin.vector_upsert( + collection_id="kb_uuid", + vectors=[[0.1, 0.2, ...], [0.3, 0.4, ...]], + ids=["chunk_0", "chunk_1"], + metadata=[{"document_id": "doc1"}, {"document_id": "doc1"}], +) +``` + +### Vector Search + +```python +async def vector_search( + self, + collection_id: str, + query_vector: list[float], + top_k: int = 5, + filters: dict | None = None, +) -> list[dict]: + """Search similar vectors in host's vector database + + Args: + collection_id: Target collection ID + query_vector: Query vector for similarity search + top_k: Number of results to return + filters: Optional metadata filters + + Returns: + List of search results (dict with id, score, metadata, etc.) + """ + +# Usage example +results = await self.plugin.vector_search( + collection_id="kb_uuid", + query_vector=[0.1, 0.2, ...], + top_k=5, +) +# Return format: [{"id": "chunk_0", "score": 0.123, "metadata": {"document_id": "doc1", ...}}, ...] +``` + + +Each result returned by `vector_search` is a dict containing `id` (vector ID), `score` (distance score), and `metadata` (metadata provided during upsert). If you need text content in retrieval results, store the text in metadata during ingestion. + + +### Vector Delete + +```python +async def vector_delete( + self, + collection_id: str, + file_ids: list[str] | None = None, + filters: dict | None = None, +) -> int: + """Delete vectors from host's vector database + + Args: + collection_id: Target collection ID + file_ids: File IDs whose vectors should be deleted + filters: Optional metadata filters for deletion + + Returns: + Number of deleted items + """ + +# Usage example +deleted = await self.plugin.vector_delete( + collection_id="kb_uuid", + file_ids=["doc_001"], +) +``` + + +The `filters` parameter supports Chroma-style `where` syntax for metadata filtering. Multiple top-level keys are AND-ed. Supported operators: `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin`. Example: `{"file_id": {"$eq": "abc"}}`. + +**Note:** Chroma, Qdrant, and SeekDB store full metadata and can filter on any field. Milvus and pgvector only store `text`, `file_id`, and `chunk_uuid` — filters on other fields will be silently ignored. + + +```python +async def get_knowledge_file_stream(self, storage_path: str) -> bytes: + """Get file content from host storage + + Args: + storage_path: File storage path (from FileObject.storage_path) + + Returns: + File content as bytes + """ + +# Usage example +file_bytes = await self.plugin.get_knowledge_file_stream(context.file_object.storage_path) +``` + +## Testing the Knowledge Engine + +After creation, execute the command `lbp run` in the plugin directory to start debugging. Then in LangBot: + +1. Go to the "Knowledge Base" page +2. Click "Create Knowledge Base" +3. Select the Knowledge Engine provided by your plugin and fill in the configuration based on the engine's `creation_schema` +4. After creation, upload documents to test ingestion capabilities (if the engine declares `DOC_INGESTION` capability) +5. Bind the knowledge base to a pipeline and test retrieval capabilities diff --git a/en/plugin/dev/components/parser.mdx b/en/plugin/dev/components/parser.mdx new file mode 100644 index 000000000..de19c9d50 --- /dev/null +++ b/en/plugin/dev/components/parser.mdx @@ -0,0 +1,200 @@ +--- +title: "Component: Parser" +--- + +The Parser component allows plugins to provide document parsing capabilities for LangBot. When a user uploads a document to a knowledge base, LangBot invokes the parser before the Knowledge Engine's `ingest` step, extracting structured text from binary files such as PDF, Word, Markdown, etc. + +Relationship between Parser and KnowledgeEngine: +- **Parser** is responsible for converting files to text (file → text) +- **KnowledgeEngine** is responsible for indexing and retrieving text (text → chunks → vectors) + +If the Knowledge Engine already has native document parsing capabilities (declared `DOC_PARSING` capability), users can choose to use the Knowledge Engine's built-in parsing or an external Parser plugin. + +## Adding a Parser Component + +A single plugin can add any number of parsers. Execute the command `lbp comp Parser` in the plugin directory and follow the prompts to enter the parser configuration. + +```bash +➜ MyParserPlugin > lbp comp Parser +Generating component Parser... +Parser name: pdf_parser +Parser description: A PDF document parser +Component Parser generated successfully. +``` + +This will generate `pdf_parser.yaml` and `pdf_parser.py` files in the `components/parser/` directory. The `.yaml` file defines the parser's basic information and supported MIME types, and the `.py` file is the handler for this parser: + +```bash +➜ MyParserPlugin > tree +... +├── components +│ ├── __init__.py +│ └── parser +│ ├── __init__.py +│ ├── pdf_parser.py +│ └── pdf_parser.yaml +... +``` + +## Manifest File: Parser + +```yaml +apiVersion: v1 # Do not modify +kind: Parser # Do not modify +metadata: + name: pdf_parser # Parser name, used to identify this parser + label: + en_US: PDF Parser # Parser display name, shown in LangBot's UI, supports multiple languages + zh_Hans: PDF 解析器 + description: + en_US: 'A PDF document parser' + zh_Hans: 'PDF 文档解析器' +spec: + supported_mime_types: # Declare supported file MIME types + - application/pdf +execution: + python: + path: pdf_parser.py # Parser handler, do not modify + attr: PdfParser # Class name of the parser handler, consistent with the class name in pdf_parser.py +``` + +### supported_mime_types + +`supported_mime_types` declares the file types this parser supports. Common MIME types: + +| MIME Type | Description | +|----------|-------------| +| `application/pdf` | PDF documents | +| `application/vnd.openxmlformats-officedocument.wordprocessingml.document` | Word documents (.docx) | +| `text/markdown` | Markdown files | +| `text/plain` | Plain text files | +| `text/html` | HTML files | + +## Plugin Handler + +The following code will be generated by default (`components/parser/.py`). You need to implement the `parse` method. + +```python +from langbot_plugin.api.definition.components.parser.parser import Parser +from langbot_plugin.api.entities.builtin.rag.models import ( + ParseContext, + ParseResult, + TextSection, +) + + +class PdfParser(Parser): + """Parser component for extracting text from files.""" + + async def parse(self, context: ParseContext) -> ParseResult: + """Parse a file and extract structured text. + + Args: + context: Contains file_content (bytes), mime_type, filename, and metadata. + + Returns: + ParseResult with extracted text and optional structured sections. + """ + # TODO: Implement parsing logic + text = context.file_content.decode("utf-8", errors="replace") + + return ParseResult( + text=text, + sections=[ + TextSection( + content=text, + heading=context.filename, + level=0, + ), + ], + metadata={ + "filename": context.filename, + "mime_type": context.mime_type, + }, + ) +``` + +### Parse Method + +The `parse` method is called when a document is uploaded to a knowledge base (before Knowledge Engine ingestion): + +```python +async def parse(self, context: ParseContext) -> ParseResult: +``` + +**ParseContext** contains the following information: + +```python +class ParseContext(pydantic.BaseModel): + file_content: bytes # Raw file bytes (read by LangBot from storage) + mime_type: str # Detected MIME type of the file + filename: str # Original filename + metadata: dict[str, Any] # Extra metadata from FileObject +``` + +**ParseResult** should return the parsing result: + +```python +class ParseResult(pydantic.BaseModel): + text: str # Full extracted plain text + sections: list[TextSection] = [] # Structured sections (optional) + metadata: dict[str, Any] = {} # Parsing metadata (e.g., page_count, language) +``` + +**TextSection** represents a section of text extracted from the document: + +```python +class TextSection(pydantic.BaseModel): + content: str # Section text content + heading: str | None = None # Section heading + level: int = 0 # Nesting level + page: int | None = None # Source page number (for PDF, etc.) + metadata: dict[str, Any] = {} # Additional section metadata +``` + +## Integration with KnowledgeEngine + +When a user uploads a document, LangBot determines the parsing flow as follows: + +- If the user selects an external Parser plugin, LangBot first calls the Parser's `parse` method, then passes the result to the Knowledge Engine's `ingest` method via `IngestionContext.parsed_content`. +- If the Knowledge Engine declares `DOC_PARSING` capability and the user does not select an external parser, the Knowledge Engine handles document parsing on its own. + +KnowledgeEngine can check `IngestionContext.parsed_content` to determine whether pre-parsed content is available: + +```python +async def ingest(self, context: IngestionContext) -> IngestionResult: + if context.parsed_content: + # Use pre-parsed content from external Parser + text = context.parsed_content.text + sections = context.parsed_content.sections + else: + # Parse the document internally + file_bytes = await self.plugin.get_knowledge_file_stream(context.file_object.storage_path) + text = file_bytes.decode('utf-8') + ... +``` + +## Cross-Plugin Parser Invocation + +KnowledgeEngine plugins can invoke parsers provided by other plugins via `self.plugin.invoke_parser`: + +```python +result = await self.plugin.invoke_parser( + plugin_author="author_name", + plugin_name="plugin_name", + storage_path=context.file_object.storage_path, + mime_type=context.file_object.metadata.mime_type, + filename=context.file_object.metadata.filename, + metadata={}, +) +# result is a dict containing text, sections, metadata +``` + +## Testing the Parser + +After creation, execute the command `lbp run` in the plugin directory to start debugging. Then in LangBot: + +1. Go to the "Knowledge Base" page +2. Select a knowledge base and enter document management +3. When uploading a file, select your plugin's parser in the parser selector +4. After uploading, verify that the document is correctly ingested diff --git a/en/plugin/plugin-intro.mdx b/en/plugin/plugin-intro.mdx index 68b3648d2..add342e22 100644 --- a/en/plugin/plugin-intro.mdx +++ b/en/plugin/plugin-intro.mdx @@ -15,7 +15,7 @@ Plugins can provide the following components based on their specific functionali - Event Handlers: Listen to events during pipeline execution and modify context or pipelines. - Commands: Triggered by user command messages starting with `!` (or other configured prefixes). - Tools: Called by LLMs during execution of LangBot's built-in Local Agent. -- Knowledge Retrievers: Used to retrieve relevant knowledge from external knowledge bases. +- Knowledge Engines: Manage document indexing and retrieval for knowledge bases. More component types will be supported for plugin implementation in the future. diff --git a/en/usage/knowledge/readme.mdx b/en/usage/knowledge/readme.mdx index 5326e5746..3722e94fb 100644 --- a/en/usage/knowledge/readme.mdx +++ b/en/usage/knowledge/readme.mdx @@ -2,25 +2,26 @@ title: "Using the Knowledge Base" --- -LangBot natively supports RAG (Retrieval-Augmented Generation). You can create a built-in knowledge base and bind it to a pipeline, allowing the pipeline to answer questions based on the contents of the knowledge base. LangBot also supports connecting to external knowledge retrieval services (e.g., Dify, RAGFlow). +LangBot natively supports RAG (Retrieval-Augmented Generation). You can create a knowledge base and bind it to a pipeline, allowing the pipeline to answer questions based on the contents of the knowledge base. -## Build a Built-in Knowledge Base +Knowledge bases are powered by Knowledge Engine plugins, with different Knowledge Engines providing different indexing and retrieval strategies. You can find available Knowledge Engine plugins in the [Plugin Marketplace](https://space.langbot.app/market). + +## Creating a Knowledge Base + +On the knowledge base page, click the `Create Knowledge Base` button: + +1. Fill in the knowledge base name +2. Select a Knowledge Engine (provided by installed plugins) +3. Fill in the relevant parameters based on the selected engine's configuration form (e.g., embedding model, chunk size, etc.) +4. Click the `Create` button -Creating and using a built-in knowledge base requires configuring an embedding model first. Please read [Configure Models](/en/usage/models/readme). +Different Knowledge Engines have different configuration parameters, depending on the engine's `creation_schema` definition. Some engines may require configuring an embedding model first. Please read [Configure Models](/en/usage/models/readme). -On the knowledge base page, click the `Create Knowledge Base` button, fill in the name, select an already configured embedding model, then click `Create`. +If the selected Knowledge Engine supports document upload (declares `DOC_INGESTION` capability), after creation go to the "Documents" tab of your knowledge base and upload documents. LangBot will automatically parse and index these in the background. -After creation, go to the "Documents" tab of your knowledge base and upload documents. LangBot will automatically parse and index these in the background. - -create_kb - -## Connect External Knowledge Base - -On the knowledge base page, switch to the "External Knowledge Base" tab and add an external retriever. In the dialog, select the knowledge retriever plugin to use (please install the related plugin in Plugin Management first). Supported retrievers can be found in the [Plugin Marketplace](https://space.langbot.app/market?category=KnowledgeRetriever). - -## Use the Knowledge Base +## Using the Knowledge Base Go to your pipeline configuration, under the "AI" tab, choose `Local Agent` as the runner, then select the knowledge base you just created. @@ -35,3 +36,7 @@ LangBot's built-in knowledge base can be used only when the runner is set to `Lo Now you can use the knowledge base in `Chat Debug`, or through the bot linked to this pipeline: use_kb_in_chat + +## Developing Custom Knowledge Engines + +If existing Knowledge engine plugins don't meet your needs, you can develop your own Knowledge engine plugin. See [Component: Knowledge Engine](/en/plugin/dev/components/knowledge-engine) for development details. diff --git a/ja/plugin/dev/apis/common.mdx b/ja/plugin/dev/apis/common.mdx index e93d48404..c61ab1bb0 100644 --- a/ja/plugin/dev/apis/common.mdx +++ b/ja/plugin/dev/apis/common.mdx @@ -307,4 +307,192 @@ async def get_config_file(self, file_key: str) -> bytes: file_bytes = await self.plugin.get_config_file("key") ``` -これは[``fileまたはarray[file``](/en/plugin/dev/basic-info.html#type-file)型の設定フィールドと組み合わせて使用します。 \ No newline at end of file +これは[``fileまたはarray[file``](/en/plugin/dev/basic-info.html#type-file)型の設定フィールドと組み合わせて使用します。 + +## RAG API + +これらのAPIは`KnowledgeEngine`コンポーネントがLangBotホストの埋め込みモデル、ベクトルデータベース、ファイルストレージにアクセスするために使用できます。アクセス方法: + +- `KnowledgeEngine`コンポーネントクラス内: `self.plugin`オブジェクトの内部メソッド。 + +### 埋め込みモデルの呼び出し + +ホストに設定された埋め込みモデルを使用してテキストのベクトルを生成します。 + +```python +async def invoke_embedding( + self, + embedding_model_uuid: str, + texts: list[str], +) -> list[list[float]]: + """埋め込みモデルを使用してベクトルを生成 + + Args: + embedding_model_uuid: 埋め込みモデルUUID + texts: 埋め込むテキストのリスト + + Returns: + ベクトルのリスト、入力テキストごとに1つ + """ + +# 使用例 +vectors = await self.plugin.invoke_embedding("model_uuid", ["Hello", "World"]) +``` + +### ベクトルの書き込み + +ホストのベクトルデータベースにベクトルを書き込みます。 + +```python +async def vector_upsert( + self, + collection_id: str, + vectors: list[list[float]], + ids: list[str], + metadata: list[dict[str, Any]] | None = None, + documents: list[str] | None = None, +) -> None: + """ベクトルの書き込み + + Args: + collection_id: ターゲットコレクションID + vectors: ベクトルのリスト + ids: ベクトルの一意識別子リスト + metadata: オプションのメタデータリスト + documents: オプションの生テキストドキュメントリスト。全文検索や + 混合検索をサポートするバックエンドで必要です。 + """ + +# 使用例 +await self.plugin.vector_upsert( + collection_id="kb_uuid", + vectors=[[0.1, 0.2, ...], [0.3, 0.4, ...]], + ids=["chunk_0", "chunk_1"], + metadata=[{"document_id": "doc1"}, {"document_id": "doc1"}], + documents=["チャンクテキスト 0", "チャンクテキスト 1"], +) +``` + +### ベクトル検索 + +ホストのベクトルデータベースで類似ベクトルを検索します。 + +```python +async def vector_search( + self, + collection_id: str, + query_vector: list[float], + top_k: int = 5, + filters: dict[str, Any] | None = None, + search_type: str = "vector", + query_text: str = "", +) -> list[dict[str, Any]]: + """ベクトル検索 + + Args: + collection_id: ターゲットコレクションID + query_vector: 類似性検索のクエリベクトル + top_k: 返す結果の数 + filters: オプションのメタデータフィルター + search_type: 検索方式、'vector'、'full_text'、'hybrid' のいずれか + query_text: 生のクエリテキスト、全文検索と混合検索で使用 + + Returns: + 検索結果のリスト(id, score, metadataなどを含むdict) + """ + +# 使用例 +results = await self.plugin.vector_search( + collection_id="kb_uuid", + query_vector=[0.1, 0.2, ...], + top_k=5, + search_type="hybrid", + query_text="検索クエリ", +) +# 返却フォーマット: [{"id": "chunk_0", "score": 0.123, "metadata": {"document_id": "doc1", ...}}, ...] +``` + + +`vector_search`が返す各結果はdictで、`id`(ベクトルID)、`score`(距離スコア)、`metadata`(upsert時に提供したメタデータ)の3つのフィールドを含みます。検索結果にテキスト内容が必要な場合は、取り込み時にmetadataにテキストを保存してください。 + + +### ベクトルの削除 + +ホストのベクトルデータベースからベクトルを削除します。 + +```python +async def vector_delete( + self, + collection_id: str, + file_ids: list[str] | None = None, + filters: dict[str, Any] | None = None, +) -> int: + """ベクトルの削除 + + Args: + collection_id: ターゲットコレクションID + file_ids: 削除するファイルIDのリスト + filters: オプションのメタデータフィルター + + Returns: + 削除されたアイテム数 + """ + +# 使用例 +deleted = await self.plugin.vector_delete( + collection_id="kb_uuid", + file_ids=["doc_001"], +) +``` + + +`filters`パラメータはChromaスタイルの`where`構文によるメタデータフィルタリングをサポートしています。複数のトップレベルキーはAND条件として結合されます。サポートされる演算子:`$eq`、`$ne`、`$gt`、`$gte`、`$lt`、`$lte`、`$in`、`$nin`。 + +```python +# 暗黙的な $eq +results = await self.plugin.vector_search( + collection_id="kb_uuid", + query_vector=[0.1, 0.2, ...], + filters={"file_id": "abc"}, +) + +# 比較演算子 +results = await self.plugin.vector_search( + collection_id="kb_uuid", + query_vector=[0.1, 0.2, ...], + filters={"created_at": {"$gte": 1700000000}}, +) + +# リスト演算子 +results = await self.plugin.vector_search( + collection_id="kb_uuid", + query_vector=[0.1, 0.2, ...], + filters={"file_type": {"$in": ["pdf", "docx"]}}, +) + +# フィルターによる削除 +deleted = await self.plugin.vector_delete( + collection_id="kb_uuid", + filters={"file_type": {"$eq": "pdf"}}, +) +``` + +**注意:** Chroma、Qdrant、SeekDBは完全なメタデータを保存し、任意のフィールドでフィルタリングできます。MilvusとpgvectorはDB側に`text`、`file_id`、`chunk_uuid`のみ保存しており、それ以外のフィールドでのフィルタリングは無視されます。 + + +ホストストレージからアップロードされたファイルの内容を取得します。 + +```python +async def get_knowledge_file_stream(self, storage_path: str) -> bytes: + """ファイル内容を取得 + + Args: + storage_path: ファイルのストレージパス(FileObject.storage_pathから取得) + + Returns: + ファイル内容のバイトデータ + """ + +# 使用例 +file_bytes = await self.plugin.get_knowledge_file_stream(context.file_object.storage_path) +``` \ No newline at end of file diff --git a/ja/plugin/dev/basic-info.mdx b/ja/plugin/dev/basic-info.mdx index 98d096782..d69bee840 100644 --- a/ja/plugin/dev/basic-info.mdx +++ b/ja/plugin/dev/basic-info.mdx @@ -56,6 +56,10 @@ spec: zh_Hans: 如果不填的話、图片可能會下載失败 default: '' # 設定項目のデフォルト値、オプション。 required: false # 設定項目が必須かどうか、オプション。 + show_if: # 条件付きレンダリング(条件を満たす場合にのみ表示)、オプション。 + field: mode + operator: eq + value: mode1 - name: mode type: select ... @@ -63,6 +67,21 @@ spec: 設定項目でサポートされるタイプとフィールドは次のとおりです: +### 条件付きレンダリング (`show_if`) + +すべてのタイプの設定項目は、`show_if` フィールドを使用した条件付きレンダリングをサポートしています。同じ(または関連する)フォーム内の他のフィールドの値に基づいて、現在のフィールドをレンダリングして表示するかどうかを決定できます。 + +```yaml +- name: advanced_setting + type: string + show_if: + field: mode # 依存するフィールドの名前 + operator: eq # 判定演算子。'eq' (等しい)、'neq' (等しくない)、'in' (リストに含まれる) をサポートします + value: advanced # 判断の基準値 +``` + +> **注意:** クロスフォームのカスケード関係(例:RAG エンジンの `creation_schema` 設定が `retrieval_schema` の値を読み取る、またはその逆)は、LangBot の最新フロントエンドでサポートされています。 + ### type: string 文字列型。 diff --git a/ja/plugin/dev/components/add.mdx b/ja/plugin/dev/components/add.mdx index 74758c7de..3f19b4dcc 100644 --- a/ja/plugin/dev/components/add.mdx +++ b/ja/plugin/dev/components/add.mdx @@ -7,7 +7,8 @@ title: "コンポーネントの追加" - イベントリスナー(EventListener): パイプライン実行中のイベントをリッスンし、コンテキストやパイプラインを変更します。 - コマンド(Command): `!`(または他の設定されたプレフィックス)で始まるユーザーコマンドメッセージによってトリガーされます。 - ツール(Tool): LangBotの組み込みLocal Agentの実行中にLLMによって呼び出されます。 -- ナレッジリトリーバー(KnowledgeRetriever): LangBotに外部ナレッジベース検索機能を提供し、さまざまなRAGシステムとの統合をサポートします。 +- RAGエンジン(KnowledgeEngine): LangBotにナレッジベースのインデックス作成と検索機能を提供し、ドキュメントの取り込み、検索、削除を管理します。 +- パーサー(Parser): ドキュメント取り込みの前にPDF、Wordなどのバイナリファイルを構造化テキストに解析し、RAGエンジンで使用できるようにします。 ## コンポーネントの追加 @@ -71,4 +72,5 @@ components: - [EventListener](./event-listener) - [Command](./command) - [Tool](./tool) -- [KnowledgeRetriever](./knowledge-retriever) +- [KnowledgeEngine](./knowledge-engine) +- [Parser](./parser) diff --git a/ja/plugin/dev/components/knowledge-engine.mdx b/ja/plugin/dev/components/knowledge-engine.mdx new file mode 100644 index 000000000..7f1a9f1a4 --- /dev/null +++ b/ja/plugin/dev/components/knowledge-engine.mdx @@ -0,0 +1,479 @@ +--- +title: "コンポーネント: 知識エンジン" +--- + +RAGエンジン(KnowledgeEngine)コンポーネントにより、プラグインはLangBotにナレッジベースの完全なインデックス作成と検索機能を提供できます。ユーザーがLangBotでナレッジベースを作成する際、プラグインが提供するRAGエンジンを選択して、ドキュメントの取り込み、検索、削除を管理できます。また、プラグインを通じてDify、RAGFlow、FastGPTなどの業界先進のナレッジベースサービスとブリッジすることも可能です。 + +## RAGエンジンコンポーネントの追加 + +1つのプラグインには任意の数のRAGエンジンを追加できます。プラグインディレクトリでコマンド`lbp comp KnowledgeEngine`を実行し、プロンプトに従ってRAGエンジンの設定を入力します。 + +```bash +➜ MyRAGPlugin > lbp comp KnowledgeEngine +Generating component KnowledgeEngine... +Knowledge Engine name: simple_rag +Knowledge Engine description: A simple Knowledge Engine with built-in chunking and retrieval +Component KnowledgeEngine generated successfully. +組件 KnowledgeEngine 生成成功。 +``` + +これにより、`components/knowledge_engine/`ディレクトリに`simple_rag.yaml`と`simple_rag.py`ファイルが生成されます。`.yaml`ファイルはRAGエンジンの基本情報と設定スキーマを定義し、`.py`ファイルはこのエンジンのハンドラです: + +```bash +➜ MyRAGPlugin > tree +... +├── components +│ ├── __init__.py +│ └── knowledge_engine +│ ├── __init__.py +│ ├── simple_rag.py +│ └── simple_rag.yaml +... +``` + +## マニフェストファイル: RAGエンジン + +```yaml +apiVersion: v1 # 変更しないでください +kind: KnowledgeEngine # 変更しないでください +metadata: + name: simple_rag # RAGエンジン名、このエンジンを識別するために使用 + label: + en_US: Simple Knowledge Engine # エンジン表示名、LangBotのUIに表示、多言語対応 + zh_Hans: 简易 RAG 引擎 + ja_JP: シンプル RAG エンジン + description: + en_US: 'A simple Knowledge Engine with built-in chunking and retrieval' + zh_Hans: '内置分块和检索能力的简易 RAG 引擎' + ja_JP: 'チャンキングと検索を内蔵したシンプルなRAGエンジン' +spec: + creation_schema: # ナレッジベース作成時にユーザーが入力する設定パラメータ + - name: chunk_size + label: + en_US: Chunk Size + zh_Hans: 分块大小 + type: integer + required: false + default: 500 + - name: chunk_overlap + label: + en_US: Chunk Overlap + zh_Hans: 分块重叠 + type: integer + required: false + default: 50 + retrieval_schema: # 検索時に設定可能なパラメータ + - name: score_threshold + label: + en_US: Score Threshold + zh_Hans: 分数阈值 + type: float + required: false + default: 0.5 +execution: + python: + path: simple_rag.py # エンジンハンドラ、変更しないでください + attr: SimpleRag # エンジンハンドラのクラス名、simple_rag.pyのクラス名と一致 +``` + +設定項目フォーマットのリファレンスについては、[プラグインマニフェスト設定フォーマット](/ja/plugin/dev/basic-info)を参照してください。 + +### creation_schema と retrieval_schema + +旧バージョンの`KnowledgeRetriever`コンポーネントが単一の`spec.config`を使用していたのとは異なり、KnowledgeEngineは2つの独立したスキーマを使用します: + +- **creation_schema**: ナレッジベース作成時にユーザーが入力するパラメータ。取り込みと検索時に`creation_settings`を通じてプラグインに渡されます。 +- **retrieval_schema**: ナレッジベースのクエリ時にユーザーが調整できるパラメータ。`retrieval_settings`を通じてプラグインに渡されます。 + +## 能力宣言 + +KnowledgeEngineはサポートする能力を宣言でき、LangBotはこれらの能力宣言に基づいてUIの表示と利用可能な操作を決定します: + +```python +from langbot_plugin.api.definition.components.knowledge_engine.engine import KnowledgeEngine, KnowledgeEngineCapability + +class SimpleRag(KnowledgeEngine): + + @classmethod + def get_capabilities(cls) -> list[str]: + return [ + KnowledgeEngineCapability.DOC_INGESTION, # ドキュメントのアップロードと処理をサポート + ] +``` + +利用可能な能力定数: + +| 能力 | 説明 | +|------|------| +| `DOC_INGESTION` | ドキュメントのアップロードと処理をサポート。宣言すると、LangBotはナレッジベースの詳細に「ドキュメント」タブを表示します。プラグインが他のナレッジベースサービスとのブリッジ用である場合や、ユーザーによるドキュメントのアップロードが不要な場合は、この能力宣言を省略できます。 | +| `DOC_PARSING` | ネイティブドキュメント解析(ファイルからテキストへの抽出)をサポート。宣言すると、ユーザーはドキュメントアップロード時にRAGエンジンの内蔵解析機能を選択でき、外部[Parserプラグイン](./parser)が不要になります。未宣言の場合、ドキュメントをアップロードするには外部Parserプラグインのインストールが必要です。 | + + +その他の検索動作(リランキング、ハイブリッド検索など)はプラグインの`retrieval_schema`で制御され、能力フラグは不要です。 + + +## プラグインハンドラ + +以下のコードがデフォルトで生成されます(`components/knowledge_engine/<エンジン名>.py`)。`ingest`、`retrieve`、`delete_document`の3つのコアメソッドを実装する必要があります。完全なコードは[langbot-plugin-demo](https://github.com/langbot-app/langbot-plugin-demo)の`SimpleKnowledgeEngine`で見つけることができます。 + +```python +from langbot_plugin.api.definition.components.knowledge_engine.engine import KnowledgeEngine, KnowledgeEngineCapability +from langbot_plugin.api.entities.builtin.rag.models import ( + IngestionContext, + IngestionResult, +) +from langbot_plugin.api.entities.builtin.rag.context import ( + RetrievalContext, + RetrievalResponse, + RetrievalResultEntry, +) +from langbot_plugin.api.entities.builtin.rag.enums import DocumentStatus +from langbot_plugin.api.entities.builtin.provider.message import ContentElement + + +class SimpleRag(KnowledgeEngine): + + @classmethod + def get_capabilities(cls) -> list[str]: + return [KnowledgeEngineCapability.DOC_INGESTION] + + async def on_knowledge_base_create(self, kb_id: str, config: dict) -> None: + """ナレッジベース作成時のコールバック、リソースの初期化に使用できます""" + pass + + async def on_knowledge_base_delete(self, kb_id: str) -> None: + """ナレッジベース削除時のコールバック、リソースのクリーンアップに使用できます""" + pass + + async def ingest(self, context: IngestionContext) -> IngestionResult: + """ドキュメントをナレッジベースに取り込む""" + + # 1. ファイル内容を取得 + file_bytes = await self.plugin.get_knowledge_file_stream(context.file_object.storage_path) + + # 2. ドキュメントを解析してチャンクに分割 + text = file_bytes.decode('utf-8') + chunk_size = context.creation_settings.get('chunk_size', 500) + chunk_overlap = context.creation_settings.get('chunk_overlap', 50) + chunks = self._split_text(text, chunk_size, chunk_overlap) + + # 3. ホストの埋め込みモデルを使用してベクトルを生成 + embedding_model_uuid = context.creation_settings.get('embedding_model_uuid', '') + vectors = await self.plugin.invoke_embedding(embedding_model_uuid, chunks) + + # 4. ホストのベクトルデータベースに書き込み + collection_id = context.get_collection_id() + ids = [f"{context.file_object.metadata.document_id}_{i}" for i in range(len(chunks))] + metadata = [{"document_id": context.file_object.metadata.document_id, "chunk_index": i, "text": chunks[i]} for i in range(len(chunks))] + await self.plugin.vector_upsert(collection_id, vectors, ids, metadata) + + return IngestionResult( + document_id=context.file_object.metadata.document_id, + status=DocumentStatus.COMPLETED, + chunks_created=len(chunks), + ) + + async def retrieve(self, context: RetrievalContext) -> RetrievalResponse: + """ナレッジベースから関連コンテンツを検索""" + + # 1. クエリベクトルを生成 + embedding_model_uuid = context.creation_settings.get('embedding_model_uuid', '') + query_vectors = await self.plugin.invoke_embedding(embedding_model_uuid, [context.query]) + query_vector = query_vectors[0] + + # 2. ベクトル検索 + collection_id = context.get_collection_id() + results = await self.plugin.vector_search(collection_id, query_vector, top_k=context.retrieval_settings.get('top_k', 5)) + + # 3. 検索結果に変換 + entries = [] + for r in results: + entry = RetrievalResultEntry( + id=r.get('id', ''), + content=[ContentElement.from_text(r.get('metadata', {}).get('text', ''))], + metadata=r.get('metadata', {}), + distance=r.get('score', 0.0), + ) + entries.append(entry) + + return RetrievalResponse( + results=entries, + total_found=len(entries), + metadata={}, + ) + + async def delete_document(self, kb_id: str, document_id: str) -> bool: + """ナレッジベースからドキュメントと関連データを削除""" + collection_id = kb_id + deleted = await self.plugin.vector_delete(collection_id, file_ids=[document_id]) + return deleted > 0 +``` + +### ライフサイクルフック + +KnowledgeEngineは、ナレッジベースの作成・削除時に呼び出される2つのライフサイクルフックを提供します: + +```python +async def on_knowledge_base_create(self, kb_id: str, config: dict) -> None: + """このエンジンを使用するナレッジベースが作成された時のコールバック + + Args: + kb_id: ナレッジベースUUID + config: ユーザーが作成時に入力した設定(creation_schemaのフィールド) + """ + +async def on_knowledge_base_delete(self, kb_id: str) -> None: + """このエンジンを使用するナレッジベースが削除された時のコールバック + + Args: + kb_id: ナレッジベースUUID + """ +``` + +### ドキュメントの取り込み + +`ingest`メソッドは、ユーザーがナレッジベースにドキュメントをアップロードした時に呼び出されます: + +```python +async def ingest(self, context: IngestionContext) -> IngestionResult: +``` + +**IngestionContext**には以下の情報が含まれます: + +```python +class IngestionContext(pydantic.BaseModel): + file_object: FileObject # 取り込むファイルオブジェクト + knowledge_base_id: str # ターゲットのナレッジベースID + collection_id: str | None # ベクトルコレクションID(デフォルトはknowledge_base_idにフォールバック) + creation_settings: dict # ナレッジベース作成時の設定パラメータ + parsed_content: ParseResult | None # 外部Parserプラグインの事前解析コンテンツ(存在する場合) +``` + +**FileObject**にはファイルのメタ情報が含まれます: + +```python +class FileObject(pydantic.BaseModel): + metadata: FileMetadata # filename, file_size, mime_type, document_id, knowledge_base_id等を含む + storage_path: str # ストレージシステム内のファイルパス +``` + +**IngestionResult**は取り込み結果を返す必要があります: + +```python +class IngestionResult(pydantic.BaseModel): + document_id: str # ドキュメントID + status: DocumentStatus # 処理ステータス: COMPLETED / FAILED + chunks_created: int = 0 # 作成されたチャンク数 + error_message: str | None = None # 失敗時のエラーメッセージ + metadata: dict = {} # 追加メタデータ +``` + +### ナレッジ検索 + +`retrieve`メソッドは、ナレッジベースがクエリされた時に呼び出されます: + +```python +async def retrieve(self, context: RetrievalContext) -> RetrievalResponse: +``` + +**RetrievalContext**には以下の情報が含まれます: + +```python +class RetrievalContext(pydantic.BaseModel): + query: str # クエリテキスト + knowledge_base_id: str | None # ナレッジベースID + collection_id: str | None # ベクトルコレクションID + retrieval_settings: dict # 検索時の設定パラメータ(retrieval_schemaのフィールド) + creation_settings: dict # ナレッジベース作成時の設定パラメータ + filters: dict # メタデータフィルター条件(Chromaスタイルwhere構文) +``` + + +LangBotホストは`retrieval_settings`をプラグインに渡す前にデフォルトの`top_k`値(デフォルト5)を自動的に注入します。プラグインは`context.retrieval_settings.get('top_k', 5)`でアクセスできます。 + +`filters`フィールドには`retrieval_settings`から抽出されたChromaスタイルの`where`フィルター条件が含まれます。呼び出し元が検索設定で`filters`を指定した場合(例:時間範囲、ファイルタイプ、カスタムメタデータフィールドによるドキュメントフィルタリング)、ホストがこのフィールドを設定し、プラグインは検索時にフィルタリングを適用できます。フィルターが指定されていない場合、このフィールドは空のdictです。 + + +**RetrievalResponse**は検索結果を返す必要があります: + +```python +class RetrievalResponse(pydantic.BaseModel): + results: list[RetrievalResultEntry] # 検索結果リスト + total_found: int # 一致した総数 + metadata: dict # 追加メタデータ +``` + +**RetrievalResultEntry**は単一の検索結果を表します: + +```python +class RetrievalResultEntry(pydantic.BaseModel): + id: str # 結果ID + content: list[ContentElement] # コンテンツ、ContentElement.from_text()を使用して作成 + metadata: dict # メタデータ + distance: float # 距離スコア(小さいほど関連性が高い) + score: float | None = None # 類似度スコア(大きいほど関連性が高い) +``` + +### ドキュメントの削除 + +`delete_document`メソッドは、ユーザーがナレッジベースからドキュメントを削除した時に呼び出されます: + +```python +async def delete_document(self, kb_id: str, document_id: str) -> bool: + """ドキュメントと関連データを削除 + + Args: + kb_id: ナレッジベースID + document_id: ドキュメントID + + Returns: + 削除が成功したかどうか + """ +``` + +## ホストRAG API + +KnowledgeEngineコンポーネントは`self.plugin`を通じて、LangBotホストが提供する埋め込みモデル呼び出し、ベクトルデータベース操作、ファイル読み取りなどのRAG関連APIを呼び出すことができます。 + +### 埋め込みモデルの呼び出し + +```python +async def invoke_embedding( + self, + embedding_model_uuid: str, + texts: list[str], +) -> list[list[float]]: + """ホストの埋め込みモデルを使用してベクトルを生成 + + Args: + embedding_model_uuid: 埋め込みモデルUUID + texts: 埋め込むテキストのリスト + + Returns: + ベクトルのリスト、入力テキストごとに1つ + """ + +# 使用例 +vectors = await self.plugin.invoke_embedding("model_uuid", ["Hello", "World"]) +``` + +### ベクトルの書き込み + +```python +async def vector_upsert( + self, + collection_id: str, + vectors: list[list[float]], + ids: list[str], + metadata: list[dict] | None = None, +) -> None: + """ホストのベクトルデータベースにベクトルを書き込む + + Args: + collection_id: ターゲットコレクションID + vectors: ベクトルのリスト + ids: ベクトルの一意識別子リスト + metadata: オプションのメタデータリスト + """ + +# 使用例 +await self.plugin.vector_upsert( + collection_id="kb_uuid", + vectors=[[0.1, 0.2, ...], [0.3, 0.4, ...]], + ids=["chunk_0", "chunk_1"], + metadata=[{"document_id": "doc1"}, {"document_id": "doc1"}], +) +``` + +### ベクトル検索 + +```python +async def vector_search( + self, + collection_id: str, + query_vector: list[float], + top_k: int = 5, + filters: dict | None = None, +) -> list[dict]: + """ホストのベクトルデータベースで類似ベクトルを検索 + + Args: + collection_id: ターゲットコレクションID + query_vector: 類似性検索のクエリベクトル + top_k: 返す結果の数 + filters: オプションのメタデータフィルター + + Returns: + 検索結果のリスト(id, score, metadataなどを含むdict) + """ + +# 使用例 +results = await self.plugin.vector_search( + collection_id="kb_uuid", + query_vector=[0.1, 0.2, ...], + top_k=5, +) +# 返却フォーマット: [{"id": "chunk_0", "score": 0.123, "metadata": {"document_id": "doc1", ...}}, ...] +``` + + +`vector_search`が返す各結果はdictで、`id`(ベクトルID)、`score`(距離スコア)、`metadata`(upsert時に提供したメタデータ)の3つのフィールドを含みます。検索結果にテキスト内容が必要な場合は、取り込み時にmetadataにテキストを保存してください。 + + +### ベクトルの削除 + +```python +async def vector_delete( + self, + collection_id: str, + file_ids: list[str] | None = None, + filters: dict | None = None, +) -> int: + """ホストのベクトルデータベースからベクトルを削除 + + Args: + collection_id: ターゲットコレクションID + file_ids: 削除するファイルIDのリスト + filters: オプションのメタデータフィルター + + Returns: + 削除されたアイテム数 + """ + +# 使用例 +deleted = await self.plugin.vector_delete( + collection_id="kb_uuid", + file_ids=["doc_001"], +) +``` + + +`filters`パラメータはChromaスタイルの`where`構文によるメタデータフィルタリングをサポートしています。複数のトップレベルキーはAND条件として結合されます。サポートされる演算子:`$eq`、`$ne`、`$gt`、`$gte`、`$lt`、`$lte`、`$in`、`$nin`。例:`{"file_id": {"$eq": "abc"}}`。 + +**注意:** Chroma、Qdrant、SeekDBは完全なメタデータを保存し、任意のフィールドでフィルタリングできます。MilvusとpgvectorはDB側に`text`、`file_id`、`chunk_uuid`のみ保存しており、それ以外のフィールドでのフィルタリングは無視されます。 + + +```python +async def get_knowledge_file_stream(self, storage_path: str) -> bytes: + """ホストストレージからファイル内容を取得 + + Args: + storage_path: ファイルのストレージパス(FileObject.storage_pathから取得) + + Returns: + ファイル内容のバイトデータ + """ + +# 使用例 +file_bytes = await self.plugin.get_knowledge_file_stream(context.file_object.storage_path) +``` + +## RAGエンジンのテスト + +作成後、プラグインディレクトリでコマンド`lbp run`を実行してデバッグを開始します。その後、LangBotで: + +1. 「ナレッジベース」ページに移動 +2. 「ナレッジベースを作成」をクリック +3. プラグインが提供するRAGエンジンを選択し、エンジンの`creation_schema`に基づいて設定を入力 +4. 作成後、ドキュメントをアップロードして取り込み機能をテスト(エンジンが`DOC_INGESTION`能力を宣言している場合) +5. パイプラインにナレッジベースをバインドし、検索機能をテスト diff --git a/ja/plugin/dev/components/parser.mdx b/ja/plugin/dev/components/parser.mdx new file mode 100644 index 000000000..247894ada --- /dev/null +++ b/ja/plugin/dev/components/parser.mdx @@ -0,0 +1,201 @@ +--- +title: "コンポーネント: パーサー" +--- + +パーサー(Parser)コンポーネントにより、プラグインはLangBotにドキュメント解析機能を提供できます。ユーザーがナレッジベースにドキュメントをアップロードする際、LangBotはRAGエンジンの`ingest`ステップの前にパーサーを呼び出し、PDF、Word、Markdownなどのバイナリファイルから構造化テキストを抽出します。 + +ParserとKnowledgeEngineの関係: +- **Parser** はファイルをテキストに変換する役割(file → text) +- **KnowledgeEngine** はテキストのインデックス作成と検索を行う役割(text → chunks → vectors) + +RAGエンジン自体がドキュメント解析機能を持っている場合(`DOC_PARSING`能力を宣言)、ユーザーはRAGエンジンの内蔵解析機能を使用するか、外部Parserプラグインを使用するかを選択できます。 + +## パーサーコンポーネントの追加 + +1つのプラグインには任意の数のパーサーを追加できます。プラグインディレクトリでコマンド`lbp comp Parser`を実行し、プロンプトに従ってパーサーの設定を入力します。 + +```bash +➜ MyParserPlugin > lbp comp Parser +Generating component Parser... +Parser name: pdf_parser +Parser description: A PDF document parser +Component Parser generated successfully. +組件 Parser 生成成功。 +``` + +これにより、`components/parser/`ディレクトリに`pdf_parser.yaml`と`pdf_parser.py`ファイルが生成されます。`.yaml`ファイルはパーサーの基本情報とサポートするMIMEタイプを定義し、`.py`ファイルはこのパーサーのハンドラです: + +```bash +➜ MyParserPlugin > tree +... +├── components +│ ├── __init__.py +│ └── parser +│ ├── __init__.py +│ ├── pdf_parser.py +│ └── pdf_parser.yaml +... +``` + +## マニフェストファイル: パーサー + +```yaml +apiVersion: v1 # 変更しないでください +kind: Parser # 変更しないでください +metadata: + name: pdf_parser # パーサー名、このパーサーを識別するために使用 + label: + en_US: PDF Parser # パーサー表示名、LangBotのUIに表示、多言語対応 + zh_Hans: PDF 解析器 + description: + en_US: 'A PDF document parser' + zh_Hans: 'PDF 文档解析器' +spec: + supported_mime_types: # サポートするファイルのMIMEタイプを宣言 + - application/pdf +execution: + python: + path: pdf_parser.py # パーサーハンドラ、変更しないでください + attr: PdfParser # パーサーハンドラのクラス名、pdf_parser.pyのクラス名と一致 +``` + +### supported_mime_types + +`supported_mime_types`はこのパーサーがサポートするファイルタイプを宣言します。一般的なMIMEタイプ: + +| MIMEタイプ | 説明 | +|----------|------| +| `application/pdf` | PDFドキュメント | +| `application/vnd.openxmlformats-officedocument.wordprocessingml.document` | Wordドキュメント(.docx) | +| `text/markdown` | Markdownファイル | +| `text/plain` | プレーンテキストファイル | +| `text/html` | HTMLファイル | + +## プラグインハンドラ + +以下のコードがデフォルトで生成されます(`components/parser/<パーサー名>.py`)。`parse`メソッドを実装する必要があります。 + +```python +from langbot_plugin.api.definition.components.parser.parser import Parser +from langbot_plugin.api.entities.builtin.rag.models import ( + ParseContext, + ParseResult, + TextSection, +) + + +class PdfParser(Parser): + """ファイルからテキストを抽出するパーサーコンポーネント。""" + + async def parse(self, context: ParseContext) -> ParseResult: + """ファイルを解析して構造化テキストを抽出。 + + Args: + context: file_content(bytes)、mime_type、filename、metadataを含む。 + + Returns: + 抽出されたテキストとオプションの構造化セクションを含むParseResult。 + """ + # TODO: 解析ロジックを実装 + text = context.file_content.decode("utf-8", errors="replace") + + return ParseResult( + text=text, + sections=[ + TextSection( + content=text, + heading=context.filename, + level=0, + ), + ], + metadata={ + "filename": context.filename, + "mime_type": context.mime_type, + }, + ) +``` + +### 解析メソッド + +`parse`メソッドは、ドキュメントがナレッジベースにアップロードされた時(RAGエンジンの取り込み前)に呼び出されます: + +```python +async def parse(self, context: ParseContext) -> ParseResult: +``` + +**ParseContext**には以下の情報が含まれます: + +```python +class ParseContext(pydantic.BaseModel): + file_content: bytes # 生のファイルバイト(LangBotがストレージから読み取り) + mime_type: str # 検出されたファイルのMIMEタイプ + filename: str # 元のファイル名 + metadata: dict[str, Any] # FileObjectからの追加メタデータ +``` + +**ParseResult**は解析結果を返す必要があります: + +```python +class ParseResult(pydantic.BaseModel): + text: str # 抽出された完全なプレーンテキスト + sections: list[TextSection] = [] # 構造化セクション(オプション) + metadata: dict[str, Any] = {} # 解析メタデータ(例: page_count, language) +``` + +**TextSection**はドキュメントから抽出されたテキストのセクションを表します: + +```python +class TextSection(pydantic.BaseModel): + content: str # セクションのテキスト内容 + heading: str | None = None # セクション見出し + level: int = 0 # ネストレベル + page: int | None = None # ソースページ番号(PDFなど) + metadata: dict[str, Any] = {} # 追加セクションメタデータ +``` + +## KnowledgeEngineとの連携 + +ユーザーがドキュメントをアップロードする際、LangBotは以下のロジックで解析フローを決定します: + +- ユーザーが外部Parserプラグインを選択した場合、LangBotはまずParserの`parse`メソッドを呼び出し、その結果を`IngestionContext.parsed_content`を通じてRAGエンジンの`ingest`メソッドに渡します。 +- RAGエンジンが`DOC_PARSING`能力を宣言しており、ユーザーが外部パーサーを選択していない場合、RAGエンジンが自身でドキュメント解析を処理します。 + +KnowledgeEngineは`IngestionContext.parsed_content`をチェックして、事前解析されたコンテンツが利用可能かどうかを判断できます: + +```python +async def ingest(self, context: IngestionContext) -> IngestionResult: + if context.parsed_content: + # 外部Parserの解析結果を使用 + text = context.parsed_content.text + sections = context.parsed_content.sections + else: + # ドキュメントを内部で解析 + file_bytes = await self.plugin.get_knowledge_file_stream(context.file_object.storage_path) + text = file_bytes.decode('utf-8') + ... +``` + +## クロスプラグインパーサー呼び出し + +KnowledgeEngineプラグインは`self.plugin.invoke_parser`を通じて、他のプラグインが提供するパーサーを呼び出すことができます: + +```python +result = await self.plugin.invoke_parser( + plugin_author="author_name", + plugin_name="plugin_name", + storage_path=context.file_object.storage_path, + mime_type=context.file_object.metadata.mime_type, + filename=context.file_object.metadata.filename, + metadata={}, +) +# resultはtext、sections、metadataを含むdict +``` + +## パーサーのテスト + +作成後、プラグインディレクトリでコマンド`lbp run`を実行してデバッグを開始します。その後、LangBotで: + +1. 「ナレッジベース」ページに移動 +2. ナレッジベースを選択してドキュメント管理に入る +3. ファイルのアップロード時、パーサーセレクターでプラグインが提供するパーサーを選択 +4. アップロード後、ドキュメントが正しく取り込まれたことを確認 diff --git a/ja/plugin/plugin-intro.mdx b/ja/plugin/plugin-intro.mdx index affc667d4..68dd420a5 100644 --- a/ja/plugin/plugin-intro.mdx +++ b/ja/plugin/plugin-intro.mdx @@ -15,7 +15,7 @@ LangBotは、大規模言語モデルツール、コマンド、メッセージ - イベントハンドラー: パイプライン実行中のイベントをリッスンし、コンテキストやパイプラインを変更します。 - コマンド: `!`(または他の設定されたプレフィックス)で始まるユーザーコマンドメッセージによってトリガーされます。 - ツール: LangBotの組み込みLocal Agentの実行中にLLMによって呼び出されます。 -- ナレッジリトリーバー: 外部ナレッジベースから関連するナレッジを検索するために使用されます。 +- RAGエンジン: ナレッジベースのドキュメントインデックス作成と検索を管理します。 将来的には、プラグイン実装のためにより多くのコンポーネントタイプがサポートされます。 diff --git a/ja/usage/knowledge/readme.mdx b/ja/usage/knowledge/readme.mdx index 7accc82d2..64af60ad3 100644 --- a/ja/usage/knowledge/readme.mdx +++ b/ja/usage/knowledge/readme.mdx @@ -2,23 +2,24 @@ title: "ナレッジベースの使用" --- -LangBotはRAG(Retrieval-Augmented Generation)をネイティブサポートしています。組み込みナレッジベースを作成してパイプラインにバインドすることで、ナレッジベースの内容に基づいて質問に答えることができます。LangBotは外部のナレッジ検索サービス(例: Dify、RAGFlow)への接続もサポートしています。 +LangBotはRAG(Retrieval-Augmented Generation)をネイティブサポートしています。ナレッジベースを作成してパイプラインにバインドすることで、ナレッジベースの内容に基づいて質問に答えることができます。 -## 組み込みナレッジベースの構築 +ナレッジベースはRAGエンジンプラグインによって駆動され、異なるRAGエンジンは異なるインデックス作成と検索戦略を提供します。利用可能なRAGエンジンプラグインは[プラグインマーケットプレイス](https://space.langbot.app/market)で見つけることができます。 + +## ナレッジベースの作成 + +ナレッジベースページで`ナレッジベースを作成`ボタンをクリックします: + +1. ナレッジベース名を入力 +2. RAGエンジンを選択(インストール済みのプラグインが提供) +3. 選択したエンジンの設定フォームに基づいて関連パラメータを入力(例: 埋め込みモデル、チャンクサイズなど) +4. `作成`ボタンをクリック -組み込みナレッジベースの作成と使用には、まず埋め込みモデルの設定が必要です。[モデルの設定](/ja/usage/models/readme)をお読みください。 +RAGエンジンによって設定パラメータが異なり、エンジンの`creation_schema`定義に依存します。一部のエンジンでは、まず埋め込みモデルの設定が必要な場合があります。[モデルの設定](/ja/usage/models/readme)をお読みください。 -`ナレッジベースを作成`ボタンをクリックし、名前を入力し、既に設定済みの埋め込みモデルを選択してから`作成`をクリックします。 - -作成後、ナレッジベースの「ドキュメント」タブに移動してドキュメントをアップロードします。LangBotはバックグラウンドで自動的に解析とインデックス化を行います。 - -create_kb - -## 外部ナレッジベースの接続 - -ナレッジベースページで「外部ナレッジベース」タブに切り替え、外部リトリーバーを追加します。ダイアログで使用するナレッジリトリーバープラグインを選択します(関連プラグインをプラグイン管理で事前にインストールしてください)。サポートされているリトリーバーは[プラグインマーケットプレイス](https://space.langbot.app/market?category=KnowledgeRetriever)で確認できます。 +選択したRAGエンジンがドキュメントのアップロードをサポートしている場合(`DOC_INGESTION`能力を宣言している場合)、作成後にナレッジベースの「ドキュメント」タブに移動してドキュメントをアップロードします。LangBotはバックグラウンドで自動的に解析とインデックス化を行います。 ## ナレッジベースの使用 @@ -35,3 +36,7 @@ LangBotの組み込みナレッジベースは、ランナーが`Local Agent`に これで`チャットデバッグ`でナレッジベースを使用したり、このパイプラインにリンクされたボットを通じて使用できます: use_kb_in_chat + +## カスタムRAGエンジンの開発 + +既存の知識エンジンプラグインがニーズを満たさない場合、独自の知識エンジンプラグインを開発できます。開発方法については、[コンポーネント: 知識エンジン](/ja/plugin/dev/components/knowledge-engine)を参照してください。 diff --git a/zh/plugin/dev/apis/common.mdx b/zh/plugin/dev/apis/common.mdx index 47533ba3f..c3db5e186 100644 --- a/zh/plugin/dev/apis/common.mdx +++ b/zh/plugin/dev/apis/common.mdx @@ -307,4 +307,192 @@ async def get_config_file(self, file_key: str) -> bytes: file_bytes = await self.plugin.get_config_file("key") ``` -请结合 [``file 或 array[file] 类型配置项``](/zh/plugin/dev/basic-info.html#type-file)使用。 \ No newline at end of file +请结合 [``file 或 array[file] 类型配置项``](/zh/plugin/dev/basic-info.html#type-file)使用。 + +## RAG API + +这些 API 供 `KnowledgeEngine` 组件调用,用于访问 LangBot 宿主的嵌入模型、向量数据库和文件存储。访问方式: + +- 在 `KnowledgeEngine` 组件类中:`self.plugin` 对象内部方法。 + +### 调用嵌入模型 + +调用宿主已配置的嵌入模型生成文本向量。 + +```python +async def invoke_embedding( + self, + embedding_model_uuid: str, + texts: list[str], +) -> list[list[float]]: + """调用嵌入模型生成向量 + + Args: + embedding_model_uuid: 嵌入模型 UUID + texts: 待嵌入的文本列表 + + Returns: + 向量列表,每个输入文本对应一个向量 + """ + +# 调用示例 +vectors = await self.plugin.invoke_embedding("model_uuid", ["Hello", "World"]) +``` + +### 向量写入 + +将向量写入宿主的向量数据库。 + +```python +async def vector_upsert( + self, + collection_id: str, + vectors: list[list[float]], + ids: list[str], + metadata: list[dict[str, Any]] | None = None, + documents: list[str] | None = None, +) -> None: + """向量写入 + + Args: + collection_id: 目标集合 ID + vectors: 向量列表 + ids: 向量唯一标识列表 + metadata: 可选的元数据列表 + documents: 可选的原始文本文档列表。在支持全文/混合检索的 + 后端中,需要传入此参数以启用全文和混合检索。 + """ + +# 调用示例 +await self.plugin.vector_upsert( + collection_id="kb_uuid", + vectors=[[0.1, 0.2, ...], [0.3, 0.4, ...]], + ids=["chunk_0", "chunk_1"], + metadata=[{"document_id": "doc1"}, {"document_id": "doc1"}], + documents=["分块文本 0", "分块文本 1"], +) +``` + +### 向量搜索 + +在宿主的向量数据库中搜索相似向量。 + +```python +async def vector_search( + self, + collection_id: str, + query_vector: list[float], + top_k: int = 5, + filters: dict[str, Any] | None = None, + search_type: str = "vector", + query_text: str = "", +) -> list[dict[str, Any]]: + """向量搜索 + + Args: + collection_id: 目标集合 ID + query_vector: 查询向量 + top_k: 返回结果数量 + filters: 可选的元数据过滤条件 + search_type: 检索方式,可选 'vector'、'full_text'、'hybrid' + query_text: 原始查询文本,用于全文检索和混合检索 + + Returns: + 搜索结果列表(包含 id, score, metadata 等字段) + """ + +# 调用示例 +results = await self.plugin.vector_search( + collection_id="kb_uuid", + query_vector=[0.1, 0.2, ...], + top_k=5, + search_type="hybrid", + query_text="搜索查询", +) +# 返回格式: [{"id": "chunk_0", "score": 0.123, "metadata": {"document_id": "doc1", ...}}, ...] +``` + + +`vector_search` 返回的每个结果为 dict,包含 `id`(向量 ID)、`score`(距离分数)和 `metadata`(写入时附带的元数据)三个字段。如果需要在检索结果中返回文本内容,请在摄取阶段将文本存入 metadata 中。 + + +### 向量删除 + +从宿主的向量数据库中删除向量。 + +```python +async def vector_delete( + self, + collection_id: str, + file_ids: list[str] | None = None, + filters: dict[str, Any] | None = None, +) -> int: + """向量删除 + + Args: + collection_id: 目标集合 ID + file_ids: 要删除的文件 ID 列表 + filters: 可选的元数据过滤条件 + + Returns: + 删除的条目数量 + """ + +# 调用示例 +deleted = await self.plugin.vector_delete( + collection_id="kb_uuid", + file_ids=["doc_001"], +) +``` + + +`filters` 参数支持 Chroma 风格的 `where` 语法进行元数据过滤。多个顶层键之间为 AND 关系。支持的运算符:`$eq`、`$ne`、`$gt`、`$gte`、`$lt`、`$lte`、`$in`、`$nin`。 + +```python +# 隐式 $eq +results = await self.plugin.vector_search( + collection_id="kb_uuid", + query_vector=[0.1, 0.2, ...], + filters={"file_id": "abc"}, +) + +# 比较运算符 +results = await self.plugin.vector_search( + collection_id="kb_uuid", + query_vector=[0.1, 0.2, ...], + filters={"created_at": {"$gte": 1700000000}}, +) + +# 列表运算符 +results = await self.plugin.vector_search( + collection_id="kb_uuid", + query_vector=[0.1, 0.2, ...], + filters={"file_type": {"$in": ["pdf", "docx"]}}, +) + +# 按过滤条件删除 +deleted = await self.plugin.vector_delete( + collection_id="kb_uuid", + filters={"file_type": {"$eq": "pdf"}}, +) +``` + +**注意:** Chroma、Qdrant 和 SeekDB 存储完整的元数据,可以对任意字段进行过滤。Milvus 和 pgvector 仅存储 `text`、`file_id` 和 `chunk_uuid`,对其他字段的过滤将被静默忽略。 + + +从宿主存储中获取上传文件的内容。 + +```python +async def get_knowledge_file_stream(self, storage_path: str) -> bytes: + """获取文件内容 + + Args: + storage_path: 文件的存储路径(来自 FileObject.storage_path) + + Returns: + 文件内容的字节数据 + """ + +# 调用示例 +file_bytes = await self.plugin.get_knowledge_file_stream(context.file_object.storage_path) +``` \ No newline at end of file diff --git a/zh/plugin/dev/basic-info.mdx b/zh/plugin/dev/basic-info.mdx index 72f1399dd..db3e636f9 100644 --- a/zh/plugin/dev/basic-info.mdx +++ b/zh/plugin/dev/basic-info.mdx @@ -56,6 +56,10 @@ spec: zh_Hans: 如果不填的话,图片可能会下载失败 default: '' # 配置项默认值,可选。 required: false # 配置项是否必填,可选。 + show_if: # 配置项条件渲染(满足条件时才显示),可选。 + field: mode + operator: eq + value: mode1 - name: mode type: select ... @@ -63,6 +67,21 @@ spec: 配置项支持的各个类型及字段如下: +### 条件渲染 (`show_if`) + +所有类型的配置项都支持通过 `show_if` 字段实现条件渲染。您可以根据同一个表单(或关联表单)中其他字段的值,来决定当前字段是否在界面上渲染显示。 + +```yaml +- name: advanced_setting + type: string + show_if: + field: mode # 依赖的字段名称 + operator: eq # 判断操作符,支持 'eq' (等于), 'neq' (不等于), 'in' (包含于列表) + value: advanced # 判断的标准值 +``` + +> **注意:** 跨表单的级联关系(如 RAG 引擎中 `creation_schema` 的配置去读取 `retrieval_schema` 的值,反之亦然)在 LangBot 最新前端中已经受支持。 + ### type: string 字符串。 diff --git a/zh/plugin/dev/components/add.mdx b/zh/plugin/dev/components/add.mdx index f6a2c4b30..3df97814d 100644 --- a/zh/plugin/dev/components/add.mdx +++ b/zh/plugin/dev/components/add.mdx @@ -7,7 +7,8 @@ title: "添加组件" - 事件监听器(EventListener):监听流水线执行期间的事件,对上下文或流水线进行修改。 - 命令(Command):由用户通过`!`(或其他已设置的前缀)开头的命令消息触发。 - 工具(Tool):供 LangBot 内置的 Local Agent 在执行期间由 LLM 调用。 -- 知识检索器(KnowledgeRetriever):为 LangBot 提供外部知识库检索能力,支持接入各类 RAG 系统。 +- RAG 引擎(KnowledgeEngine):为 LangBot 提供知识库的索引和检索能力,管理文档摄取、检索和删除。 +- 解析器(Parser):在文档摄取前将 PDF、Word 等二进制文件解析为结构化文本,供 RAG 引擎使用。 ## 添加组件 @@ -72,4 +73,5 @@ components: - [EventListener](./event-listener) - [Command](./command) - [Tool](./tool) -- [KnowledgeRetriever](./knowledge-retriever) +- [KnowledgeEngine](./knowledge-engine) +- [Parser](./parser) diff --git a/zh/plugin/dev/components/knowledge-engine.mdx b/zh/plugin/dev/components/knowledge-engine.mdx new file mode 100644 index 000000000..de2968ef5 --- /dev/null +++ b/zh/plugin/dev/components/knowledge-engine.mdx @@ -0,0 +1,479 @@ +--- +title: "组件:知识引擎" +--- + +RAG 引擎(KnowledgeEngine)组件允许插件为 LangBot 提供完整的知识库索引与检索能力。用户在 LangBot 中创建知识库时,可以选择由插件提供的 RAG 引擎来管理文档的摄取、检索和删除。或通过插件桥接来自 Dify、RAGFlow、FastGPT 等业内先进知识库服务。 + +## 添加 RAG 引擎组件 + +单个插件中能添加任意数量的 RAG 引擎,请在插件目录执行命令 `lbp comp KnowledgeEngine`,并根据提示输入 RAG 引擎的配置。 + +```bash +➜ MyRAGPlugin > lbp comp KnowledgeEngine +Generating component KnowledgeEngine... +Knowledge Engine name: simple_rag +Knowledge Engine description: A simple Knowledge Engine with built-in chunking and retrieval +Component KnowledgeEngine generated successfully. +组件 KnowledgeEngine 生成成功。 +``` + +现在即会在 `components/knowledge_engine/` 目录下生成 `simple_rag.yaml` 和 `simple_rag.py` 文件,`.yaml` 定义了 RAG 引擎的基础信息和配置 Schema,`.py` 是该引擎的处理程序: + +```bash +➜ MyRAGPlugin > tree +... +├── components +│ ├── __init__.py +│ └── knowledge_engine +│ ├── __init__.py +│ ├── simple_rag.py +│ └── simple_rag.yaml +... +``` + +## 清单文件:RAG 引擎 + +```yaml +apiVersion: v1 # 请勿修改 +kind: KnowledgeEngine # 请勿修改 +metadata: + name: simple_rag # RAG 引擎名称,用于标识该引擎 + label: + en_US: Simple Knowledge Engine # 引擎显示名称,用于显示在 LangBot 的 UI 上,支持多语言 + zh_Hans: 简易 RAG 引擎 + ja_JP: シンプル RAG エンジン + description: + en_US: 'A simple Knowledge Engine with built-in chunking and retrieval' + zh_Hans: '内置分块和检索能力的简易 RAG 引擎' + ja_JP: 'チャンキングと検索を内蔵したシンプルなRAGエンジン' +spec: + creation_schema: # 用户创建知识库时需要填写的配置参数 + - name: chunk_size + label: + en_US: Chunk Size + zh_Hans: 分块大小 + type: integer + required: false + default: 500 + - name: chunk_overlap + label: + en_US: Chunk Overlap + zh_Hans: 分块重叠 + type: integer + required: false + default: 50 + retrieval_schema: # 检索时可配置的参数 + - name: score_threshold + label: + en_US: Score Threshold + zh_Hans: 分数阈值 + type: float + required: false + default: 0.5 +execution: + python: + path: simple_rag.py # 引擎处理程序,请勿修改 + attr: SimpleRag # 引擎处理程序的类名,与 simple_rag.py 中的类名一致 +``` + +配置项格式可参考:[插件清单配置项格式](/zh/plugin/dev/basic-info) + +### creation_schema 与 retrieval_schema + +与旧版 `KnowledgeRetriever` 组件使用单一 `spec.config` 不同,KnowledgeEngine 使用两个独立的 Schema: + +- **creation_schema**:用户创建知识库时填写的参数,会在摄取和检索时通过 `creation_settings` 传入插件。 +- **retrieval_schema**:用户在检索知识库时可调整的参数,通过 `retrieval_settings` 传入插件。 + +## 能力声明 + +KnowledgeEngine 可以声明自身支持的能力,LangBot 会根据能力声明决定 UI 展示和可用操作: + +```python +from langbot_plugin.api.definition.components.knowledge_engine.engine import KnowledgeEngine, KnowledgeEngineCapability + +class SimpleRag(KnowledgeEngine): + + @classmethod + def get_capabilities(cls) -> list[str]: + return [ + KnowledgeEngineCapability.DOC_INGESTION, # 支持文档上传和处理 + ] +``` + +可用的能力常量: + +| 能力 | 说明 | +|------|------| +| `DOC_INGESTION` | 支持文档上传和处理。声明此能力后,LangBot 将在知识库详情中显示"文档"标签页;若该插件是用于桥接其他的知识库服务或不需要用户上传文档,则可不提供该能力声明。 | +| `DOC_PARSING` | 支持原生文档解析(文件到文本的提取)。声明此能力后,用户上传文档时可以选择使用 RAG 引擎内置的解析能力,而不需要外部 [Parser 插件](./parser)。若未声明此能力,则必须安装外部 Parser 插件才能上传文档。 | + + +其他检索行为(如重排序、混合搜索等)由插件的 `retrieval_schema` 控制,不需要能力标志。 + + +## 插件处理 + +默认会生成如下代码(`components/knowledge_engine/<引擎名称>.py`),您需要实现 `ingest`、`retrieve`、`delete_document` 三个核心方法。完整代码可在 [langbot-plugin-demo](https://github.com/langbot-app/langbot-plugin-demo) 的 `SimpleKnowledgeEngine` 中找到。 + +```python +from langbot_plugin.api.definition.components.knowledge_engine.engine import KnowledgeEngine, KnowledgeEngineCapability +from langbot_plugin.api.entities.builtin.rag.models import ( + IngestionContext, + IngestionResult, +) +from langbot_plugin.api.entities.builtin.rag.context import ( + RetrievalContext, + RetrievalResponse, + RetrievalResultEntry, +) +from langbot_plugin.api.entities.builtin.rag.enums import DocumentStatus +from langbot_plugin.api.entities.builtin.provider.message import ContentElement + + +class SimpleRag(KnowledgeEngine): + + @classmethod + def get_capabilities(cls) -> list[str]: + return [KnowledgeEngineCapability.DOC_INGESTION] + + async def on_knowledge_base_create(self, kb_id: str, config: dict) -> None: + """知识库创建时的回调,可用于初始化资源""" + pass + + async def on_knowledge_base_delete(self, kb_id: str) -> None: + """知识库删除时的回调,可用于清理资源""" + pass + + async def ingest(self, context: IngestionContext) -> IngestionResult: + """将文档摄取到知识库中""" + + # 1. 获取文件内容 + file_bytes = await self.plugin.get_knowledge_file_stream(context.file_object.storage_path) + + # 2. 解析文档并分块 + text = file_bytes.decode('utf-8') + chunk_size = context.creation_settings.get('chunk_size', 500) + chunk_overlap = context.creation_settings.get('chunk_overlap', 50) + chunks = self._split_text(text, chunk_size, chunk_overlap) + + # 3. 调用宿主嵌入模型生成向量 + embedding_model_uuid = context.creation_settings.get('embedding_model_uuid', '') + vectors = await self.plugin.invoke_embedding(embedding_model_uuid, chunks) + + # 4. 写入宿主向量数据库 + collection_id = context.get_collection_id() + ids = [f"{context.file_object.metadata.document_id}_{i}" for i in range(len(chunks))] + metadata = [{"document_id": context.file_object.metadata.document_id, "chunk_index": i, "text": chunks[i]} for i in range(len(chunks))] + await self.plugin.vector_upsert(collection_id, vectors, ids, metadata) + + return IngestionResult( + document_id=context.file_object.metadata.document_id, + status=DocumentStatus.COMPLETED, + chunks_created=len(chunks), + ) + + async def retrieve(self, context: RetrievalContext) -> RetrievalResponse: + """从知识库中检索相关内容""" + + # 1. 生成查询向量 + embedding_model_uuid = context.creation_settings.get('embedding_model_uuid', '') + query_vectors = await self.plugin.invoke_embedding(embedding_model_uuid, [context.query]) + query_vector = query_vectors[0] + + # 2. 向量搜索 + collection_id = context.get_collection_id() + results = await self.plugin.vector_search(collection_id, query_vector, top_k=context.retrieval_settings.get('top_k', 5)) + + # 3. 转换为检索结果 + entries = [] + for r in results: + entry = RetrievalResultEntry( + id=r.get('id', ''), + content=[ContentElement.from_text(r.get('metadata', {}).get('text', ''))], + metadata=r.get('metadata', {}), + distance=r.get('score', 0.0), + ) + entries.append(entry) + + return RetrievalResponse( + results=entries, + total_found=len(entries), + metadata={}, + ) + + async def delete_document(self, kb_id: str, document_id: str) -> bool: + """从知识库中删除文档及其关联数据""" + collection_id = kb_id + deleted = await self.plugin.vector_delete(collection_id, file_ids=[document_id]) + return deleted > 0 +``` + +### 生命周期钩子 + +KnowledgeEngine 提供两个生命周期钩子,会在知识库创建和删除时被调用: + +```python +async def on_knowledge_base_create(self, kb_id: str, config: dict) -> None: + """知识库创建时的回调 + + Args: + kb_id: 知识库 UUID + config: 用户在创建时填写的配置(creation_schema 中的字段) + """ + +async def on_knowledge_base_delete(self, kb_id: str) -> None: + """知识库删除时的回调 + + Args: + kb_id: 知识库 UUID + """ +``` + +### 文档摄取 + +`ingest` 方法在用户上传文档到知识库时被调用: + +```python +async def ingest(self, context: IngestionContext) -> IngestionResult: +``` + +**IngestionContext** 包含以下信息: + +```python +class IngestionContext(pydantic.BaseModel): + file_object: FileObject # 待摄取的文件对象 + knowledge_base_id: str # 目标知识库 ID + collection_id: str | None # 向量集合 ID(默认回退到 knowledge_base_id) + creation_settings: dict # 知识库创建时的配置参数 + parsed_content: ParseResult | None # 外部 Parser 插件的预解析内容(若有) +``` + +**FileObject** 包含文件元信息: + +```python +class FileObject(pydantic.BaseModel): + metadata: FileMetadata # 包含 filename, file_size, mime_type, document_id, knowledge_base_id 等 + storage_path: str # 文件在存储系统中的路径 +``` + +**IngestionResult** 应返回摄取结果: + +```python +class IngestionResult(pydantic.BaseModel): + document_id: str # 文档 ID + status: DocumentStatus # 处理状态:COMPLETED / FAILED + chunks_created: int = 0 # 创建的分块数量 + error_message: str | None = None # 失败时的错误信息 + metadata: dict = {} # 额外元数据 +``` + +### 知识检索 + +`retrieve` 方法在知识库被查询时调用: + +```python +async def retrieve(self, context: RetrievalContext) -> RetrievalResponse: +``` + +**RetrievalContext** 包含以下信息: + +```python +class RetrievalContext(pydantic.BaseModel): + query: str # 查询文本 + knowledge_base_id: str | None # 知识库 ID + collection_id: str | None # 向量集合 ID + retrieval_settings: dict # 检索时的配置参数(retrieval_schema 中的字段) + creation_settings: dict # 知识库创建时的配置参数 + filters: dict # 元数据过滤条件(Chroma 风格 where 语法) +``` + + +LangBot 宿主会在传入 `retrieval_settings` 前自动注入 `top_k` 默认值(默认为 5),插件可通过 `context.retrieval_settings.get('top_k', 5)` 获取。 + +`filters` 字段包含从 `retrieval_settings` 中提取的 Chroma 风格 `where` 过滤条件。当调用方在检索设置中提供 `filters`(例如按时间范围、文件类型或自定义元数据字段过滤文档)时,宿主会填充此字段,插件可据此在检索时进行过滤。如果未提供过滤条件,此字段为空 dict。 + + +**RetrievalResponse** 应返回检索结果: + +```python +class RetrievalResponse(pydantic.BaseModel): + results: list[RetrievalResultEntry] # 检索结果列表 + total_found: int # 匹配的总数 + metadata: dict # 额外元数据 +``` + +**RetrievalResultEntry** 表示单个检索结果: + +```python +class RetrievalResultEntry(pydantic.BaseModel): + id: str # 结果 ID + content: list[ContentElement] # 内容,使用 ContentElement.from_text() 创建 + metadata: dict # 元数据 + distance: float # 距离分数(越小越相关) + score: float | None = None # 相似度分数(越大越相关) +``` + +### 文档删除 + +`delete_document` 方法在用户从知识库删除文档时调用: + +```python +async def delete_document(self, kb_id: str, document_id: str) -> bool: + """删除文档及其关联数据 + + Args: + kb_id: 知识库 ID + document_id: 文档 ID + + Returns: + 是否删除成功 + """ +``` + +## 宿主 RAG API + +KnowledgeEngine 组件可以通过 `self.plugin` 调用 LangBot 宿主提供的 RAG 相关 API,包括嵌入模型调用、向量数据库操作和文件读取。 + +### 调用嵌入模型 + +```python +async def invoke_embedding( + self, + embedding_model_uuid: str, + texts: list[str], +) -> list[list[float]]: + """调用宿主的嵌入模型生成向量 + + Args: + embedding_model_uuid: 嵌入模型 UUID + texts: 待嵌入的文本列表 + + Returns: + 向量列表,每个输入文本对应一个向量 + """ + +# 调用示例 +vectors = await self.plugin.invoke_embedding("model_uuid", ["Hello", "World"]) +``` + +### 向量写入 + +```python +async def vector_upsert( + self, + collection_id: str, + vectors: list[list[float]], + ids: list[str], + metadata: list[dict] | None = None, +) -> None: + """将向量写入宿主的向量数据库 + + Args: + collection_id: 目标集合 ID + vectors: 向量列表 + ids: 向量唯一标识列表 + metadata: 可选的元数据列表 + """ + +# 调用示例 +await self.plugin.vector_upsert( + collection_id="kb_uuid", + vectors=[[0.1, 0.2, ...], [0.3, 0.4, ...]], + ids=["chunk_0", "chunk_1"], + metadata=[{"document_id": "doc1"}, {"document_id": "doc1"}], +) +``` + +### 向量搜索 + +```python +async def vector_search( + self, + collection_id: str, + query_vector: list[float], + top_k: int = 5, + filters: dict | None = None, +) -> list[dict]: + """在宿主的向量数据库中搜索相似向量 + + Args: + collection_id: 目标集合 ID + query_vector: 查询向量 + top_k: 返回结果数量 + filters: 可选的元数据过滤条件 + + Returns: + 搜索结果列表(包含 id, score, metadata 等字段) + """ + +# 调用示例 +results = await self.plugin.vector_search( + collection_id="kb_uuid", + query_vector=[0.1, 0.2, ...], + top_k=5, +) +# 返回格式: [{"id": "chunk_0", "score": 0.123, "metadata": {"document_id": "doc1", ...}}, ...] +``` + + +`vector_search` 返回的每个结果为 dict,包含 `id`(向量 ID)、`score`(距离分数)和 `metadata`(写入时附带的元数据)三个字段。如果需要在检索结果中返回文本内容,请在摄取阶段将文本存入 metadata 中。 + + +### 向量删除 + +```python +async def vector_delete( + self, + collection_id: str, + file_ids: list[str] | None = None, + filters: dict | None = None, +) -> int: + """从宿主的向量数据库中删除向量 + + Args: + collection_id: 目标集合 ID + file_ids: 要删除的文件 ID 列表 + filters: 可选的元数据过滤条件 + + Returns: + 删除的条目数量 + """ + +# 调用示例 +deleted = await self.plugin.vector_delete( + collection_id="kb_uuid", + file_ids=["doc_001"], +) +``` + + +`filters` 参数支持 Chroma 风格的 `where` 语法进行元数据过滤。多个顶层键之间为 AND 关系。支持的运算符:`$eq`、`$ne`、`$gt`、`$gte`、`$lt`、`$lte`、`$in`、`$nin`。示例:`{"file_id": {"$eq": "abc"}}`。 + +**注意:** Chroma、Qdrant 和 SeekDB 存储完整的元数据,可以对任意字段进行过滤。Milvus 和 pgvector 仅存储 `text`、`file_id` 和 `chunk_uuid`,对其他字段的过滤将被静默忽略。 + + +```python +async def get_knowledge_file_stream(self, storage_path: str) -> bytes: + """从宿主存储中获取文件内容 + + Args: + storage_path: 文件的存储路径(来自 FileObject.storage_path) + + Returns: + 文件内容的字节数据 + """ + +# 调用示例 +file_bytes = await self.plugin.get_knowledge_file_stream(context.file_object.storage_path) +``` + +## 测试 RAG 引擎 + +创建完成后,在插件目录执行命令 `lbp run`,启动调试。然后在 LangBot 中: + +1. 进入"知识库"页面 +2. 点击"创建知识库" +3. 选择您的插件提供的 RAG 引擎,并根据引擎的 `creation_schema` 填写配置 +4. 创建成功后,可上传文档测试摄取能力(如引擎声明了 `DOC_INGESTION` 能力) +5. 在流水线中绑定该知识库,测试检索能力 diff --git a/zh/plugin/dev/components/parser.mdx b/zh/plugin/dev/components/parser.mdx new file mode 100644 index 000000000..47d4a1d32 --- /dev/null +++ b/zh/plugin/dev/components/parser.mdx @@ -0,0 +1,201 @@ +--- +title: "组件:解析器" +--- + +解析器(Parser)组件允许插件为 LangBot 提供文档解析能力。在用户上传文档到知识库时,LangBot 会在 RAG 引擎摄取(`ingest`)之前调用解析器,将 PDF、Word、Markdown 等二进制文件提取为结构化文本。 + +Parser 与 KnowledgeEngine 的关系: +- **Parser** 负责将文件转换为文本(file → text) +- **KnowledgeEngine** 负责将文本索引和检索(text → chunks → vectors) + +如果 RAG 引擎自身已具备文档解析能力(声明了 `DOC_PARSING` 能力),用户可以选择使用 RAG 引擎内置的解析能力,也可以选择使用外部 Parser 插件。 + +## 添加解析器组件 + +单个插件中能添加任意数量的解析器,请在插件目录执行命令 `lbp comp Parser`,并根据提示输入解析器的配置。 + +```bash +➜ MyParserPlugin > lbp comp Parser +Generating component Parser... +Parser name: pdf_parser +Parser description: A PDF document parser +Component Parser generated successfully. +组件 Parser 生成成功。 +``` + +现在即会在 `components/parser/` 目录下生成 `pdf_parser.yaml` 和 `pdf_parser.py` 文件,`.yaml` 定义了解析器的基础信息和支持的 MIME 类型,`.py` 是该解析器的处理程序: + +```bash +➜ MyParserPlugin > tree +... +├── components +│ ├── __init__.py +│ └── parser +│ ├── __init__.py +│ ├── pdf_parser.py +│ └── pdf_parser.yaml +... +``` + +## 清单文件:解析器 + +```yaml +apiVersion: v1 # 请勿修改 +kind: Parser # 请勿修改 +metadata: + name: pdf_parser # 解析器名称,用于标识该解析器 + label: + en_US: PDF Parser # 解析器显示名称,用于显示在 LangBot 的 UI 上,支持多语言 + zh_Hans: PDF 解析器 + description: + en_US: 'A PDF document parser' + zh_Hans: 'PDF 文档解析器' +spec: + supported_mime_types: # 声明支持的文件 MIME 类型 + - application/pdf +execution: + python: + path: pdf_parser.py # 解析器处理程序,请勿修改 + attr: PdfParser # 解析器处理程序的类名,与 pdf_parser.py 中的类名一致 +``` + +### supported_mime_types + +`supported_mime_types` 声明该解析器支持解析的文件类型。常见 MIME 类型: + +| MIME 类型 | 说明 | +|----------|------| +| `application/pdf` | PDF 文档 | +| `application/vnd.openxmlformats-officedocument.wordprocessingml.document` | Word 文档(.docx) | +| `text/markdown` | Markdown 文件 | +| `text/plain` | 纯文本文件 | +| `text/html` | HTML 文件 | + +## 插件处理 + +默认会生成如下代码(`components/parser/<解析器名称>.py`),您需要实现 `parse` 方法。 + +```python +from langbot_plugin.api.definition.components.parser.parser import Parser +from langbot_plugin.api.entities.builtin.rag.models import ( + ParseContext, + ParseResult, + TextSection, +) + + +class PdfParser(Parser): + """解析器组件,用于从文件中提取文本。""" + + async def parse(self, context: ParseContext) -> ParseResult: + """解析文件并提取结构化文本。 + + Args: + context: 包含 file_content(bytes)、mime_type、filename 和 metadata。 + + Returns: + ParseResult,包含提取的文本和可选的结构化分节。 + """ + # TODO: 实现解析逻辑 + text = context.file_content.decode("utf-8", errors="replace") + + return ParseResult( + text=text, + sections=[ + TextSection( + content=text, + heading=context.filename, + level=0, + ), + ], + metadata={ + "filename": context.filename, + "mime_type": context.mime_type, + }, + ) +``` + +### 解析方法 + +`parse` 方法在文档上传到知识库时被调用(在 RAG 引擎摄取之前): + +```python +async def parse(self, context: ParseContext) -> ParseResult: +``` + +**ParseContext** 包含以下信息: + +```python +class ParseContext(pydantic.BaseModel): + file_content: bytes # 原始文件字节(由 LangBot 从存储中读取) + mime_type: str # 检测到的文件 MIME 类型 + filename: str # 原始文件名 + metadata: dict[str, Any] # FileObject 中的额外元数据 +``` + +**ParseResult** 应返回解析结果: + +```python +class ParseResult(pydantic.BaseModel): + text: str # 提取的完整纯文本 + sections: list[TextSection] = [] # 结构化分节(可选) + metadata: dict[str, Any] = {} # 解析元数据(如 page_count、language 等) +``` + +**TextSection** 表示文档中的一个段落/分节: + +```python +class TextSection(pydantic.BaseModel): + content: str # 分节文本内容 + heading: str | None = None # 分节标题 + level: int = 0 # 嵌套层级 + page: int | None = None # 来源页码(适用于 PDF 等) + metadata: dict[str, Any] = {} # 额外分节元数据 +``` + +## 与 KnowledgeEngine 的协作 + +当用户上传文档时,LangBot 会根据以下逻辑决定解析流程: + +- 若用户选择了外部 Parser 插件,LangBot 会先调用 Parser 的 `parse` 方法,然后将解析结果通过 `IngestionContext.parsed_content` 传递给 RAG 引擎的 `ingest` 方法。 +- 若 RAG 引擎声明了 `DOC_PARSING` 能力且用户未选择外部解析器,则由 RAG 引擎自行处理文档解析。 + +KnowledgeEngine 可通过检查 `IngestionContext.parsed_content` 来判断是否存在预解析内容: + +```python +async def ingest(self, context: IngestionContext) -> IngestionResult: + if context.parsed_content: + # 使用外部 Parser 的解析结果 + text = context.parsed_content.text + sections = context.parsed_content.sections + else: + # 自行解析文档 + file_bytes = await self.plugin.get_knowledge_file_stream(context.file_object.storage_path) + text = file_bytes.decode('utf-8') + ... +``` + +## 跨插件调用解析器 + +KnowledgeEngine 插件可以通过 `self.plugin.invoke_parser` 方法调用其他插件提供的解析器: + +```python +result = await self.plugin.invoke_parser( + plugin_author="author_name", + plugin_name="plugin_name", + storage_path=context.file_object.storage_path, + mime_type=context.file_object.metadata.mime_type, + filename=context.file_object.metadata.filename, + metadata={}, +) +# result 为 dict,包含 text、sections、metadata +``` + +## 测试解析器 + +创建完成后,在插件目录执行命令 `lbp run`,启动调试。然后在 LangBot 中: + +1. 进入"知识库"页面 +2. 选择一个知识库并进入文档管理 +3. 上传文件时,在解析器选择器中选择您的插件提供的解析器 +4. 上传文件后检查文档是否正确摄取 diff --git a/zh/plugin/plugin-intro.mdx b/zh/plugin/plugin-intro.mdx index c994a629f..23bc67feb 100644 --- a/zh/plugin/plugin-intro.mdx +++ b/zh/plugin/plugin-intro.mdx @@ -15,7 +15,7 @@ LangBot 内部由多种组件组成,如大模型工具、命令、消息平台 - 事件处理器:监听流水线执行期间的事件,对上下文或流水线进行修改。 - 命令:由用户通过`!`(或其他已设置的前缀)开头的命令消息触发。 - 工具:供 LangBot 内置的 Local Agent 在执行期间由 LLM 调用。 -- 知识检索器:用于从外部知识库中检索相关知识。 +- RAG 引擎:管理知识库的文档索引和检索。 后续还将支持更多组件的插件化。 diff --git a/zh/usage/knowledge/readme.mdx b/zh/usage/knowledge/readme.mdx index d2bee7678..e4b69d7da 100644 --- a/zh/usage/knowledge/readme.mdx +++ b/zh/usage/knowledge/readme.mdx @@ -2,27 +2,28 @@ title: "使用知识库 (Knowledge Base)" --- -LangBot 原生支持 RAG(Retrieval-Augmented Generation,即检索增强生成),您可以创建内置知识库,并将其与流水线绑定,流水线将根据知识库中的内容回答问题。同时,我们支持接入外部服务(Dify、RAGFLow等)提供的知识检索服务。 +LangBot 原生支持 RAG(Retrieval-Augmented Generation,即检索增强生成),您可以创建知识库,并将其与流水线绑定,流水线将根据知识库中的内容回答问题。 -## 构建内置知识库 +知识库由 RAG 引擎插件驱动,不同的 RAG 引擎提供不同的索引和检索策略。您可以在[插件市场](https://space.langbot.app/market)中查找可用的 RAG 引擎插件。 + +## 创建知识库 + +在知识库页面,点击`创建知识库`按钮: + +1. 填写知识库名称 +2. 选择一个 RAG 引擎(由已安装的插件提供) +3. 根据所选引擎的配置表单填写相关参数(如嵌入模型、分块大小等) +4. 点击`创建`按钮 -内置知识库的创建和使用需要先配置嵌入模型,请先阅读[配置模型](/zh/usage/models/readme)。 +不同的 RAG 引擎有不同的配置参数,具体取决于引擎的 `creation_schema` 定义。某些引擎可能需要先配置嵌入模型,请先阅读[配置模型](/zh/usage/models/readme)。 -在知识库页面,点击`创建知识库`按钮,填写知识库名称,选择已配置好的嵌入模型,然后点击`创建`按钮。 - -之后请到知识库的”文档”页面,上传文档,LangBot 将会在后台开始解析和索引。 - -create_kb - -## 接入外部知识库 - -请在知识库页面,切换到”外部知识库”标签页,添加外部知识库。在对话框中可以选择需要使用的外部知识库检索器,您需要先在插件管理中安装对应的插件。目前支持的知识检索器可以在[插件市场](https://space.langbot.app/market?category=KnowledgeRetriever)中查看。 +如果所选 RAG 引擎支持文档上传(声明了 `DOC_INGESTION` 能力),创建后请到知识库的"文档"页面,上传文档,LangBot 将会在后台开始解析和索引。 ## 使用知识库 -请到流水线配置中,”AI 能力”页,选择`内置 Agent`作为运行器,并在下方选择您刚刚创建的知识库。 +请到流水线配置中,"AI 能力"页,选择`内置 Agent`作为运行器,并在下方选择您刚刚创建的知识库。 use_kb @@ -35,3 +36,7 @@ LangBot 原生支持 RAG(Retrieval-Augmented Generation,即检索增强生 现在即可在`对话调试`或流水线所绑定的机器人上使用知识库进行对话: use_kb_in_chat + +## 开发自定义 RAG 引擎 + +如果现有的知识引擎插件不能满足您的需求,您可以开发自己的知识引擎插件。请参阅[组件:知识引擎](/zh/plugin/dev/components/knowledge-engine)了解开发方式。