--- license: apache-2.0 language: - en - es base_model: Qwen/Qwen3.5-9B tags: - knowledge-graph - entity-extraction - relation-extraction - intent-classification - structured-output - json - topic-detection - acervo - fine-tuned - LoRA datasets: - custom pipeline_tag: text-generation library_name: transformers model-index: - name: acervo-extractor-v2 results: - task: type: structured-output name: Knowledge Graph Extraction metrics: - name: JSON Parse Rate type: accuracy value: 100 - name: Extraction Accuracy type: accuracy value: 85 --- # Acervo Extractor v2 A fine-tuned version of [Qwen3.5-9B](https://huggingface.co/Qwen/Qwen3.5-9B) specialized in **knowledge graph extraction** from conversations. Given a conversation turn and existing graph context, the model outputs structured JSON with intent classification, topic detection, retrieval decision, entities, relations, and facts. > **Base model:** Qwen3.5-9B | **Method:** QLoRA (4-bit, r=16, alpha=32) | **Training:** ~1,000 examples, 3 epochs Built for [Acervo](https://github.com/SandyVeliz/acervo) — a semantic compression layer for AI agents that replaces raw conversation history with compressed knowledge graph nodes. > **Supersedes:** [acervo-extractor-qwen3.5-9b](https://huggingface.co/SandyVeliz/acervo-extractor-qwen3.5-9b) (v1, deprecated) ## What's new in v2 v1 only handled topic detection and entity extraction. v2 adds **intent classification** and **retrieval decision** — two fields that were previously handled by regex/keyword heuristics outside the model. | Feature | v1 | v2 | |---|---|---| | Topic detection | same / subtopic / changed | same / subtopic / changed | | **Intent classification** | - | overview / specific / chat / followup | | **Retrieval decision** | - | summary_only / with_chunks | | Entity extraction | 8 types, 15 relations | 8 types, 15 relations | | **Code extraction** | - | Extract entities from code snippets | | **Document extraction** | - | Extract from READMEs, changelogs, docs | | **Prose extraction** | - | Extract characters, locations from literature | | Training examples | 612 | ~1,000 | | S1 Intent accuracy | 78% | 92%+ (target) | ### Why intent matters v1 benchmarks showed **78% intent accuracy** — the model classified overview questions as specific (6 out of 9 failures). This cascaded: wrong intent led to wrong retrieval strategy (56% S2 accuracy) and wrong budget allocation (32% S3 accuracy). v2 trains the model to classify intent directly, replacing the external regex classifier. ### Why retrieval matters The `retrieval` field tells the system whether to fetch full document chunks or just use node summaries: - `summary_only` — for overview questions, chat, conceptual queries (cheaper, faster) - `with_chunks` — for code lookups, specific facts, detailed analysis (needs raw content) ## Output schema ### v1 output (deprecated) ```json { "topic": {"action": "same|changed|subtopic", "label": "..."}, "entities": [...], "relations": [...], "facts": [...] } ``` ### v2 output (new fields highlighted) ```json { "intent": "overview|specific|chat|followup", // NEW "topic": {"action": "same|changed|subtopic", "label": "..."}, "retrieval": "summary_only|with_chunks", // NEW "entities": [...], "relations": [...], "facts": [...] } ``` ## Intent types | Intent | Description | Examples | |---|---|---| | `overview` | High-level summary, counts, listings, general info | "What is this project?", "How many files?", "Give me a summary" | | `specific` | Precise detail, specific code, particular fact | "How does auth work?", "Show me the controller", "What's the deadline?" | | `chat` | Casual conversation, acknowledgments, opinions | "Thanks", "That's interesting", "Ok", "Good job" | | `followup` | Continuing previous topic with more depth | "Tell me more", "What about the other one?", "Expand on that" | ## Examples ### Intent: overview ``` USER: What is this project about? ``` ```json { "intent": "overview", "topic": {"action": "same", "label": null}, "retrieval": "summary_only", "entities": [], "relations": [], "facts": [] } ``` ### Intent: specific (with extraction) ``` USER: Beacon ya tiene 50 mil usuarios y estamos migrando a Kubernetes. ``` ```json { "intent": "specific", "topic": {"action": "same", "label": null}, "retrieval": "with_chunks", "entities": [ { "id": "kubernetes", "label": "Kubernetes", "type": "technology", "layer": "UNIVERSAL", "attributes": {}, "facts": [], "existing_id": null } ], "relations": [ {"source": "beacon", "target": "kubernetes", "relation": "uses_technology"} ], "facts": [ {"entity": "beacon", "text": "Has 50,000 users", "speaker": "user"} ] } ``` ### Intent: chat (empty output) ``` USER: That's interesting, thanks! ``` ```json { "intent": "chat", "topic": {"action": "same", "label": null}, "retrieval": "summary_only", "entities": [], "relations": [], "facts": [] } ``` ### Intent: followup ``` PREVIOUS ASSISTANT: The auth module uses JWT tokens with 24-hour expiry. USER: Tell me more about that. ``` ```json { "intent": "followup", "topic": {"action": "same", "label": null}, "retrieval": "with_chunks", "entities": [], "relations": [], "facts": [] } ``` ## Key capabilities | Capability | Description | |---|---| | **Intent classification** | Classifies user intent to drive retrieval strategy | | **Retrieval decision** | Decides summary_only vs with_chunks for downstream pipeline | | **Bilingual** | Handles English and Spanish input natively | | **Empty output** | Returns empty arrays for small talk and pure queries (no hallucinated entities) | | **Dedup awareness** | References existing nodes via `existing_id` instead of creating duplicates | | **Code extraction** | Extracts technologies, patterns, and dependencies from code snippets | | **Document extraction** | Extracts entities from READMEs, changelogs, sprint reviews, API docs | | **Prose extraction** | Extracts characters, locations, events from literature and narratives | | **Controlled vocabulary** | Uses strict enums for types (8) and relations (15) | | **Topic detection** | Classifies same/subtopic/changed with optional hint from upstream classifiers | ## Training details | Parameter | Value | |---|---| | **Base model** | Qwen/Qwen3.5-9B | | **Method** | LoRA (QLoRA 4-bit, r=16, alpha=32) | | **Framework** | Unsloth + Transformers + TRL | | **Dataset size** | ~1,000 examples | | **Training** | v1 base (3 epochs, lr=2e-4) + v2 incremental (2 epochs, lr=5e-5) + v3 intent+retrieval (3 epochs, lr=5e-5) | | **Max sequence length** | 2048 | | **Languages** | English (~65%), Spanish (~35%) | | **Hardware** | NVIDIA RTX 5070 Ti (16GB VRAM) | ### Dataset composition | Category | Count | Description | |---|---|---| | Conversation extraction (v1) | 350 | Facts, entities, relations from conversations | | Topic detection (v1) | 120 | Topic changes, subtopics | | Empty output (v1) | 90 | Small talk, queries with no extraction | | Corrections / dedup (v1) | 52 | "We switched from React to Vue", existing references | | Stress / edge cases (v1) | 22 | Edge cases from v1 testing | | **Intent classification (v2)** | **100** | Overview, specific, chat, followup examples | | **Retrieval decision (v2)** | **80** | summary_only vs with_chunks | | **Code extraction (v2)** | **50** | TypeScript, Python, YAML, Docker, SQL | | **Literature extraction (v2)** | **40** | Characters, locations, events from prose | | **Documentation extraction (v2)** | **40** | READMEs, changelogs, sprint reviews, API docs | | **S1.5 improvement (v2)** | **30** | Extracting from assistant responses | | **S1 failure variations (v2)** | **50** | Variations of 9 v0.4 benchmark failures | ## Schema ### Entity types (enum) ``` person, organization, project, technology, place, event, document, concept ``` ### Relation types (enum) ``` part_of, created_by, maintains, works_at, member_of, uses_technology, depends_on, alternative_to, located_in, deployed_on, produces, serves, documented_in, participated_in, triggered_by, resulted_in ``` ### Layers - **PERSONAL** — user owns, created, or directly uses it - **UNIVERSAL** — public knowledge (technologies, fictional characters, cities) ## Usage ### With LM Studio / Ollama (GGUF) Download the GGUF file from the `gguf/` folder and load in LM Studio. The model appears as **acervo-extractor-v2**. ### With Transformers + LoRA ```python from peft import PeftModel from transformers import AutoModelForCausalLM, AutoTokenizer base_model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3.5-9B", device_map="auto") model = PeftModel.from_pretrained(base_model, "SandyVeliz/acervo-extractor-v2") tokenizer = AutoTokenizer.from_pretrained("SandyVeliz/acervo-extractor-v2") messages = [ {"role": "system", "content": "You are a knowledge extractor for a personal knowledge graph. Analyze the conversation and return a single JSON object with: intent, topic, retrieval, entities, relations, and facts.\n\nIntent — classify the user's intent:\n- \"overview\": user wants a high-level summary, project description, general information, counts, or listings.\n- \"specific\": user wants a precise detail, specific code, a particular fact, or a specific section.\n- \"chat\": casual conversation, greetings, acknowledgments, opinions, or thanks.\n- \"followup\": continuing the previous topic with more depth, \"tell me more\", or referencing something just discussed.\n\nRetrieval — decide what data the system should fetch:\n- \"summary_only\": the node summary is enough (overview, chat, conceptual questions).\n- \"with_chunks\": the user needs specific content from documents (code lookups, specific facts, detailed analysis).\n\nOutput valid JSON only, no markdown, no explanation."}, {"role": "user", "content": "EXISTING NODES:\n[]\n\nTOPIC HINT: unresolved\nCURRENT TOPIC: null\n\nPREVIOUS ASSISTANT: null\nUSER: I work at Acme Corp building a React app called Beacon with PostgreSQL."} ] inputs = tokenizer.apply_chat_template(messages, return_tensors="pt", add_generation_prompt=True) outputs = model.generate(inputs.to(model.device), max_new_tokens=1024, temperature=0.1) print(tokenizer.decode(outputs[0][inputs.shape[-1]:], skip_special_tokens=True)) ``` ### With Unsloth (recommended for inference) ```python from unsloth import FastLanguageModel model, tokenizer = FastLanguageModel.from_pretrained( "SandyVeliz/acervo-extractor-v2", max_seq_length=2048, load_in_4bit=True, ) FastLanguageModel.for_inference(model) ``` ### With Acervo (intended use) ```python from acervo import Acervo, OpenAIClient llm = OpenAIClient(base_url="http://localhost:1234/v1", model="acervo-extractor-v2") memory = Acervo(llm=llm, owner="user") ``` ## Intended use This model is designed as the extraction component inside [Acervo](https://github.com/SandyVeliz/acervo), a semantic compression layer for AI agents. It replaces general-purpose LLM calls for topic detection, intent classification, and entity extraction with a specialized, faster model. It can also be used standalone for: - Building knowledge graphs from conversations - Structured entity/relation extraction from text - Topic detection in multi-turn dialogues - Intent classification for conversational AI - Retrieval strategy decisions (RAG pipelines) ## Version history | Version | Repo | Examples | Key changes | |---|---|---|---| | v1 | [acervo-extractor-qwen3.5-9b](https://huggingface.co/SandyVeliz/acervo-extractor-qwen3.5-9b) | 612 | Topic detection + entity extraction | | **v2** | **acervo-extractor-v2** | **~1,000** | **+ Intent classification, retrieval decision, code/doc/prose extraction** | ## License Apache 2.0 — same as the base model.