From c3555e0cfda6540c96e254928263296ee1947b91 Mon Sep 17 00:00:00 2001 From: KaifAhmad1 Date: Sat, 27 Dec 2025 23:23:32 +0530 Subject: [PATCH] Add LLM providers module and GraphRAG reasoning features - Add semantica.llms module with Groq, OpenAI, HuggingFace, and LiteLLM providers - Add query_with_reasoning() method for multi-hop reasoning with LLM-generated responses - Update ContextRetriever and AgentContext with reasoning capabilities - Add comprehensive documentation for LLM providers and GraphRAG reasoning - Update README and docs with new features - Update notebook examples to use new query_with_reasoning() method --- README.md | 102 +++- .../01_Real_Time_Anomaly_Detection.ipynb | 466 ++++++++++++++---- docs/modules.md | 116 ++++- docs/reference/context.md | 134 +++++ docs/reference/llms.md | 277 +++++++++++ semantica/context/agent_context.py | 58 +++ semantica/context/context_retriever.py | 348 +++++++++++++ semantica/llms/__init__.py | 46 ++ semantica/llms/groq.py | 89 ++++ semantica/llms/huggingface.py | 100 ++++ semantica/llms/litellm.py | 189 +++++++ semantica/llms/openai.py | 89 ++++ 12 files changed, 1885 insertions(+), 129 deletions(-) create mode 100644 docs/reference/llms.md create mode 100644 semantica/llms/__init__.py create mode 100644 semantica/llms/groq.py create mode 100644 semantica/llms/huggingface.py create mode 100644 semantica/llms/litellm.py create mode 100644 semantica/llms/openai.py diff --git a/README.md b/README.md index aac30d3e..37067a1b 100644 --- a/README.md +++ b/README.md @@ -161,7 +161,7 @@ flowchart TD **Knowledge Graph Construction** — Production-ready graphs with entity resolution, temporal support, and graph analytics. Queryable knowledge ready for AI applications. -**GraphRAG Engine** — Hybrid vector + graph retrieval achieves 91% accuracy (30% improvement) via semantic search + graph traversal for multi-hop reasoning. [See Comparison Benchmark](cookbook/use_cases/advanced_rag/02_RAG_vs_GraphRAG_Comparison.ipynb) +**GraphRAG Engine** — Hybrid vector + graph retrieval achieves 91% accuracy (30% improvement) via semantic search + graph traversal for multi-hop reasoning. Features LLM-generated responses grounded in knowledge graph context with reasoning traces. [See Comparison Benchmark](cookbook/use_cases/advanced_rag/02_RAG_vs_GraphRAG_Comparison.ipynb) **AI Agent Context Engineering** — Persistent memory with RAG + knowledge graphs enables context maintenance, action validation, and structured knowledge access. @@ -179,7 +179,8 @@ flowchart TD | **Semantic Extraction** | NER, relationship extraction, triplet generation, LLM enhancement | Automated discovery of entities and relationships | | **Knowledge Graphs** | Entity resolution, temporal support, graph analytics, query interface | Production-ready, queryable knowledge structures | | **Ontology Generation** | 6-stage LLM pipeline, OWL generation, HermiT/Pellet validation | Automated ontology creation from documents | -| **GraphRAG** | Hybrid vector + graph retrieval, multi-hop reasoning | 91% accuracy, 30% improvement over vector-only | +| **GraphRAG** | Hybrid vector + graph retrieval, multi-hop reasoning, LLM-generated responses | 91% accuracy, 30% improvement over vector-only, reasoning traces | +| **LLM Providers** | Unified interface to 100+ LLMs (Groq, OpenAI, HuggingFace, LiteLLM) | Clean imports, multiple providers, structured output | | **Agent Memory** | Persistent memory (Save/Load), Hybrid Retrieval (Vector+Graph), FastEmbed support | Context-aware agents with semantic understanding | | **Pipeline Orchestration** | Parallel execution, custom steps, orchestrator-worker pattern | Scalable, flexible data processing | | **Quality Assurance** | Conflict detection, deduplication, quality scoring, provenance | Trusted knowledge graphs ready for production | @@ -322,8 +323,10 @@ pip install -e ".[dev]" | **Data Ingestion** | **Semantic Extract** | **Knowledge Graphs** | **Ontology** | |:--------------------:|:----------------------:|:----------------------:|:--------------:| | [Multiple Formats](#universal-data-ingestion) | [Entity & Relations](#semantic-intelligence-engine) | [Graph Analytics](#knowledge-graph-construction) | [Auto Generation](#ontology-generation--management) | -| **Context** | **GraphRAG** | **Pipeline** | **QA** | -| [Agent Memory](#context-engineering-for-ai-agents) | [Hybrid RAG](#knowledge-graph-powered-rag-graphrag) | [Parallel Workers](#pipeline-orchestration--parallel-processing) | [Conflict Resolution](#production-ready-quality-assurance) | +| **Context** | **GraphRAG** | **LLM Providers** | **Pipeline** | +| [Agent Memory](#context-engineering--memory-systems) | [Hybrid RAG](#knowledge-graph-powered-rag-graphrag) | [100+ LLMs](#llm-providers-module) | [Parallel Workers](#pipeline-orchestration--parallel-processing) | +| **QA** | **Reasoning** | | | +| [Conflict Resolution](#production-ready-quality-assurance) | [Rule-based Inference](#reasoning--inference-engine) | | | --- @@ -428,12 +431,13 @@ print(f"Classes: {len(ontology.classes)}") ### Context Engineering & Memory Systems -> **Persistent Memory** • **Hybrid Retrieval (Vector + Graph)** • **Production Graph Store (Neo4j)** • **Entity Linking** +> **Persistent Memory** • **Hybrid Retrieval (Vector + Graph)** • **Production Graph Store (Neo4j)** • **Entity Linking** • **Multi-Hop Reasoning** ```python from semantica.context import AgentContext from semantica.vector_store import VectorStore from semantica.graph_store import GraphStore +from semantica.llms import Groq # Initialize Context with Hybrid Retrieval (Graph + Vector) context = AgentContext( @@ -450,6 +454,14 @@ context.store( # Retrieve with context expansion results = context.retrieve("What is the user building?", use_graph_expansion=True) + +# Query with reasoning and LLM-generated responses +llm_provider = Groq(model="llama-3.1-8b-instant", api_key=os.getenv("GROQ_API_KEY")) +reasoned_result = context.query_with_reasoning( + query="What is the user building?", + llm_provider=llm_provider, + max_hops=2 +) ``` **Core Notebooks:** @@ -461,21 +473,89 @@ results = context.retrieve("What is the user building?", use_graph_expansion=Tru ### Knowledge Graph-Powered RAG (GraphRAG) -> **30% Accuracy Improvement** • Vector + Graph Hybrid Search • 91% Accuracy +> **30% Accuracy Improvement** • Vector + Graph Hybrid Search • 91% Accuracy • **Multi-Hop Reasoning** • **LLM-Generated Responses** ```python -from semantica.qa_rag import GraphRAGEngine +from semantica.context import AgentContext +from semantica.llms import Groq, OpenAI, LiteLLM from semantica.vector_store import VectorStore +import os -graphrag = GraphRAGEngine( +# Initialize GraphRAG with hybrid retrieval +context = AgentContext( vector_store=VectorStore(backend="faiss"), knowledge_graph=kg ) -result = graphrag.query("Who founded the company?", top_k=5, expand_graph=True) -print(f"Answer: {result.answer} (Confidence: {result.confidence:.2f})") + +# Configure LLM provider (supports Groq, OpenAI, HuggingFace, LiteLLM) +llm_provider = Groq( + model="llama-3.1-8b-instant", + api_key=os.getenv("GROQ_API_KEY") +) + +# Query with multi-hop reasoning and LLM-generated responses +result = context.query_with_reasoning( + query="What IPs are associated with security alerts?", + llm_provider=llm_provider, + max_results=10, + max_hops=2 +) + +print(f"Response: {result['response']}") +print(f"Reasoning Path: {result['reasoning_path']}") +print(f"Confidence: {result['confidence']:.3f}") ``` -[**Cookbook: GraphRAG**](https://github.com/Hawksight-AI/semantica/tree/main/cookbook/use_cases/advanced_rag/01_GraphRAG_Complete.ipynb) +**Key Features:** +- **Multi-Hop Reasoning**: Traverses knowledge graph up to N hops to find related entities +- **LLM-Generated Responses**: Natural language answers grounded in graph context +- **Reasoning Trace**: Shows entity relationship paths used in reasoning +- **Multiple LLM Providers**: Supports Groq, OpenAI, HuggingFace, and LiteLLM (100+ LLMs) + +[**Cookbook: GraphRAG**](https://github.com/Hawksight-AI/semantica/tree/main/cookbook/use_cases/advanced_rag/01_GraphRAG_Complete.ipynb) • [**Real-Time Anomaly Detection**](https://github.com/Hawksight-AI/semantica/tree/main/cookbook/use_cases/cybersecurity/01_Real_Time_Anomaly_Detection.ipynb) + +### LLM Providers Module + +> **Unified LLM Interface** • **100+ LLM Support via LiteLLM** • **Clean Imports** • **Multiple Providers** + +```python +from semantica.llms import Groq, OpenAI, HuggingFaceLLM, LiteLLM +import os + +# Groq - Fast inference +groq = Groq( + model="llama-3.1-8b-instant", + api_key=os.getenv("GROQ_API_KEY") +) +response = groq.generate("What is AI?") + +# OpenAI +openai = OpenAI( + model="gpt-4", + api_key=os.getenv("OPENAI_API_KEY") +) +response = openai.generate("What is AI?") + +# HuggingFace - Local models +hf = HuggingFaceLLM(model_name="gpt2") +response = hf.generate("What is AI?") + +# LiteLLM - Unified interface to 100+ LLMs +litellm = LiteLLM( + model="openai/gpt-4o", # or "anthropic/claude-sonnet-4-20250514", "groq/llama-3.1-8b-instant", etc. + api_key=os.getenv("OPENAI_API_KEY") +) +response = litellm.generate("What is AI?") + +# Structured output +structured = groq.generate_structured("Extract entities from: Apple Inc. was founded by Steve Jobs.") +``` + +**Supported Providers:** +- **Groq**: Fast inference with Llama models +- **OpenAI**: GPT-3.5, GPT-4, and other OpenAI models +- **HuggingFace**: Local LLM inference with Transformers +- **LiteLLM**: Unified interface to 100+ LLM providers (OpenAI, Anthropic, Azure, Bedrock, Vertex AI, and more) ### Reasoning & Inference Engine diff --git a/cookbook/use_cases/cybersecurity/01_Real_Time_Anomaly_Detection.ipynb b/cookbook/use_cases/cybersecurity/01_Real_Time_Anomaly_Detection.ipynb index d2303f59..481005c4 100644 --- a/cookbook/use_cases/cybersecurity/01_Real_Time_Anomaly_Detection.ipynb +++ b/cookbook/use_cases/cybersecurity/01_Real_Time_Anomaly_Detection.ipynb @@ -60,9 +60,32 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Note: you may need to restart the kernel to use updated packages.\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "WARNING: Ignoring invalid distribution ~gno (c:\\Users\\Mohd Kaif\\AppData\\Local\\Programs\\Python\\Python311\\Lib\\site-packages)\n", + "WARNING: Ignoring invalid distribution ~lotly (c:\\Users\\Mohd Kaif\\AppData\\Local\\Programs\\Python\\Python311\\Lib\\site-packages)\n", + "WARNING: Ignoring invalid distribution ~ython-socketio (c:\\Users\\Mohd Kaif\\AppData\\Local\\Programs\\Python\\Python311\\Lib\\site-packages)\n", + "WARNING: Ignoring invalid distribution ~gno (c:\\Users\\Mohd Kaif\\AppData\\Local\\Programs\\Python\\Python311\\Lib\\site-packages)\n", + "WARNING: Ignoring invalid distribution ~lotly (c:\\Users\\Mohd Kaif\\AppData\\Local\\Programs\\Python\\Python311\\Lib\\site-packages)\n", + "WARNING: Ignoring invalid distribution ~ython-socketio (c:\\Users\\Mohd Kaif\\AppData\\Local\\Programs\\Python\\Python311\\Lib\\site-packages)\n", + "WARNING: Ignoring invalid distribution ~gno (c:\\Users\\Mohd Kaif\\AppData\\Local\\Programs\\Python\\Python311\\Lib\\site-packages)\n", + "WARNING: Ignoring invalid distribution ~lotly (c:\\Users\\Mohd Kaif\\AppData\\Local\\Programs\\Python\\Python311\\Lib\\site-packages)\n", + "WARNING: Ignoring invalid distribution ~ython-socketio (c:\\Users\\Mohd Kaif\\AppData\\Local\\Programs\\Python\\Python311\\Lib\\site-packages)\n" + ] + } + ], "source": [ "%pip install -qU semantica networkx matplotlib plotly pandas faiss-cpu beautifulsoup4 groq sentence-transformers scikit-learn\n" ] @@ -76,13 +99,13 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "import os\n", "\n", - "os.environ[\"GROQ_API_KEY\"] = os.getenv(\"GROQ_API_KEY\", \"your-key-here\")\n", + "os.environ[\"GROQ_API_KEY\"] = os.getenv(\"GROQ_API_KEY\", \"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\")\n", "\n", "# Configuration constants\n", "EMBEDDING_DIMENSION = 384\n", @@ -101,9 +124,40 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Ingesting from 5 feed sources...\n" + ] + }, + { + "data": { + "text/html": [ + "

🧠 Semantica - 📊 Current Progress

StatusActionModuleSubmoduleFileTime
Semantica is extracting🎯 semantic_extractNERExtractor-0.55s
Semantica is extracting🎯 semantic_extractRelationExtractor-0.05s
Semantica is resolving⚠️ conflictsConflictDetector-0.00s
Semantica is building🧠 kgGraphBuilder-350.71s
🔄Semantica is building🧠 kgEntityResolver-84.09s
Semantica is deduplicating🔄 deduplicationDuplicateDetector-0.08s
Semantica is deduplicating🔄 deduplicationSimilarityCalculator-0.02s
Semantica is deduplicating🔄 deduplicationEntityMerger-0.13s
Semantica is deduplicating🔄 deduplicationMergeStrategyManager-0.03s
Semantica is indexing📊 vector_storeVectorStore-0.01s
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + " [1/5] US-CERT Alerts: 10 documents\n", + " [2/5] SANS ISC: 10 documents\n", + " [3/5] Krebs on Security: 10 documents\n", + " [4/5] ThreatPost: 10 documents\n", + "Ingested 40 documents\n" + ] + } + ], "source": [ "from semantica.ingest import FeedIngestor, StreamIngestor, FileIngestor\n", "import os\n", @@ -169,9 +223,18 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Parsing 40 documents...\n", + " Parsed 40/40 documents...\n" + ] + } + ], "source": [ "from semantica.parse import DocumentParser\n", "\n", @@ -203,9 +266,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Normalizing 40 documents...\n", + " Normalized 40/40 documents...\n", + "Chunking 40 documents...\n", + " Chunked 40/40 documents (307 chunks so far)\n", + "Created 307 chunks from 40 documents\n" + ] + } + ], "source": [ "from semantica.normalize import TextNormalizer\n", "from semantica.split import TextSplitter\n", @@ -254,18 +329,47 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Extracting entities from 307 chunks...\n", + " Processed 20/307 chunks (6 entities found)\n", + " Processed 40/307 chunks (10 entities found)\n", + " Processed 60/307 chunks (15 entities found)\n", + " Processed 80/307 chunks (26 entities found)\n", + " Processed 100/307 chunks (61 entities found)\n", + " Processed 120/307 chunks (86 entities found)\n", + " Processed 140/307 chunks (98 entities found)\n", + " Processed 160/307 chunks (116 entities found)\n", + " Processed 180/307 chunks (144 entities found)\n", + " Processed 200/307 chunks (160 entities found)\n", + " Processed 220/307 chunks (198 entities found)\n", + " Processed 240/307 chunks (232 entities found)\n", + " Processed 260/307 chunks (247 entities found)\n", + " Processed 280/307 chunks (257 entities found)\n", + " Processed 300/307 chunks (269 entities found)\n", + " Processed 307/307 chunks (273 entities found)\n", + "Extracted 3 IPs, 28 users, 0 alerts\n" + ] + } + ], "source": [ "from semantica.semantic_extract import NERExtractor\n", "\n", - "entity_extractor = NERExtractor(\n", - " method=\"llm\",\n", - " provider=\"groq\",\n", - " llm_model=\"llama-3.1-8b-instant\",\n", - " temperature=0.0\n", - ")\n", + "security_patterns = {\n", + " \"IP\": r\"\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\b\",\n", + " \"User\": r\"\\buser\\s+([a-zA-Z0-9_\\-\\.]+)\\b\",\n", + " \"Alert\": r\"\\b(?:alert|warning|alarm):\\s*([^\\n\\.]+)|\\b(?:alert|warning|alarm)\\s+(?:detected|triggered|generated|raised)\\b\",\n", + " \"Event\": r\"\\b(?:login|access|connection|request|attempt|failed|successful|suspicious|unusual)\\s+(?:event|attempt|request|activity|access)\\b\",\n", + " \"Log\": r\"\\b\\d{4}-\\d{2}-\\d{2}\\s+\\d{2}:\\d{2}:\\d{2}\\s+-\\s+([^\\n]+)\",\n", + " \"Attack\": r\"\\b(?:attack|breach|intrusion|exploit|malware|virus|ransomware|phishing|brute\\s+force|ddos)\\b\",\n", + "}\n", + "\n", + "entity_extractor = NERExtractor(method=\"regex\", patterns=security_patterns)\n", "\n", "all_entities = []\n", "print(f\"Extracting entities from {len(chunked_documents)} chunks...\")\n", @@ -299,30 +403,69 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Extracting relationships from 307 chunks using 267 filtered entities...\n", + " Processed 20/307 chunks (534 relationships found)\n", + " Processed 40/307 chunks (534 relationships found)\n", + " Processed 60/307 chunks (534 relationships found)\n", + " Processed 80/307 chunks (534 relationships found)\n", + " Processed 100/307 chunks (534 relationships found)\n", + " Processed 120/307 chunks (534 relationships found)\n", + " Processed 140/307 chunks (534 relationships found)\n", + " Processed 160/307 chunks (534 relationships found)\n", + " Processed 180/307 chunks (534 relationships found)\n", + " Processed 200/307 chunks (534 relationships found)\n", + " Processed 220/307 chunks (534 relationships found)\n", + " Processed 240/307 chunks (534 relationships found)\n", + " Processed 260/307 chunks (534 relationships found)\n", + " Processed 280/307 chunks (534 relationships found)\n", + " Processed 300/307 chunks (534 relationships found)\n", + " Processed 307/307 chunks (534 relationships found)\n", + "Extracted 534 relationships\n" + ] + } + ], "source": [ "from semantica.semantic_extract import RelationExtractor\n", "\n", + "# Filter entities to only meaningful security entities\n", + "filtered_entities = [\n", + " e for e in all_entities \n", + " if e.label in [\"IP\", \"User\", \"Alert\", \"Attack\", \"Event\", \"Log\"] \n", + " and len(e.text) > 2\n", + " and e.text.lower() not in [\"to\", \"from\", \"should\", \"would\", \"choices\", \"connects\"]\n", + "]\n", + "\n", "relation_extractor = RelationExtractor(\n", - " method=\"llm\",\n", - " provider=\"groq\",\n", - " llm_model=\"llama-3.1-8b-instant\",\n", - " temperature=0.0\n", + " method=\"cooccurrence\",\n", + " max_distance=60,\n", + " confidence_threshold=0.6\n", ")\n", "\n", + "# Deduplicate relationships\n", + "seen_relationships = set()\n", "all_relationships = []\n", - "print(f\"Extracting relationships from {len(chunked_documents)} chunks...\")\n", + "print(f\"Extracting relationships from {len(chunked_documents)} chunks using {len(filtered_entities)} filtered entities...\")\n", "for i, chunk in enumerate(chunked_documents, 1):\n", " chunk_text = chunk.text if hasattr(chunk, 'text') else str(chunk)\n", " try:\n", " relationships = relation_extractor.extract_relations(\n", " chunk_text,\n", - " entities=all_entities,\n", + " entities=filtered_entities,\n", " relation_types=[\"from\", \"attempts\", \"triggers\", \"detects\", \"associated_with\", \"causes\"]\n", " )\n", - " all_relationships.extend(relationships)\n", + " # Deduplicate based on subject, predicate, object\n", + " for rel in relationships:\n", + " rel_key = (rel.subject.text, rel.predicate, rel.object.text)\n", + " if rel_key not in seen_relationships:\n", + " seen_relationships.add(rel_key)\n", + " all_relationships.append(rel)\n", " except Exception:\n", " continue\n", " \n", @@ -336,72 +479,88 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Resolving Duplicate Events\n" + "## Detecting Security Conflicts\n", + "\n", + "- **Using entity-wide conflict detection** to identify all types of conflicts (value, type, relationship, temporal) across security entities from multiple sources. \n", + "- **Voting resolution strategy** selects the majority consensus value, ensuring reliability through multi-source agreement for security event data.\n" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 21, "metadata": {}, - "outputs": [], - "source": [ - "from semantica.kg import EntityResolver\n", - "from semantica.semantic_extract import Entity\n", - "\n", - "# Convert Entity objects to dictionaries for EntityResolver\n", - "print(f\"Converting {len(all_entities)} entities to dictionaries...\")\n", - "entity_dicts = [{\"name\": e.text, \"type\": e.label, \"confidence\": e.confidence} for e in all_entities]\n", - "\n", - "# Use EntityResolver class to resolve duplicates\n", - "entity_resolver = EntityResolver(strategy=\"fuzzy\", similarity_threshold=0.85)\n", - "\n", - "print(f\"Resolving duplicates in {len(entity_dicts)} entities...\")\n", - "resolved_entities = entity_resolver.resolve_entities(entity_dicts)\n", - "\n", - "# Convert back to Entity objects\n", - "print(f\"Converting {len(resolved_entities)} resolved entities back to Entity objects...\")\n", - "merged_entities = [\n", - " Entity(text=e[\"name\"], label=e[\"type\"], confidence=e.get(\"confidence\", 1.0))\n", - " for e in resolved_entities\n", - "]\n", - "\n", - "print(f\"Deduplicated {len(entity_dicts)} entities to {len(merged_entities)} unique entities\")\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Detecting Security Conflicts\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Detecting conflicts in 273 entities and 534 relationships...\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Detected 0 conflicts (0 entity, 0 relationship)\n", + "No conflicts detected\n" + ] + } + ], "source": [ "from semantica.conflicts import ConflictDetector, ConflictResolver\n", "\n", - "# Use entity conflict detection for conflicting security event attributes\n", - "# first_seen strategy prioritizes the initial reported event in real-time streams\n", "conflict_detector = ConflictDetector()\n", "conflict_resolver = ConflictResolver()\n", "\n", - "print(f\"Detecting entity conflicts in {len(merged_entities)} entities...\")\n", - "conflicts = conflict_detector.detect_conflicts(\n", - " entities=merged_entities,\n", - " relationships=all_relationships,\n", - " method=\"entity\" # Detect conflicts in entity attributes\n", - ")\n", + "# Convert entities to dictionaries for conflict detection\n", + "entity_dicts = [\n", + " {\n", + " \"id\": e.text,\n", + " \"text\": e.text,\n", + " \"label\": e.label,\n", + " \"type\": e.label,\n", + " \"confidence\": e.confidence if hasattr(e, 'confidence') else 1.0,\n", + " \"metadata\": e.metadata if hasattr(e, 'metadata') else {}\n", + " }\n", + " for e in all_entities\n", + "]\n", "\n", - "print(f\"Detected {len(conflicts)} entity conflicts\")\n", + "# Convert relationships to dictionaries for conflict detection\n", + "relationship_dicts = [\n", + " {\n", + " \"id\": f\"{r.subject.text}_{r.predicate}_{r.object.text}\",\n", + " \"source_id\": r.subject.text,\n", + " \"target_id\": r.object.text,\n", + " \"type\": r.predicate,\n", + " \"subject\": r.subject.text,\n", + " \"object\": r.object.text,\n", + " \"predicate\": r.predicate,\n", + " \"confidence\": r.confidence if hasattr(r, 'confidence') else 1.0\n", + " }\n", + " for r in all_relationships\n", + "]\n", "\n", - "if conflicts:\n", - " print(f\"Resolving conflicts using first_seen strategy...\")\n", + "print(f\"Detecting conflicts in {len(entity_dicts)} entities and {len(relationship_dicts)} relationships...\")\n", + "\n", + "# Detect entity conflicts (value, type, temporal)\n", + "value_conflicts = conflict_detector.detect_value_conflicts(entity_dicts, property_name=\"label\")\n", + "type_conflicts = conflict_detector.detect_type_conflicts(entity_dicts)\n", + "temporal_conflicts = conflict_detector.detect_temporal_conflicts(entity_dicts)\n", + "entity_conflicts = value_conflicts + type_conflicts + temporal_conflicts\n", + "\n", + "# Detect relationship conflicts\n", + "relationship_conflicts = conflict_detector.detect_relationship_conflicts(relationship_dicts)\n", + "\n", + "# Combine all conflicts\n", + "all_conflicts = entity_conflicts + relationship_conflicts\n", + "\n", + "print(f\"Detected {len(all_conflicts)} conflicts ({len(entity_conflicts)} entity, {len(relationship_conflicts)} relationship)\")\n", + "\n", + "if all_conflicts:\n", + " print(f\"Resolving conflicts using voting strategy...\")\n", " resolved = conflict_resolver.resolve_conflicts(\n", - " conflicts,\n", - " strategy=\"first_seen\" # Prioritize the first seen event in real-time\n", + " all_conflicts,\n", + " strategy=\"voting\"\n", " )\n", " print(f\"Resolved {len(resolved)} conflicts\")\n", "else:\n", @@ -417,9 +576,39 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 23, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Building knowledge graph...\n", + "Processing 273 entities, 534 relationships (807 total)...\n", + " Entities: 100/273 (36.6%) | ETA: 3.5m | Rate: 0.8/s\n", + " Entities: 200/273 (73.3%) | ETA: 1.5m | Rate: 0.8/s\n", + " Entities: 273/273 (100.0%) | ETA: 0.0s | Rate: 0.8/s\n", + " Relationships: 100/534\n", + " Relationships: 200/534\n", + " Relationships: 300/534\n", + " Relationships: 400/534\n", + " Relationships: 500/534\n", + " Relationships: 534/534\n", + "Resolving 30 entities...\n", + "✅ Resolved to 3 unique entities (8.32s)\n", + "Building graph structure...\n", + "✅ Graph structure built (0.00s)\n", + "\n", + "============================================================\n", + "✅ Knowledge Graph Build Complete\n", + " Entities: 3\n", + " Relationships: 534\n", + " Total time: 350.71s\n", + "============================================================\n", + "Graph: 3 entities, 534 relationships\n" + ] + } + ], "source": [ "from semantica.kg import GraphBuilder\n", "\n", @@ -433,8 +622,8 @@ "\n", "print(f\"Building knowledge graph...\")\n", "kg_sources = [{\n", - " \"entities\": [{\"text\": e.text, \"type\": e.label, \"confidence\": e.confidence} for e in merged_entities],\n", - " \"relationships\": [{\"source\": r.source, \"target\": r.target, \"type\": r.label, \"confidence\": r.confidence} for r in all_relationships]\n", + " \"entities\": [{\"text\": e.text, \"type\": e.label, \"confidence\": e.confidence if hasattr(e, 'confidence') else 1.0} for e in all_entities],\n", + " \"relationships\": [{\"source\": r.subject.text, \"target\": r.object.text, \"type\": r.predicate, \"confidence\": r.confidence if hasattr(r, 'confidence') else 1.0} for r in all_relationships]\n", "}]\n", "\n", "kg = graph_builder.build(kg_sources)\n", @@ -453,9 +642,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 24, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "fastembed not available. Install with: pip install fastembed. Using fallback embedding method.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Generating embeddings for 6 events and 3 IPs...\n", + "Generated 6 event embeddings and 3 IP embeddings\n" + ] + } + ], "source": [ "from semantica.embeddings import EmbeddingGenerator\n", "\n", @@ -484,9 +689,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 25, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "fastembed not available. Install with: pip install fastembed. Using fallback embedding method.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Storing 6 event vectors and 3 IP vectors...\n", + "Stored 6 event vectors and 3 IP vectors\n" + ] + } + ], "source": [ "from semantica.vector_store import VectorStore\n", "\n", @@ -515,9 +736,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 26, "metadata": {}, - "outputs": [], + "outputs": [ + { + "ename": "AttributeError", + "evalue": "'TemporalGraphQuery' object has no attribute 'detect_temporal_patterns'", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mAttributeError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[1;32mIn[26], line 15\u001b[0m\n\u001b[0;32m 8\u001b[0m query_results \u001b[38;5;241m=\u001b[39m temporal_query\u001b[38;5;241m.\u001b[39mquery_at_time(\n\u001b[0;32m 9\u001b[0m kg,\n\u001b[0;32m 10\u001b[0m query\u001b[38;5;241m=\u001b[39m{\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mtype\u001b[39m\u001b[38;5;124m\"\u001b[39m: \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mAlert\u001b[39m\u001b[38;5;124m\"\u001b[39m},\n\u001b[0;32m 11\u001b[0m at_time\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m2024-01-01 10:04:00\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m 12\u001b[0m )\n\u001b[0;32m 14\u001b[0m evolution \u001b[38;5;241m=\u001b[39m temporal_query\u001b[38;5;241m.\u001b[39manalyze_evolution(kg)\n\u001b[1;32m---> 15\u001b[0m temporal_patterns \u001b[38;5;241m=\u001b[39m \u001b[43mtemporal_query\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdetect_temporal_patterns\u001b[49m(kg, pattern_type\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124msequence\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m 17\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mTemporal queries: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mlen\u001b[39m(query_results)\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m alerts at query time\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m 18\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mTemporal patterns detected: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mlen\u001b[39m(temporal_patterns)\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n", + "\u001b[1;31mAttributeError\u001b[0m: 'TemporalGraphQuery' object has no attribute 'detect_temporal_patterns'" + ] + } + ], "source": [ "from semantica.kg import TemporalGraphQuery\n", "\n", @@ -656,27 +889,34 @@ "outputs": [], "source": [ "from semantica.context import AgentContext\n", + "from semantica.llms import Groq\n", + "import os\n", "\n", "context = AgentContext(vector_store=vector_store, knowledge_graph=kg)\n", "\n", - "query = \"What IPs are associated with security alerts?\"\n", - "results = context.retrieve(\n", - " query,\n", - " max_results=10,\n", - " use_graph=True,\n", - " expand_graph=True,\n", - " include_entities=True,\n", - " include_relationships=True\n", + "# Initialize LLM provider\n", + "llm_provider = Groq(\n", + " model=\"llama-3.1-8b-instant\",\n", + " api_key=os.getenv(\"GROQ_API_KEY\")\n", ")\n", "\n", - "print(f\"GraphRAG query: '{query}'\")\n", - "print(f\"\\nRetrieved {len(results)} results:\\n\")\n", - "for i, result in enumerate(results[:5], 1):\n", - " print(f\"{i}. Score: {result.get('score', 0):.3f}\")\n", - " print(f\" Content: {result.get('content', '')[:200]}...\")\n", - " if result.get('related_entities'):\n", - " print(f\" Related entities: {len(result['related_entities'])}\")\n", - " print()\n" + "query = \"What IPs are associated with security alerts?\"\n", + "result = context.query_with_reasoning(\n", + " query=query,\n", + " llm_provider=llm_provider,\n", + " max_results=10,\n", + " max_hops=2\n", + ")\n", + "\n", + "print(f\"GraphRAG Query with Reasoning: '{query}'\\n\")\n", + "print(\"=\" * 80)\n", + "print(f\"\\nGenerated Response:\\n{result['response']}\\n\")\n", + "print(\"=\" * 80)\n", + "if result.get('reasoning_path'):\n", + " print(f\"\\nReasoning Path:\\n{result['reasoning_path']}\\n\")\n", + "print(f\"Confidence: {result.get('confidence', 0):.3f}\")\n", + "print(f\"Sources Used: {result.get('num_sources', 0)}\")\n", + "print(f\"Reasoning Paths Found: {result.get('num_reasoning_paths', 0)}\")\n" ] }, { @@ -730,8 +970,22 @@ } ], "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.9" } }, "nbformat": 4, diff --git a/docs/modules.md b/docs/modules.md index 2352cbb4..581222d1 100644 --- a/docs/modules.md +++ b/docs/modules.md @@ -17,7 +17,7 @@ Semantica's modules are organized into six logical layers: | **Core Processing** | [Semantic Extract](#semantic-extract-module), [Knowledge Graph](#knowledge-graph-kg-module), [Ontology](#ontology-module), [Reasoning](#reasoning-module) | Entity extraction, graph construction, inference | | **Storage** | [Embeddings](#embeddings-module), [Vector Store](#vector-store-module), [Graph Store](#graph-store-module), [Triplet Store](#triplet-store-module) | Vector, graph, and triplet persistence | | **Quality Assurance** | [Deduplication](#deduplication-module), [Conflicts](#conflicts-module) | Data quality and consistency | -| **Context & Memory** | [Context](#context-module), [Seed](#seed-module) | Agent memory and foundation data | +| **Context & Memory** | [Context](#context-module), [Seed](#seed-module), [LLM Providers](#llm-providers-module) | Agent memory, foundation data, and LLM integration | | **Output & Orchestration** | [Export](#export-module), [Visualization](#visualization-module), [Pipeline](#pipeline-module) | Export, visualization, and workflow management | --- @@ -778,7 +778,7 @@ These modules provide context engineering for agents and foundation data managem ### Context Module !!! abstract "Purpose" - Context engineering infrastructure for agents. Formalizes context as a graph of connections with RAG-enhanced memory. + Context engineering infrastructure for agents. Formalizes context as a graph of connections with RAG-enhanced memory. Features GraphRAG with multi-hop reasoning and LLM-generated responses. **Key Features:** @@ -786,6 +786,9 @@ These modules provide context engineering for agents and foundation data managem - Agent memory management with RAG integration - Entity linking across sources with URI assignment - Hybrid context retrieval (vector + graph + memory) +- **Multi-hop reasoning** through knowledge graphs +- **LLM-generated responses** grounded in graph context +- **Reasoning trace** showing entity relationship paths - Conversation history management - Context accumulation and synthesis - Graph-based context traversal @@ -796,9 +799,10 @@ These modules provide context engineering for agents and foundation data managem - `ContextNode` — Context graph node data structure - `ContextEdge` — Context graph edge data structure - `AgentMemory` — Manages persistent agent memory with RAG +- `AgentContext` — High-level context interface with GraphRAG capabilities +- `ContextRetriever` — Retrieves relevant context with multi-hop reasoning - `MemoryItem` — Memory item data structure - `EntityLinker` — Links entities across sources with URI assignment -- `ContextRetriever` — Retrieves relevant context from multiple sources **Algorithms:** @@ -807,22 +811,43 @@ These modules provide context engineering for agents and foundation data managem | **Graph Construction** | BFS/DFS traversal, type-based indexing | | **Memory Management** | Vector embedding, similarity search, retention policies | | **Context Retrieval** | Vector similarity, multi-hop graph expansion, hybrid scoring | +| **Multi-Hop Reasoning** | BFS traversal up to N hops, reasoning path construction | +| **LLM Integration** | Prompt engineering with context and reasoning paths | | **Entity Linking** | Hash-based URI generation, text similarity matching | **Quick Example:** ```python -from semantica.context import ContextGraph, AgentMemory -from semantica.context.methods import build_context_graph +from semantica.context import AgentContext, ContextGraph, AgentMemory +from semantica.llms import Groq +from semantica.vector_store import VectorStore +import os -# Using convenience function -result = build_context_graph( - entities=entities, - relationships=relationships, - method="entities_relationships" +# Using AgentContext with GraphRAG reasoning +context = AgentContext( + vector_store=VectorStore(backend="faiss"), + knowledge_graph=kg ) -# Using classes directly +# Configure LLM provider +llm_provider = Groq( + model="llama-3.1-8b-instant", + api_key=os.getenv("GROQ_API_KEY") +) + +# Query with multi-hop reasoning and LLM-generated response +result = context.query_with_reasoning( + query="What IPs are associated with security alerts?", + llm_provider=llm_provider, + max_results=10, + max_hops=2 +) + +print(f"Response: {result['response']}") +print(f"Reasoning Path: {result['reasoning_path']}") +print(f"Confidence: {result['confidence']:.3f}") + +# Traditional context graph and memory graph = ContextGraph() graph_data = graph.build_from_entities_and_relationships(entities, relationships) @@ -831,6 +856,72 @@ memory_id = memory.store("User asked about Python", metadata={"type": "conversat results = memory.retrieve("Python", max_results=5) ``` +**API Reference**: [Context Module](reference/context.md) + +--- + +### LLM Providers Module + +!!! abstract "Purpose" + Unified interface for LLM providers. Supports Groq, OpenAI, HuggingFace, and LiteLLM (100+ LLMs) with clean imports and consistent API. + +**Key Features:** + +- **Unified Interface**: Same `generate()` and `generate_structured()` methods across all providers +- **Multiple Providers**: Groq, OpenAI, HuggingFace, and LiteLLM (100+ LLMs) +- **Clean Imports**: Simple `from semantica.llms import Groq, OpenAI, HuggingFaceLLM, LiteLLM` +- **Structured Output**: JSON generation support +- **API Key Management**: Environment variable and direct key support +- **Error Handling**: Graceful fallback when providers unavailable + +**Components:** + +- `Groq` — Groq API provider for fast inference +- `OpenAI` — OpenAI API provider (GPT-3.5, GPT-4, etc.) +- `HuggingFaceLLM` — HuggingFace Transformers for local LLM inference +- `LiteLLM` — Unified interface to 100+ LLM providers (OpenAI, Anthropic, Azure, Bedrock, Vertex AI, etc.) + +**Supported Providers via LiteLLM:** + +- OpenAI, Anthropic, Groq, Azure, Bedrock, Vertex AI, Cohere, Mistral, and 90+ more + +**Quick Example:** + +```python +from semantica.llms import Groq, OpenAI, HuggingFaceLLM, LiteLLM +import os + +# Groq - Fast inference +groq = Groq( + model="llama-3.1-8b-instant", + api_key=os.getenv("GROQ_API_KEY") +) +response = groq.generate("What is AI?") + +# OpenAI +openai = OpenAI( + model="gpt-4", + api_key=os.getenv("OPENAI_API_KEY") +) +response = openai.generate("What is AI?") + +# HuggingFace - Local models +hf = HuggingFaceLLM(model_name="gpt2") # or model="gpt2" for consistency +response = hf.generate("What is AI?") + +# LiteLLM - Unified interface to 100+ LLMs +litellm = LiteLLM( + model="openai/gpt-4o", # or "anthropic/claude-sonnet-4-20250514", etc. + api_key=os.getenv("OPENAI_API_KEY") +) +response = litellm.generate("What is AI?") + +# Structured output +structured = groq.generate_structured("Extract entities from: Apple Inc. was founded by Steve Jobs.") +``` + +**API Reference**: [LLM Providers Module](reference/llms.md) + --- ### Seed Module @@ -1115,7 +1206,8 @@ new_facts = reasoner.infer_facts(kg) | **Triplet Store** | `semantica.triplet_store` | `TripletStore` | RDF storage | | **Deduplication** | `semantica.deduplication` | `DuplicateDetector` | Duplicate removal | | **Conflicts** | `semantica.conflicts` | `ConflictDetector` | Conflict resolution | -| **Context** | `semantica.context` | `AgentMemory` | Agent context | +| **Context** | `semantica.context` | `AgentContext` | Agent context & GraphRAG | +| **LLM Providers** | `semantica.llms` | `Groq`, `OpenAI`, `HuggingFaceLLM`, `LiteLLM` | LLM integration | | **Seed** | `semantica.seed` | `SeedDataManager` | Foundation data | | **Export** | `semantica.export` | `JSONExporter` | Data export | | **Visualization** | `semantica.visualization` | `KGVisualizer` | Visualization | diff --git a/docs/reference/context.md b/docs/reference/context.md index 55e9a832..0a02353c 100644 --- a/docs/reference/context.md +++ b/docs/reference/context.md @@ -70,6 +70,7 @@ The high-level facade that unifies all context operations. It routes data to the |--------|-------------| | `store(content, ...)` | Writes information to memory. Handles auto-detection, write-through to vector store, and entity extraction. | | `retrieve(query, ...)` | Fetches relevant context using hybrid search (Vector + Graph) and reranking. | +| `query_with_reasoning(query, llm_provider, ...)` | **GraphRAG with multi-hop reasoning**: Retrieves context, builds reasoning paths, and generates LLM-based natural language responses grounded in the knowledge graph. | #### **Code Example** ```python @@ -92,6 +93,26 @@ context.store( # 3. Retrieve Context results = context.retrieve("What is the user building?") + +# 4. Query with Reasoning (GraphRAG) +from semantica.llms import Groq +import os + +llm_provider = Groq( + model="llama-3.1-8b-instant", + api_key=os.getenv("GROQ_API_KEY") +) + +result = context.query_with_reasoning( + query="What IPs are associated with security alerts?", + llm_provider=llm_provider, + max_results=10, + max_hops=2 +) + +print(f"Response: {result['response']}") +print(f"Reasoning Path: {result['reasoning_path']}") +print(f"Confidence: {result['confidence']:.3f}") ``` --- @@ -254,6 +275,119 @@ results = retriever.retrieve( --- +### GraphRAG with Multi-Hop Reasoning + +The `query_with_reasoning()` method extends traditional retrieval by performing multi-hop graph traversal and generating natural language responses using LLMs. This enables deeper understanding of relationships and context-aware answer generation. + +#### **How It Works** + +1. **Context Retrieval**: Retrieves relevant context using hybrid search (vector + graph) +2. **Entity Extraction**: Extracts entities from query and retrieved context +3. **Multi-Hop Reasoning**: Traverses knowledge graph up to N hops to find related entities +4. **Reasoning Path Construction**: Builds reasoning chains showing entity relationships +5. **LLM Response Generation**: Generates natural language response grounded in graph context + +#### **Key Features** + +- **Multi-Hop Reasoning**: Traverses graph up to configurable hops (default: 2) +- **Reasoning Trace**: Shows entity relationship paths used in reasoning +- **Grounded Responses**: LLM generates answers citing specific graph entities +- **Multiple LLM Providers**: Supports Groq, OpenAI, HuggingFace, and LiteLLM (100+ LLMs) +- **Fallback Handling**: Returns context with reasoning path if LLM unavailable + +#### **Method Signature** + +```python +def query_with_reasoning( + self, + query: str, + llm_provider: Any, # LLM provider from semantica.llms + max_results: int = 10, + max_hops: int = 2, + **kwargs +) -> Dict[str, Any]: +``` + +**Parameters:** +- `query` (str): User query +- `llm_provider`: LLM provider instance (from `semantica.llms`) +- `max_results` (int): Maximum context results to retrieve (default: 10) +- `max_hops` (int): Maximum graph traversal hops (default: 2) +- `**kwargs`: Additional retrieval options + +**Returns:** +- `response` (str): Generated natural language answer +- `reasoning_path` (str): Multi-hop reasoning trace +- `sources` (List[Dict]): Retrieved context items used +- `confidence` (float): Overall confidence score +- `num_sources` (int): Number of sources retrieved +- `num_reasoning_paths` (int): Number of reasoning paths found + +#### **Code Example** + +```python +from semantica.context import AgentContext +from semantica.llms import Groq +from semantica.vector_store import VectorStore +import os + +# Initialize context +context = AgentContext( + vector_store=VectorStore(backend="faiss"), + knowledge_graph=kg +) + +# Configure LLM provider +llm_provider = Groq( + model="llama-3.1-8b-instant", + api_key=os.getenv("GROQ_API_KEY") +) + +# Query with reasoning +result = context.query_with_reasoning( + query="What IPs are associated with security alerts?", + llm_provider=llm_provider, + max_results=10, + max_hops=2 +) + +# Access results +print(f"Response: {result['response']}") +print(f"\nReasoning Path: {result['reasoning_path']}") +print(f"Confidence: {result['confidence']:.3f}") +``` + +#### **Using Different LLM Providers** + +```python +# Groq +from semantica.llms import Groq +llm = Groq(model="llama-3.1-8b-instant", api_key=os.getenv("GROQ_API_KEY")) + +# OpenAI +from semantica.llms import OpenAI +llm = OpenAI(model="gpt-4", api_key=os.getenv("OPENAI_API_KEY")) + +# LiteLLM (100+ providers) +from semantica.llms import LiteLLM +llm = LiteLLM(model="anthropic/claude-sonnet-4-20250514") + +# Use with query_with_reasoning +result = context.query_with_reasoning( + query="Your question here", + llm_provider=llm, + max_hops=3 +) +``` + +!!! tip "When to Use" + - **Complex Queries**: When simple retrieval doesn't capture relationships + - **Explainable AI**: When you need to show reasoning paths + - **Multi-Hop Questions**: "What IPs are associated with alerts that affect users?" + - **Grounded Responses**: When you need answers citing specific graph entities + +--- + ### EntityLinker (The Connector) Resolves text mentions to unique entities and assigns URIs. diff --git a/docs/reference/llms.md b/docs/reference/llms.md new file mode 100644 index 00000000..7579eaa5 --- /dev/null +++ b/docs/reference/llms.md @@ -0,0 +1,277 @@ +# LLM Providers Module + +The `semantica.llms` module provides a unified interface for LLM providers, supporting Groq, OpenAI, HuggingFace, and LiteLLM (100+ LLMs) with clean imports and consistent API. + +## Overview + +The LLM Providers module abstracts away provider-specific details, providing a consistent interface for text generation across multiple LLM providers. This enables easy switching between providers and integration with GraphRAG reasoning features. + +## Quick Start + +```python +from semantica.llms import Groq, OpenAI, HuggingFaceLLM, LiteLLM +import os + +# Groq - Fast inference +groq = Groq(model="llama-3.1-8b-instant", api_key=os.getenv("GROQ_API_KEY")) +response = groq.generate("What is AI?") + +# OpenAI +openai = OpenAI(model="gpt-4", api_key=os.getenv("OPENAI_API_KEY")) +response = openai.generate("What is AI?") + +# HuggingFace - Local models +hf = HuggingFaceLLM(model_name="gpt2") # or model="gpt2" +response = hf.generate("What is AI?") + +# LiteLLM - Unified interface to 100+ LLMs +litellm = LiteLLM(model="openai/gpt-4o", api_key=os.getenv("OPENAI_API_KEY")) +response = litellm.generate("What is AI?") +``` + +## Providers + +### Groq + +Fast inference provider using Groq's API. + +```python +from semantica.llms import Groq + +groq = Groq( + model="llama-3.1-8b-instant", + api_key="your-api-key" # or use GROQ_API_KEY env var +) + +response = groq.generate("Hello, world!") +structured = groq.generate_structured("Extract entities from: Apple Inc.") +``` + +**Parameters:** +- `model` (str): Model name (default: "llama-3.1-8b-instant") +- `api_key` (str, optional): Groq API key (default: from GROQ_API_KEY env var) +- `**kwargs`: Additional provider options + +**Methods:** +- `generate(prompt: str, **kwargs) -> str`: Generate text from prompt +- `generate_structured(prompt: str, **kwargs) -> Dict[str, Any]`: Generate structured JSON output +- `is_available() -> bool`: Check if provider is available + +### OpenAI + +OpenAI API provider for GPT models. + +```python +from semantica.llms import OpenAI + +openai = OpenAI( + model="gpt-4", + api_key="your-api-key" # or use OPENAI_API_KEY env var +) + +response = openai.generate("Hello, world!") +``` + +**Parameters:** +- `model` (str): Model name (default: "gpt-3.5-turbo") +- `api_key` (str, optional): OpenAI API key (default: from OPENAI_API_KEY env var) +- `**kwargs`: Additional provider options + +**Methods:** +- `generate(prompt: str, **kwargs) -> str`: Generate text from prompt +- `generate_structured(prompt: str, **kwargs) -> Dict[str, Any]`: Generate structured JSON output +- `is_available() -> bool`: Check if provider is available + +### HuggingFaceLLM + +Local LLM inference using HuggingFace Transformers. + +```python +from semantica.llms import HuggingFaceLLM + +hf = HuggingFaceLLM( + model_name="gpt2", + device="cuda" # or "cpu", default: auto-detect +) + +response = hf.generate("Hello, world!") +``` + +**Parameters:** +- `model_name` (str, optional): HuggingFace model name (default: "gpt2") +- `model` (str, optional): Alias for model_name (for consistency with other providers) +- `device` (str, optional): Device to use ("cuda" or "cpu", default: auto-detect) +- `**kwargs`: Additional provider options + +**Note:** Both `model` and `model_name` are supported for consistency with other providers. + +**Methods:** +- `generate(prompt: str, **kwargs) -> str`: Generate text from prompt +- `generate_structured(prompt: str, **kwargs) -> Dict[str, Any]`: Generate structured JSON output +- `is_available() -> bool`: Check if provider is available + +### LiteLLM + +Unified interface to 100+ LLM providers via LiteLLM library. + +```python +from semantica.llms import LiteLLM + +# Use any provider via LiteLLM +litellm = LiteLLM( + model="openai/gpt-4o", # Provider/model format + api_key=os.getenv("OPENAI_API_KEY") +) + +# Or use other providers +litellm = LiteLLM(model="anthropic/claude-sonnet-4-20250514") +litellm = LiteLLM(model="groq/llama-3.1-8b-instant") +litellm = LiteLLM(model="azure/gpt-4") + +response = litellm.generate("Hello, world!") +``` + +**Parameters:** +- `model` (str): Model identifier in format "provider/model-name" + - Examples: "openai/gpt-4o", "anthropic/claude-sonnet-4-20250514", "groq/llama-3.1-8b-instant", "azure/gpt-4" +- `api_key` (str, optional): API key (can use environment variables) +- `**kwargs`: Additional LiteLLM options (temperature, max_tokens, etc.) + +**Methods:** +- `generate(prompt: str, **kwargs) -> str`: Generate text from prompt +- `generate_structured(prompt: str, **kwargs) -> Dict[str, Any]`: Generate structured JSON output +- `is_available() -> bool`: Check if provider is available + +**Supported Providers:** +- OpenAI, Anthropic, Groq, Azure, Bedrock, Vertex AI, Cohere, Mistral, and 90+ more +- See [LiteLLM Documentation](https://docs.litellm.ai/) for full list + +## Integration with GraphRAG + +The LLM providers integrate seamlessly with GraphRAG reasoning: + +```python +from semantica.context import AgentContext +from semantica.llms import Groq +from semantica.vector_store import VectorStore +import os + +context = AgentContext( + vector_store=VectorStore(backend="faiss"), + knowledge_graph=kg +) + +llm_provider = Groq( + model="llama-3.1-8b-instant", + api_key=os.getenv("GROQ_API_KEY") +) + +result = context.query_with_reasoning( + query="What IPs are associated with security alerts?", + llm_provider=llm_provider, + max_hops=2 +) + +print(f"Response: {result['response']}") +print(f"Reasoning Path: {result['reasoning_path']}") +``` + +## Common Parameters + +All providers support common generation parameters: + +- `temperature` (float): Sampling temperature (0.0-2.0) +- `max_tokens` (int): Maximum tokens to generate +- `top_p` (float): Nucleus sampling parameter +- `frequency_penalty` (float): Frequency penalty +- `presence_penalty` (float): Presence penalty + +Example: + +```python +response = groq.generate( + "What is AI?", + temperature=0.7, + max_tokens=500, + top_p=0.9 +) +``` + +## Error Handling + +All providers gracefully handle errors: + +```python +try: + response = groq.generate("Hello") +except ProcessingError as e: + print(f"Generation failed: {e}") +``` + +If a provider is not available (library not installed, API key missing), a `ProcessingError` is raised with a helpful message. + +## Examples + +### Basic Text Generation + +```python +from semantica.llms import Groq + +groq = Groq(model="llama-3.1-8b-instant") +response = groq.generate("Explain quantum computing in simple terms.") +print(response) +``` + +### Structured Output + +```python +from semantica.llms import OpenAI + +openai = OpenAI(model="gpt-4") +result = openai.generate_structured( + "Extract entities from: Apple Inc. was founded by Steve Jobs in 1976." +) +# Returns: {"entities": [{"name": "Apple Inc.", "type": "Organization"}, ...]} +``` + +### Using LiteLLM for Multiple Providers + +```python +from semantica.llms import LiteLLM + +# Switch between providers easily +providers = [ + LiteLLM(model="openai/gpt-4o"), + LiteLLM(model="anthropic/claude-sonnet-4-20250514"), + LiteLLM(model="groq/llama-3.1-8b-instant") +] + +for provider in providers: + response = provider.generate("What is AI?") + print(f"{provider.model}: {response[:50]}...") +``` + +## Installation + +Most providers require additional dependencies: + +```bash +# Groq +pip install groq + +# OpenAI +pip install openai + +# HuggingFace +pip install transformers torch + +# LiteLLM (supports 100+ providers) +pip install litellm +``` + +## See Also + +- [Context Module](context.md) - GraphRAG with multi-hop reasoning +- [Semantic Extract Module](semantic_extract.md) - Entity and relationship extraction +- [GraphRAG Cookbook](../../cookbook/use_cases/advanced_rag/01_GraphRAG_Complete.ipynb) - Complete GraphRAG example + diff --git a/semantica/context/agent_context.py b/semantica/context/agent_context.py index cd6815a6..d04b43aa 100644 --- a/semantica/context/agent_context.py +++ b/semantica/context/agent_context.py @@ -426,6 +426,64 @@ class AgentContext: # Convert to dicts return [self._memory_to_dict(r) for r in results] + def query_with_reasoning( + self, + query: str, + llm_provider: Any, + max_results: int = 10, + max_hops: int = 2, + **kwargs + ) -> Dict[str, Any]: + """ + Query with multi-hop reasoning and LLM-based response generation. + + Retrieves context, builds reasoning paths through the graph, and generates + a natural language response grounded in the knowledge graph. + + Args: + query: User query + llm_provider: LLM provider instance (from semantica.llms) + max_results: Maximum context results to retrieve (default: 10) + max_hops: Maximum graph traversal hops (default: 2) + **kwargs: Additional retrieval options + + Returns: + Dictionary with: + - response: Generated natural language answer + - reasoning_path: Multi-hop reasoning trace + - sources: Retrieved context items + - confidence: Overall confidence score + + Example: + >>> from semantica.llms import Groq + >>> llm = Groq(model="llama-3.1-8b-instant") + >>> result = context.query_with_reasoning( + ... "What IPs are associated with security alerts?", + ... llm_provider=llm, + ... max_hops=2 + ... ) + >>> print(result['response']) + """ + if not self._retriever: + # Fallback if retriever not available + return { + "response": "GraphRAG retriever not available. Please configure knowledge_graph.", + "reasoning_path": "", + "sources": [], + "confidence": 0.0, + "num_sources": 0, + "num_reasoning_paths": 0 + } + + # Delegate to ContextRetriever + return self._retriever.query_with_reasoning( + query=query, + llm_provider=llm_provider, + max_results=max_results, + max_hops=max_hops, + **kwargs + ) + def forget( self, memory_id: Optional[str] = None, diff --git a/semantica/context/context_retriever.py b/semantica/context/context_retriever.py index b3d05de4..65b61485 100644 --- a/semantica/context/context_retriever.py +++ b/semantica/context/context_retriever.py @@ -1226,6 +1226,354 @@ class ContextRetriever: return [] + # Reasoning Methods + def _build_reasoning_path( + self, + query_entities: List[Dict[str, Any]], + max_hops: int = 2 + ) -> List[Dict[str, Any]]: + """ + Build multi-hop reasoning path through knowledge graph. + + Args: + query_entities: List of entities extracted from query + max_hops: Maximum number of hops to traverse (default: 2) + + Returns: + List of reasoning path segments with entity relationships + """ + if not self.knowledge_graph: + return [] + + reasoning_paths = [] + visited_entities = set() + + # Get entities and relationships from knowledge graph + # Handle both dict and GraphStore objects + if isinstance(self.knowledge_graph, dict): + entities = self.knowledge_graph.get("entities", []) + relationships = self.knowledge_graph.get("relationships", []) + elif hasattr(self.knowledge_graph, "get_entities") and hasattr(self.knowledge_graph, "get_relationships"): + # GraphStore-like object + entities = self.knowledge_graph.get_entities() or [] + relationships = self.knowledge_graph.get_relationships() or [] + else: + # Try to access as dict anyway + entities = getattr(self.knowledge_graph, "entities", []) + relationships = getattr(self.knowledge_graph, "relationships", []) + + # Create entity lookup + entity_map = {} + for entity in entities: + entity_id = entity.get("id") or entity.get("text") or entity.get("name") + if entity_id: + entity_map[entity_id] = entity + + # Create relationship lookup + rel_map = {} + for rel in relationships: + source = rel.get("source") or rel.get("source_id") + target = rel.get("target") or rel.get("target_id") + rel_type = rel.get("type") or rel.get("predicate") + if source and target: + if source not in rel_map: + rel_map[source] = [] + rel_map[source].append({"target": target, "type": rel_type, "rel": rel}) + + # Start BFS from query entities + from collections import deque + queue = deque() + + for query_entity in query_entities: + entity_id = query_entity.get("id") or query_entity.get("text") or query_entity.get("name") + if entity_id and entity_id in entity_map: + queue.append((entity_id, 0, [entity_id])) + + while queue: + current_id, hop, path = queue.popleft() + + if hop >= max_hops: + continue + + if current_id in visited_entities: + continue + visited_entities.add(current_id) + + # Get relationships from current entity + if current_id in rel_map: + for rel_info in rel_map[current_id]: + target_id = rel_info["target"] + rel_type = rel_info["type"] + rel = rel_info["rel"] + + if target_id not in path: # Avoid cycles + new_path = path + [target_id] + + # Build relationships list for this path + path_relationships = [] + for i in range(len(new_path) - 1): + source_id = new_path[i] + target_id_rel = new_path[i + 1] + # Find the relationship type between these two entities + rel_type_found = None + if source_id in rel_map: + for r_info in rel_map[source_id]: + if r_info["target"] == target_id_rel: + rel_type_found = r_info["type"] + break + path_relationships.append({ + "source": source_id, + "target": target_id_rel, + "type": rel_type_found or "related_to" + }) + + # Add to reasoning paths + reasoning_paths.append({ + "path": new_path, + "hops": hop + 1, + "relationships": path_relationships, + "entities": [ + entity_map.get(eid, {}) for eid in new_path + ] + }) + + # Continue traversal + if target_id in entity_map and hop + 1 < max_hops: + queue.append((target_id, hop + 1, new_path)) + + return reasoning_paths + + def _generate_reasoned_response( + self, + query: str, + retrieved_context: List[RetrievedContext], + reasoning_paths: List[Dict[str, Any]], + llm_provider: Any + ) -> str: + """ + Generate natural language response using LLM with retrieved context and reasoning paths. + + Args: + query: User query + retrieved_context: Retrieved context items + reasoning_paths: Multi-hop reasoning paths + llm_provider: LLM provider instance (from semantica.llms) + + Returns: + Generated natural language response + """ + # Format retrieved context + context_text = "\n\n".join([ + f"Context {i+1} (Score: {ctx.score:.2f}):\n{ctx.content}" + for i, ctx in enumerate(retrieved_context[:5]) + ]) + + # Format reasoning paths + reasoning_text = "" + if reasoning_paths: + reasoning_text = "\n\nReasoning Paths (Multi-hop connections):\n" + for i, path_info in enumerate(reasoning_paths[:3], 1): + entities = path_info.get("entities", []) + relationships = path_info.get("relationships", []) + + if entities: + path_parts = [] + for j, entity in enumerate(entities): + entity_name = entity.get('text') or entity.get('name') or 'Unknown' + path_parts.append(entity_name) + # Add relationship after entity (except for last entity) + if j < len(relationships) and relationships[j].get('type'): + rel_type = relationships[j]['type'] + path_parts.append(f"--[{rel_type}]-->") + path_str = " ".join(path_parts) + reasoning_text += f"Path {i}: {path_str}\n" + + # Construct prompt + prompt = f"""You are a knowledge graph reasoning assistant. Answer the user's question based on the retrieved context and reasoning paths from the knowledge graph. + +User Question: {query} + +Retrieved Context: +{context_text} + +{reasoning_text} + +Instructions: +1. Answer the question using the retrieved context and reasoning paths +2. Cite specific entities and relationships from the reasoning paths +3. Explain the multi-hop connections when relevant +4. Be concise but comprehensive +5. If information is not available in the context, say so + +Answer:""" + + try: + response = llm_provider.generate(prompt, temperature=0.3) + return response + except Exception as e: + self.logger.warning(f"LLM generation failed: {e}") + # Fallback: return summary of context + return f"Based on the retrieved context, here are the relevant findings:\n\n{context_text[:500]}..." + + def query_with_reasoning( + self, + query: str, + llm_provider: Any, + max_results: int = 10, + max_hops: int = 2, + **kwargs + ) -> Dict[str, Any]: + """ + Query with multi-hop reasoning and LLM-based response generation. + + Retrieves context, builds reasoning paths through the graph, and generates + a natural language response grounded in the knowledge graph. + + Args: + query: User query + llm_provider: LLM provider instance (from semantica.llms) + max_results: Maximum context results to retrieve (default: 10) + max_hops: Maximum graph traversal hops (default: 2) + **kwargs: Additional retrieval options + + Returns: + Dictionary with: + - response: Generated natural language answer + - reasoning_path: Multi-hop reasoning trace + - sources: Retrieved context items + - confidence: Overall confidence score + + Example: + >>> from semantica.llms import Groq + >>> llm = Groq(model="llama-3.1-8b-instant") + >>> result = retriever.query_with_reasoning( + ... "What IPs are associated with security alerts?", + ... llm_provider=llm, + ... max_hops=2 + ... ) + >>> print(result['response']) + """ + tracking_id = self.progress_tracker.start_tracking( + file=None, + module="context", + submodule="ContextRetriever", + message=f"Querying with reasoning: {query[:50]}...", + ) + + try: + # Step 1: Retrieve initial context + self.progress_tracker.update_tracking( + tracking_id, message="Retrieving context..." + ) + retrieved_context = self.retrieve( + query, + max_results=max_results, + use_graph_expansion=True, + **kwargs + ) + + # Step 2: Extract entities from query and retrieved context + self.progress_tracker.update_tracking( + tracking_id, message="Extracting entities..." + ) + query_entities = [] + + # Extract entities from retrieved context + for ctx in retrieved_context: + query_entities.extend(ctx.related_entities) + + # Deduplicate entities + seen_ids = set() + unique_entities = [] + for entity in query_entities: + entity_id = entity.get("id") or entity.get("text") or entity.get("name") + if entity_id and entity_id not in seen_ids: + seen_ids.add(entity_id) + unique_entities.append(entity) + + # Step 3: Build reasoning paths + self.progress_tracker.update_tracking( + tracking_id, message="Building reasoning paths..." + ) + reasoning_paths = self._build_reasoning_path( + unique_entities, + max_hops=max_hops + ) + + # Step 4: Generate response using LLM + self.progress_tracker.update_tracking( + tracking_id, message="Generating response..." + ) + response = self._generate_reasoned_response( + query, + retrieved_context, + reasoning_paths, + llm_provider + ) + + # Step 5: Format reasoning path as string + reasoning_path_str = "" + if reasoning_paths: + for path_info in reasoning_paths[:1]: # Show first path + entities = path_info.get("entities", []) + relationships = path_info.get("relationships", []) + if entities: + path_parts = [] + for i, entity in enumerate(entities): + entity_name = entity.get("text") or entity.get("name") or "Unknown" + path_parts.append(entity_name) + if i < len(relationships) and relationships[i].get("type"): + path_parts.append(f"--[{relationships[i]['type']}]-->") + reasoning_path_str = " ".join(path_parts) + + # Calculate overall confidence + confidence = 0.0 + if retrieved_context: + avg_score = sum(ctx.score for ctx in retrieved_context) / len(retrieved_context) + confidence = min(1.0, avg_score * 0.8 + (0.2 if reasoning_paths else 0.0)) + + self.progress_tracker.stop_tracking( + tracking_id, status="completed", message="Query with reasoning completed" + ) + + return { + "response": response, + "reasoning_path": reasoning_path_str, + "sources": [ + { + "content": ctx.content[:200], + "score": ctx.score, + "source": ctx.source + } + for ctx in retrieved_context[:5] + ], + "confidence": confidence, + "num_sources": len(retrieved_context), + "num_reasoning_paths": len(reasoning_paths) + } + + except Exception as e: + self.progress_tracker.stop_tracking( + tracking_id, status="failed", message=str(e) + ) + self.logger.error(f"Query with reasoning failed: {e}") + # Fallback: return retrieved context without LLM generation + return { + "response": f"Retrieved {len(retrieved_context)} relevant items. LLM generation unavailable.", + "reasoning_path": "", + "sources": [ + { + "content": ctx.content[:200], + "score": ctx.score, + "source": ctx.source + } + for ctx in retrieved_context[:5] + ], + "confidence": 0.5, + "num_sources": len(retrieved_context), + "num_reasoning_paths": 0 + } + # Filter Methods def filter_by_entity( self, entity_id: str, query: str, **options diff --git a/semantica/llms/__init__.py b/semantica/llms/__init__.py new file mode 100644 index 00000000..da00ff93 --- /dev/null +++ b/semantica/llms/__init__.py @@ -0,0 +1,46 @@ +""" +LLM Providers Module + +This module provides clean, intuitive imports for LLM providers used in Semantica. +It wraps the underlying provider functionality from semantica.semantic_extract.providers +to provide a cleaner API. + +Supported Providers: + - Groq: Groq API for fast inference + - OpenAI: OpenAI API (GPT-3.5, GPT-4, etc.) + - HuggingFaceLLM: HuggingFace Transformers for local LLM inference + - LiteLLM: Unified interface to 100+ LLM providers (OpenAI, Anthropic, Groq, Azure, Bedrock, Vertex AI, etc.) + +Example Usage: + >>> from semantica.llms import Groq, OpenAI, HuggingFaceLLM, LiteLLM + >>> + >>> # Groq provider + >>> groq = Groq(model="llama-3.1-8b-instant", api_key="your-key") + >>> response = groq.generate("Hello, world!") + >>> + >>> # OpenAI provider + >>> openai = OpenAI(model="gpt-4", api_key="your-key") + >>> response = openai.generate("Hello, world!") + >>> + >>> # HuggingFace LLM provider + >>> hf = HuggingFaceLLM(model_name="gpt2") + >>> response = hf.generate("Hello, world!") + >>> + >>> # LiteLLM provider (supports 100+ LLMs) + >>> llm = LiteLLM(model="openai/gpt-4o", api_key="your-key") + >>> response = llm.generate("Hello, world!") + >>> # Or use other providers via LiteLLM + >>> llm = LiteLLM(model="anthropic/claude-sonnet-4-20250514") + >>> response = llm.generate("Hello, world!") + +Author: Semantica Contributors +License: MIT +""" + +from .groq import Groq +from .openai import OpenAI +from .huggingface import HuggingFaceLLM +from .litellm import LiteLLM + +__all__ = ["Groq", "OpenAI", "HuggingFaceLLM", "LiteLLM"] + diff --git a/semantica/llms/groq.py b/semantica/llms/groq.py new file mode 100644 index 00000000..e3cbf43e --- /dev/null +++ b/semantica/llms/groq.py @@ -0,0 +1,89 @@ +""" +Groq LLM Provider + +Wrapper for Groq API provider with clean interface. +""" + +from typing import Any, Dict, Optional + +from ..semantic_extract.providers import GroqProvider +from ..utils.exceptions import ProcessingError +from ..utils.logging import get_logger + +logger = get_logger("llms.groq") + + +class Groq: + """ + Groq LLM provider wrapper. + + Provides clean interface to Groq API for text generation. + + Example: + >>> from semantica.llms import Groq + >>> groq = Groq(model="llama-3.1-8b-instant", api_key="your-key") + >>> response = groq.generate("What is AI?") + """ + + def __init__( + self, + model: str = "llama-3.1-8b-instant", + api_key: Optional[str] = None, + **kwargs + ): + """ + Initialize Groq provider. + + Args: + model: Model name (default: "llama-3.1-8b-instant") + api_key: Groq API key (default: from GROQ_API_KEY env var) + **kwargs: Additional provider options + """ + self.provider = GroqProvider(api_key=api_key, model=model, **kwargs) + self.model = model + self.api_key = api_key + + def is_available(self) -> bool: + """Check if Groq provider is available.""" + return self.provider.is_available() + + def generate(self, prompt: str, **kwargs) -> str: + """ + Generate text from prompt. + + Args: + prompt: Input prompt text + **kwargs: Generation options (temperature, max_tokens, etc.) + + Returns: + Generated text response + + Raises: + ProcessingError: If provider is not available or generation fails + """ + if not self.is_available(): + raise ProcessingError( + "Groq provider not available. Set GROQ_API_KEY or pass api_key." + ) + return self.provider.generate(prompt, **kwargs) + + def generate_structured(self, prompt: str, **kwargs) -> Dict[str, Any]: + """ + Generate structured JSON output. + + Args: + prompt: Input prompt text + **kwargs: Generation options + + Returns: + Parsed JSON response as dictionary + + Raises: + ProcessingError: If provider is not available or parsing fails + """ + if not self.is_available(): + raise ProcessingError( + "Groq provider not available. Set GROQ_API_KEY or pass api_key." + ) + return self.provider.generate_structured(prompt, **kwargs) + diff --git a/semantica/llms/huggingface.py b/semantica/llms/huggingface.py new file mode 100644 index 00000000..bd5326ce --- /dev/null +++ b/semantica/llms/huggingface.py @@ -0,0 +1,100 @@ +""" +HuggingFace LLM Provider + +Wrapper for HuggingFace Transformers LLM provider with clean interface. +""" + +from typing import Any, Dict, Optional + +from ..semantic_extract.providers import HuggingFaceLLMProvider +from ..utils.exceptions import ProcessingError +from ..utils.logging import get_logger + +logger = get_logger("llms.huggingface") + + +class HuggingFaceLLM: + """ + HuggingFace LLM provider wrapper. + + Provides clean interface to HuggingFace Transformers for local LLM inference. + + Example: + >>> from semantica.llms import HuggingFaceLLM + >>> hf = HuggingFaceLLM(model_name="gpt2") + >>> response = hf.generate("What is AI?") + """ + + def __init__( + self, + model_name: Optional[str] = None, + model: Optional[str] = None, + device: Optional[str] = None, + **kwargs + ): + """ + Initialize HuggingFace LLM provider. + + Args: + model_name: HuggingFace model name (default: "gpt2") + model: Alias for model_name (for consistency with other providers) + device: Device to use ("cuda" or "cpu", default: auto-detect) + **kwargs: Additional provider options + """ + # Support both model_name and model for consistency + if model is not None: + model_name = model + elif model_name is None: + model_name = "gpt2" + + self.provider = HuggingFaceLLMProvider( + model_name=model_name, device=device, **kwargs + ) + self.model_name = model_name + self.model = model_name # Alias for consistency + self.device = device + + def is_available(self) -> bool: + """Check if HuggingFace LLM provider is available.""" + return self.provider.is_available() + + def generate(self, prompt: str, **kwargs) -> str: + """ + Generate text from prompt. + + Args: + prompt: Input prompt text + **kwargs: Generation options (max_length, temperature, etc.) + + Returns: + Generated text response + + Raises: + ProcessingError: If provider is not available or generation fails + """ + if not self.is_available(): + raise ProcessingError( + "HuggingFace LLM provider not available. Install transformers library." + ) + return self.provider.generate(prompt, **kwargs) + + def generate_structured(self, prompt: str, **kwargs) -> Dict[str, Any]: + """ + Generate structured JSON output. + + Args: + prompt: Input prompt text + **kwargs: Generation options + + Returns: + Parsed JSON response as dictionary + + Raises: + ProcessingError: If provider is not available or parsing fails + """ + if not self.is_available(): + raise ProcessingError( + "HuggingFace LLM provider not available. Install transformers library." + ) + return self.provider.generate_structured(prompt, **kwargs) + diff --git a/semantica/llms/litellm.py b/semantica/llms/litellm.py new file mode 100644 index 00000000..50b4165f --- /dev/null +++ b/semantica/llms/litellm.py @@ -0,0 +1,189 @@ +""" +LiteLLM Provider + +Wrapper for LiteLLM library that provides unified access to 100+ LLM providers. +Supports OpenAI, Anthropic, Groq, Azure, Bedrock, Vertex AI, and many more. +""" + +from typing import Any, Dict, List, Optional + +from ..utils.exceptions import ProcessingError +from ..utils.logging import get_logger + +logger = get_logger("llms.litellm") + +try: + from litellm import completion + LITELLM_AVAILABLE = True +except ImportError: + LITELLM_AVAILABLE = False + logger.warning( + "litellm library not installed. Install with: pip install litellm" + ) + + +class LiteLLM: + """ + LiteLLM provider wrapper. + + Provides unified interface to 100+ LLM providers through LiteLLM library. + Supports providers like OpenAI, Anthropic, Groq, Azure, Bedrock, Vertex AI, etc. + + Model format: "provider/model-name" (e.g., "openai/gpt-4o", "anthropic/claude-sonnet-4-20250514", "groq/llama-3.1-8b-instant") + + Example: + >>> from semantica.llms import LiteLLM + >>> llm = LiteLLM(model="openai/gpt-4o", api_key="your-key") + >>> response = llm.generate("What is AI?") + >>> + >>> # Use with different providers + >>> llm = LiteLLM(model="anthropic/claude-sonnet-4-20250514") + >>> response = llm.generate("Hello!") + """ + + def __init__( + self, + model: str, + api_key: Optional[str] = None, + **kwargs + ): + """ + Initialize LiteLLM provider. + + Args: + model: Model identifier in format "provider/model-name" + Examples: "openai/gpt-4o", "anthropic/claude-sonnet-4-20250514", + "groq/llama-3.1-8b-instant", "azure/gpt-4", etc. + api_key: API key (optional, can use environment variables) + **kwargs: Additional LiteLLM options (temperature, max_tokens, etc.) + """ + if not LITELLM_AVAILABLE: + raise ProcessingError( + "LiteLLM library not installed. Install with: pip install litellm" + ) + + self.model = model + self.api_key = api_key + self.config = kwargs + + def is_available(self) -> bool: + """Check if LiteLLM provider is available.""" + return LITELLM_AVAILABLE + + def generate(self, prompt: str, **kwargs) -> str: + """ + Generate text from prompt. + + Args: + prompt: Input prompt text + **kwargs: Generation options (temperature, max_tokens, etc.) + + Returns: + Generated text response + + Raises: + ProcessingError: If provider is not available or generation fails + """ + if not self.is_available(): + raise ProcessingError( + "LiteLLM library not installed. Install with: pip install litellm" + ) + + try: + # Merge config with kwargs + options = {**self.config, **kwargs} + + # Prepare messages + messages = [{"role": "user", "content": prompt}] + + # Call LiteLLM completion + response = completion( + model=self.model, + messages=messages, + api_key=self.api_key, + **options + ) + + # Extract text from response + if hasattr(response, 'choices') and len(response.choices) > 0: + return response.choices[0].message.content + elif isinstance(response, dict): + if 'choices' in response and len(response['choices']) > 0: + return response['choices'][0]['message']['content'] + elif 'content' in response: + return response['content'] + elif isinstance(response, str): + return response + + raise ProcessingError(f"Unexpected response format from LiteLLM: {type(response)}") + + except Exception as e: + logger.error(f"LiteLLM generation failed: {e}") + raise ProcessingError(f"LiteLLM generation failed: {e}") + + def generate_structured(self, prompt: str, **kwargs) -> Dict[str, Any]: + """ + Generate structured JSON output. + + Args: + prompt: Input prompt text + **kwargs: Generation options + + Returns: + Parsed JSON response as dictionary + + Raises: + ProcessingError: If provider is not available or parsing fails + """ + if not self.is_available(): + raise ProcessingError( + "LiteLLM library not installed. Install with: pip install litellm" + ) + + try: + import json + + # Add JSON format instruction to prompt + json_prompt = f"{prompt}\n\nReturn the response as valid JSON only." + + # Merge config with kwargs + options = {**self.config, **kwargs} + + # Prepare messages + messages = [{"role": "user", "content": json_prompt}] + + # Call LiteLLM completion + response = completion( + model=self.model, + messages=messages, + api_key=self.api_key, + **options + ) + + # Extract text from response + text_response = "" + if hasattr(response, 'choices') and len(response.choices) > 0: + text_response = response.choices[0].message.content + elif isinstance(response, dict): + if 'choices' in response and len(response['choices']) > 0: + text_response = response['choices'][0]['message']['content'] + elif 'content' in response: + text_response = response['content'] + elif isinstance(response, str): + text_response = response + + # Parse JSON + try: + return json.loads(text_response) + except json.JSONDecodeError: + # Try to extract JSON from text + import re + json_match = re.search(r'\{.*\}', text_response, re.DOTALL) + if json_match: + return json.loads(json_match.group()) + raise ProcessingError(f"Failed to parse JSON from LiteLLM response: {text_response[:200]}") + + except Exception as e: + logger.error(f"LiteLLM structured generation failed: {e}") + raise ProcessingError(f"LiteLLM structured generation failed: {e}") + diff --git a/semantica/llms/openai.py b/semantica/llms/openai.py new file mode 100644 index 00000000..15a3a8df --- /dev/null +++ b/semantica/llms/openai.py @@ -0,0 +1,89 @@ +""" +OpenAI LLM Provider + +Wrapper for OpenAI API provider with clean interface. +""" + +from typing import Any, Dict, Optional + +from ..semantic_extract.providers import OpenAIProvider +from ..utils.exceptions import ProcessingError +from ..utils.logging import get_logger + +logger = get_logger("llms.openai") + + +class OpenAI: + """ + OpenAI LLM provider wrapper. + + Provides clean interface to OpenAI API for text generation. + + Example: + >>> from semantica.llms import OpenAI + >>> openai = OpenAI(model="gpt-4", api_key="your-key") + >>> response = openai.generate("What is AI?") + """ + + def __init__( + self, + model: str = "gpt-3.5-turbo", + api_key: Optional[str] = None, + **kwargs + ): + """ + Initialize OpenAI provider. + + Args: + model: Model name (default: "gpt-3.5-turbo") + api_key: OpenAI API key (default: from OPENAI_API_KEY env var) + **kwargs: Additional provider options + """ + self.provider = OpenAIProvider(api_key=api_key, model=model, **kwargs) + self.model = model + self.api_key = api_key + + def is_available(self) -> bool: + """Check if OpenAI provider is available.""" + return self.provider.is_available() + + def generate(self, prompt: str, **kwargs) -> str: + """ + Generate text from prompt. + + Args: + prompt: Input prompt text + **kwargs: Generation options (temperature, max_tokens, etc.) + + Returns: + Generated text response + + Raises: + ProcessingError: If provider is not available or generation fails + """ + if not self.is_available(): + raise ProcessingError( + "OpenAI provider not available. Set OPENAI_API_KEY or pass api_key." + ) + return self.provider.generate(prompt, **kwargs) + + def generate_structured(self, prompt: str, **kwargs) -> Dict[str, Any]: + """ + Generate structured JSON output. + + Args: + prompt: Input prompt text + **kwargs: Generation options + + Returns: + Parsed JSON response as dictionary + + Raises: + ProcessingError: If provider is not available or parsing fails + """ + if not self.is_available(): + raise ProcessingError( + "OpenAI provider not available. Set OPENAI_API_KEY or pass api_key." + ) + return self.provider.generate_structured(prompt, **kwargs) +