We are going to build a small but complete pipeline that reads raw text, extracts a knowledge graph as subject-relation-object triples, and embeds every entity so we can search the graph with natural language. If you are building a RAG system or trying to connect disjoint documents, this gives you a structured layer that vector search alone cannot provide. I will use Oxlo.ai for both the LLM extraction and the embedding generation, which keeps the code uniform and avoids token-length pricing surprises on long input texts.
What you'll need
- Python 3.10 or newer
- The OpenAI SDK:
pip install openai - An Oxlo.ai API key from https://portal.oxlo.ai
Step 1: Configure the Oxlo.ai client and extraction prompt
First I set up the OpenAI-compatible client pointing to Oxlo.ai. I also define the system prompt that forces the model to return strict JSON triples and nothing else.
import json
from openai import OpenAI
client = OpenAI(
base_url="https://api.oxlo.ai/v1",
api_key="YOUR_OXLO_API_KEY" # from https://portal.oxlo.ai
)
SYSTEM_PROMPT = """You are a precise knowledge-graph extractor.
Read the user's text and emit a JSON object with exactly one key, \"triples\".
The value must be a list of lists, where each inner list contains three strings:
[subject, relation, object].
Rules:
- Use canonical entity names (e.g., \"Amazon\" instead of \"the company\").
- Keep relations simple: one or two words, lowercase, no spaces (use underscores).
- Emit nothing outside the JSON block.
"""
Step 2: Extract triples from raw text
Next I pass a raw text block to the model and parse the JSON it returns. I use llama-3.3-70b because it handles long context reliably and follows formatting instructions.
RAW_TEXT = """
Acme Corp was founded in 1999 by Alice Johnson and Bob Smith.
The company, headquartered in Austin, Texas, manufactures electric motors.
In 2021, Acme Corp acquired Beta Industries, a battery startup based in Berlin.
Alice Johnson later became the CEO of the merged entity.
"""
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": RAW_TEXT},
],
temperature=0.1,
)
content = response.choices[0].message.content
content = content.replace("
```json", "").replace("```
", "").strip()
triples_data = json.loads(content)
triples = triples_data["triples"]
print(f"Extracted {len(triples)} triples:")
for t in triples:
print(t)
Step 3: Parse and deduplicate the graph
Before embedding, I normalize entity names and build an adjacency list. This deduplication step prevents duplicate nodes and makes the downstream graph walk cleaner.
from collections import defaultdict
nodes = set()
edges = defaultdict(list)
for subj, rel, obj in triples:
s = subj.strip()
r = rel.strip().lower().replace(" ", "_")
o = obj.strip()
nodes.add(s)
nodes.add(o)
edges[s].append((r, o))
nodes = sorted(nodes)
print(f"Unique entities: {len(nodes)}")
for n in nodes:
print(f" - {n}")
Step 4: Generate entity embeddings
Now I embed every unique entity using Oxlo.ai's embedding endpoint. Because Oxlo.ai charges per request rather than per token, I can send the full node list in one call without worrying about character count.
import math
def cosine_similarity(a, b):
dot = sum(x * y for x, y in zip(a, b))
norm_a = math.sqrt(sum(x * x for x in a))
norm_b = math.sqrt(sum(x * x for x in b))
return dot / (norm_a * norm_b)
emb_response = client.embeddings.create(
model="bge-large",
input=nodes,
)
entity_vectors = {}
for item in emb_response.data:
idx = item.index
entity_vectors[nodes[idx]] = item.embedding
print(f"Stored vectors for {len(entity_vectors)} entities.")
Run it: Query the knowledge graph
With the graph and vectors in memory, I embed a natural-language question and find the most similar entity by cosine similarity. I then walk one hop through the graph to surface the connected facts that answer the question.
query = "Who leads the company that bought Beta Industries?"
q_emb_resp = client.embeddings.create(
model="bge-large",
input=[query],
)
q_vec = q_emb_resp.data[0].embedding
scored = [
(entity, cosine_similarity(q_vec, vec))
for entity, vec in entity_vectors.items()
]
scored.sort(key=lambda x: x[1], reverse=True)
top_entity, top_score = scored[0]
print(f"Top entity match: '{top_entity}' (score: {top_score:.3f})\n")
print(f"Facts about '{top_entity}':")
for rel, obj in edges.get(top_entity, []):
print(f" - {rel} -> {obj}")
print(f"\nEntities connected to '{top_entity}':")
for src, rel_objs in edges.items():
for rel, obj in rel_objs:
if obj == top_entity:
print(f" - {src} -> {rel}")
Example output:
Top entity match: 'Acme Corp' (score: 0.812)
Facts about 'Acme Corp':
- founded_in -> 1999
- headquartered_in -> Austin, Texas
- manufactures -> electric motors
- acquired -> Beta Industries
Entities connected to 'Acme Corp':
- Alice Johnson -> founded
- Bob Smith -> founded
Wrap-up
That gives you a minimal but complete LLM-driven knowledge graph embedding pipeline. To take it further, persist the vectors in a vector database such as pgvector, or add relation embeddings so you can predict missing links with vector arithmetic.
Top comments (0)