Merge branch 'mrh-online-dev' of github.com:BRUNIX-AI/assistance-engine into mrh-online-dev
This commit is contained in:
commit
eef6d28db1
|
|
@ -0,0 +1,248 @@
|
||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "f1400112",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"# Libraries"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "7d593da4",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"from dataclasses import dataclass\n",
|
||||||
|
"from typing import List, Protocol, Callable, Dict, Any\n",
|
||||||
|
"import numpy as np\n",
|
||||||
|
"from sentence_transformers import SentenceTransformer\n",
|
||||||
|
"import tiktoken\n",
|
||||||
|
"from transformers import AutoTokenizer"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "157bead3",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"# Chunking Methods"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "4555f236",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"@dataclass\n",
|
||||||
|
"class Chunk:\n",
|
||||||
|
" text: str\n",
|
||||||
|
" meta: Dict[str, Any]\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"class Chunker(Protocol):\n",
|
||||||
|
" name: str\n",
|
||||||
|
"\n",
|
||||||
|
" def chunk(self, text: str, meta: Dict[str, Any]) -> List[Chunk]: ...\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "177e3b00",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"class TokenChunker:\n",
|
||||||
|
" def __init__(\n",
|
||||||
|
" self, name: str, encoding_name=\"cl100k_base\", chunk_size=400, overlap=60\n",
|
||||||
|
" ):\n",
|
||||||
|
" self.name = name\n",
|
||||||
|
" self.enc = tiktoken.get_encoding(encoding_name)\n",
|
||||||
|
" self.chunk_size = chunk_size\n",
|
||||||
|
" self.overlap = overlap\n",
|
||||||
|
"\n",
|
||||||
|
" def chunk(self, text: str, meta: Dict[str, Any]) -> List[Chunk]:\n",
|
||||||
|
" tokens = self.enc.encode(text)\n",
|
||||||
|
" chunks = []\n",
|
||||||
|
" start = 0\n",
|
||||||
|
" i = 0\n",
|
||||||
|
" while start < len(tokens):\n",
|
||||||
|
" end = min(start + self.chunk_size, len(tokens))\n",
|
||||||
|
" piece_tokens = tokens[start:end]\n",
|
||||||
|
" piece_text = self.enc.decode(piece_tokens)\n",
|
||||||
|
" chunks.append(\n",
|
||||||
|
" Chunk(piece_text, {**meta, \"chunk_index\": i, \"method\": self.name})\n",
|
||||||
|
" )\n",
|
||||||
|
" i += 1\n",
|
||||||
|
" start = end - self.overlap\n",
|
||||||
|
" if start < 0:\n",
|
||||||
|
" start = 0\n",
|
||||||
|
" if start >= len(tokens):\n",
|
||||||
|
" break\n",
|
||||||
|
" return chunks\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "9bda4930",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"def cosine(a, b):\n",
|
||||||
|
" a = a / (np.linalg.norm(a) + 1e-12)\n",
|
||||||
|
" b = b / (np.linalg.norm(b) + 1e-12)\n",
|
||||||
|
" return float(np.dot(a, b))\n",
|
||||||
|
"\n",
|
||||||
|
"class SemanticChunker:\n",
|
||||||
|
" def __init__(\n",
|
||||||
|
" self,\n",
|
||||||
|
" name: str,\n",
|
||||||
|
" embedder: SentenceTransformer,\n",
|
||||||
|
" max_tokens_fn: Callable[[str], int],\n",
|
||||||
|
" max_tokens=400,\n",
|
||||||
|
" similarity_threshold=0.78,\n",
|
||||||
|
" min_unit_chars=80,\n",
|
||||||
|
" ):\n",
|
||||||
|
" self.name = name\n",
|
||||||
|
" self.embedder = embedder\n",
|
||||||
|
" self.max_tokens_fn = max_tokens_fn\n",
|
||||||
|
" self.max_tokens = max_tokens\n",
|
||||||
|
" self.similarity_threshold = similarity_threshold\n",
|
||||||
|
" self.min_unit_chars = min_unit_chars\n",
|
||||||
|
"\n",
|
||||||
|
" def _units(self, text: str) -> List[str]:\n",
|
||||||
|
" # Párrafos como unidad base (suele ser buen punto de partida)\n",
|
||||||
|
" parts = [p.strip() for p in text.split(\"\\n\\n\") if p.strip()]\n",
|
||||||
|
" # opcional: fusiona “párrafos enanos”\n",
|
||||||
|
" merged = []\n",
|
||||||
|
" buf = \"\"\n",
|
||||||
|
" for p in parts:\n",
|
||||||
|
" if len(buf) < self.min_unit_chars:\n",
|
||||||
|
" buf = (buf + \"\\n\\n\" + p).strip() if buf else p\n",
|
||||||
|
" else:\n",
|
||||||
|
" merged.append(buf)\n",
|
||||||
|
" buf = p\n",
|
||||||
|
" if buf:\n",
|
||||||
|
" merged.append(buf)\n",
|
||||||
|
" return merged\n",
|
||||||
|
"\n",
|
||||||
|
" def chunk(self, text: str, meta: Dict[str, Any]) -> List[Chunk]:\n",
|
||||||
|
" units = self._units(text)\n",
|
||||||
|
" if not units:\n",
|
||||||
|
" return []\n",
|
||||||
|
"\n",
|
||||||
|
" unit_embs = self.embedder.encode(units, normalize_embeddings=True)\n",
|
||||||
|
"\n",
|
||||||
|
" chunks: List[Chunk] = []\n",
|
||||||
|
" cur_texts = [units[0]]\n",
|
||||||
|
" cur_center = unit_embs[0]\n",
|
||||||
|
" cur_tokens = self.max_tokens_fn(units[0])\n",
|
||||||
|
" chunk_index = 0\n",
|
||||||
|
"\n",
|
||||||
|
" for i in range(1, len(units)):\n",
|
||||||
|
" u = units[i]\n",
|
||||||
|
" e = unit_embs[i]\n",
|
||||||
|
" u_tokens = self.max_tokens_fn(u)\n",
|
||||||
|
"\n",
|
||||||
|
" sim = float(np.dot(cur_center, e)) # ya normalizado\n",
|
||||||
|
" would_tokens = cur_tokens + u_tokens\n",
|
||||||
|
"\n",
|
||||||
|
" if sim >= self.similarity_threshold and would_tokens <= self.max_tokens:\n",
|
||||||
|
" cur_texts.append(u)\n",
|
||||||
|
" # actualiza “centro” como media (normalizada)\n",
|
||||||
|
" cur_center = cur_center + e\n",
|
||||||
|
" cur_center = cur_center / (np.linalg.norm(cur_center) + 1e-12)\n",
|
||||||
|
" cur_tokens = would_tokens\n",
|
||||||
|
" else:\n",
|
||||||
|
" chunks.append(Chunk(\"\\n\\n\".join(cur_texts), {**meta, \"chunk_index\": chunk_index, \"method\": self.name}))\n",
|
||||||
|
" chunk_index += 1\n",
|
||||||
|
" cur_texts = [u]\n",
|
||||||
|
" cur_center = e\n",
|
||||||
|
" cur_tokens = u_tokens\n",
|
||||||
|
"\n",
|
||||||
|
" chunks.append(Chunk(\"\\n\\n\".join(cur_texts), {**meta, \"chunk_index\": chunk_index, \"method\": self.name}))\n",
|
||||||
|
" return chunks"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "6c0fbd36",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"def run_chunking(texts: List[Dict[str, Any]], chunkers: List[Chunker]) -> Dict[str, List[Chunk]]:\n",
|
||||||
|
" \"\"\"\n",
|
||||||
|
" texts: lista de {\"text\": ..., \"meta\": {...}}\n",
|
||||||
|
" \"\"\"\n",
|
||||||
|
" results = {}\n",
|
||||||
|
" for ch in chunkers:\n",
|
||||||
|
" all_chunks = []\n",
|
||||||
|
" for doc in texts:\n",
|
||||||
|
" all_chunks.extend(ch.chunk(doc[\"text\"], doc.get(\"meta\", {})))\n",
|
||||||
|
" results[ch.name] = all_chunks\n",
|
||||||
|
" print(f\"{ch.name}: {len(all_chunks)} chunks\")\n",
|
||||||
|
" return results"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "a9c72266",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"enc = tiktoken.get_encoding(\"cl100k_base\")\n",
|
||||||
|
"def count_tokens(s: str) -> int:\n",
|
||||||
|
" return len(enc.encode(s))\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "90e0a6d7",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"### Prueba Chunk"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "8a75f0e1",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"embedder = SentenceTransformer(\"Qwen/Qwen3-Embedding-4B-GGUF\")\n",
|
||||||
|
"\n",
|
||||||
|
"token_chunker = TokenChunker(name=\"token_400_60\", chunk_size=400, overlap=60)\n",
|
||||||
|
"semantic_chunker = SemanticChunker(\n",
|
||||||
|
" name=\"semantic_400_thr0.78\",\n",
|
||||||
|
" embedder=embedder,\n",
|
||||||
|
" max_tokens_fn=count_tokens,\n",
|
||||||
|
" max_tokens=400,\n",
|
||||||
|
" similarity_threshold=0.78,\n",
|
||||||
|
")\n",
|
||||||
|
"\n",
|
||||||
|
"texts = [\n",
|
||||||
|
" {\"text\": open(\"doc1.txt\", \"r\", encoding=\"utf-8\").read(), \"meta\": {\"source\": \"doc1.txt\"}},\n",
|
||||||
|
" {\"text\": open(\"doc2.txt\", \"r\", encoding=\"utf-8\").read(), \"meta\": {\"source\": \"doc2.txt\"}},\n",
|
||||||
|
"]\n",
|
||||||
|
"\n",
|
||||||
|
"chunked = run_chunking(texts, [token_chunker, semantic_chunker])\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"language_info": {
|
||||||
|
"name": "python"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 5
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue