mirror of
https://github.com/semantica-agi/semantica.git
synced 2026-09-04 04:01:07 +00:00
Merge pull request #117 from Hawksight-AI/llms
Add LLM Providers Module and GraphRAG Reasoning Features
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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": [
|
||||
"<div style='font-family: monospace;'><h4>🧠 Semantica - 📊 Current Progress</h4><table style='width: 100%; border-collapse: collapse;'><tr><th>Status</th><th>Action</th><th>Module</th><th>Submodule</th><th>File</th><th>Time</th></tr><tr><td>✅</td><td>Semantica is extracting</td><td>🎯 semantic_extract</td><td>NERExtractor</td><td>-</td><td>0.55s</td></tr><tr><td>✅</td><td>Semantica is extracting</td><td>🎯 semantic_extract</td><td>RelationExtractor</td><td>-</td><td>0.05s</td></tr><tr><td>✅</td><td>Semantica is resolving</td><td>⚠️ conflicts</td><td>ConflictDetector</td><td>-</td><td>0.00s</td></tr><tr><td>✅</td><td>Semantica is building</td><td>🧠 kg</td><td>GraphBuilder</td><td>-</td><td>350.71s</td></tr><tr><td>🔄</td><td>Semantica is building</td><td>🧠 kg</td><td>EntityResolver</td><td>-</td><td>84.09s</td></tr><tr><td>✅</td><td>Semantica is deduplicating</td><td>🔄 deduplication</td><td>DuplicateDetector</td><td>-</td><td>0.08s</td></tr><tr><td>✅</td><td>Semantica is deduplicating</td><td>🔄 deduplication</td><td>SimilarityCalculator</td><td>-</td><td>0.02s</td></tr><tr><td>✅</td><td>Semantica is deduplicating</td><td>🔄 deduplication</td><td>EntityMerger</td><td>-</td><td>0.13s</td></tr><tr><td>✅</td><td>Semantica is deduplicating</td><td>🔄 deduplication</td><td>MergeStrategyManager</td><td>-</td><td>0.03s</td></tr><tr><td>✅</td><td>Semantica is indexing</td><td>📊 vector_store</td><td>VectorStore</td><td>-</td><td>0.01s</td></tr></table></div>"
|
||||
],
|
||||
"text/plain": [
|
||||
"<IPython.core.display.HTML object>"
|
||||
]
|
||||
},
|
||||
"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,
|
||||
|
||||
+104
-12
@@ -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 |
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user