ctxgraph 0.2.2__tar.gz → 0.2.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/PKG-INFO +87 -66
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/README.md +86 -65
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/pyproject.toml +1 -1
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph.egg-info/PKG-INFO +87 -66
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/setup.cfg +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/__init__.py +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/analyzers/__init__.py +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/analyzers/python/__init__.py +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/analyzers/python/importer.py +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/analyzers/python/semantic.py +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/analyzers/python/symbols.py +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/capsule/__init__.py +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/capsule/renderer.py +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/cli/__init__.py +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/cli/main.py +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/clients/__init__.py +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/clients/models.py +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/config/__init__.py +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/config/providers.py +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/config/settings.py +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/exclude/__init__.py +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/exclude/patterns.py +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/graph/__init__.py +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/graph/builder.py +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/graph/models.py +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/graph/query.py +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/graph/storage.py +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/mcp/__init__.py +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/mcp/server.py +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/view/__init__.py +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/view/visualizer.py +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/wrapper/__init__.py +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/wrapper/claude.py +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph.egg-info/SOURCES.txt +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph.egg-info/dependency_links.txt +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph.egg-info/entry_points.txt +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph.egg-info/requires.txt +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph.egg-info/top_level.txt +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/tests/test_analyzers.py +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/tests/test_benchmark.py +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/tests/test_capsule.py +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/tests/test_config.py +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/tests/test_integration.py +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/tests/test_model_mode.py +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/tests/test_models.py +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/tests/test_query.py +0 -0
- {ctxgraph-0.2.2 → ctxgraph-0.2.4}/tests/test_storage.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ctxgraph
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: AI context engine for Python — cuts LLM tokens 97% via code knowledge graphs. Build, query, and generate compact context capsules for Claude, OpenAI, Ollama.
|
|
5
5
|
Author: ctxgraph contributors
|
|
6
6
|
License: MIT
|
|
@@ -270,7 +270,7 @@ max_depth = 2
|
|
|
270
270
|
| `AZURE_OPENAI_API_KEY` | Azure OpenAI API |
|
|
271
271
|
|
|
272
272
|
```bash
|
|
273
|
-
# Ollama (default)
|
|
273
|
+
# Ollama (default — no env vars needed)
|
|
274
274
|
ctx capsule "query"
|
|
275
275
|
|
|
276
276
|
# Claude
|
|
@@ -290,6 +290,17 @@ CTXGRAPH_PROVIDER=azure \
|
|
|
290
290
|
CTXGRAPH_PROVIDER=custom CTXGRAPH_ENDPOINT=http://my-api/v1 ctx capsule "query"
|
|
291
291
|
```
|
|
292
292
|
|
|
293
|
+
> **Windows (PowerShell):** Use `$env:` prefix instead:
|
|
294
|
+
> ```powershell
|
|
295
|
+
> $env:CTXGRAPH_PROVIDER = "azure"; $env:CTXGRAPH_MODEL = "gpt-4o"; ctx capsule "query"
|
|
296
|
+
> ```
|
|
297
|
+
> Or set them once per session:
|
|
298
|
+
> ```powershell
|
|
299
|
+
> $env:CTXGRAPH_PROVIDER = "azure"
|
|
300
|
+
> $env:AZURE_OPENAI_API_KEY = "sk-..."
|
|
301
|
+
> ctx capsule "query"
|
|
302
|
+
> ```
|
|
303
|
+
|
|
293
304
|
---
|
|
294
305
|
|
|
295
306
|
## Use Cases
|
|
@@ -319,9 +330,15 @@ ctx capsule "extract payment processing into separate module" --mode deep
|
|
|
319
330
|
|
|
320
331
|
## Framework Integrations
|
|
321
332
|
|
|
322
|
-
ctxgraph
|
|
333
|
+
ctxgraph is a Python library first — the CLI is just a wrapper. This makes it easy to feed code context into LangChain, LangGraph, OpenAI Agents, or any LLM pipeline.
|
|
334
|
+
|
|
335
|
+
### How the Python API works
|
|
336
|
+
|
|
337
|
+
The flow is always the same:
|
|
323
338
|
|
|
324
|
-
|
|
339
|
+
1. **`build_graph(path)`** → scans your code, stores a knowledge graph in `path/.ctxgraph/graph.db`
|
|
340
|
+
2. **`get_storage(path)`** → opens that SQLite database for queries (fast, no re-scanning)
|
|
341
|
+
3. **`render_capsule(storage, query)`** → searches the graph, returns a compact text capsule
|
|
325
342
|
|
|
326
343
|
```python
|
|
327
344
|
from pathlib import Path
|
|
@@ -329,154 +346,158 @@ from ctxgraph.graph.builder import build_graph, get_storage
|
|
|
329
346
|
from ctxgraph.capsule.renderer import render_capsule
|
|
330
347
|
from ctxgraph.graph.query import search_relevant_nodes
|
|
331
348
|
|
|
332
|
-
# 1
|
|
333
|
-
stats = build_graph(Path("/path/to/
|
|
349
|
+
# --- Step 1: Build (one-time, ~0.1-1s per project) ---
|
|
350
|
+
stats = build_graph(Path("/path/to/my_project"))
|
|
334
351
|
print(f"Built: {stats['total_nodes']} nodes, {stats['total_edges']} edges")
|
|
335
352
|
|
|
336
|
-
# 2
|
|
337
|
-
storage = get_storage(Path("/path/to/
|
|
353
|
+
# --- Step 2: Use (instant — reads the .db file) ---
|
|
354
|
+
storage = get_storage(Path("/path/to/my_project"))
|
|
338
355
|
|
|
339
|
-
#
|
|
356
|
+
# Generate a capsule — a token-efficient DSL string
|
|
340
357
|
capsule = render_capsule(storage, "fix JWT token validation", max_nodes=20)
|
|
341
358
|
print(capsule)
|
|
342
|
-
|
|
343
|
-
#
|
|
359
|
+
# → [CTX]fix JWT token validation
|
|
360
|
+
# [F]src/auth/jwt.py
|
|
361
|
+
# D:JWT token creation and validation
|
|
362
|
+
# [F]src/auth/middleware.py
|
|
363
|
+
# D:Auth middleware for request validation
|
|
364
|
+
# ...
|
|
365
|
+
|
|
366
|
+
# Or search for nodes programmatically
|
|
344
367
|
results = search_relevant_nodes(storage, "auth login", max_nodes=10, max_depth=2)
|
|
345
368
|
for node, score in results:
|
|
346
369
|
print(f" {node.type}:{node.name} (score={score})")
|
|
347
370
|
```
|
|
348
371
|
|
|
372
|
+
> **Tip:** `build_graph` is a one-time setup. In production, run `ctx build` during CI/deployment and let your app code only call `get_storage` + `render_capsule`.
|
|
373
|
+
|
|
349
374
|
### LangChain
|
|
350
375
|
|
|
351
|
-
|
|
376
|
+
Pass the capsule as context in your prompt template. The LLM gets exactly the files, classes, and dependencies it needs — no token waste.
|
|
352
377
|
|
|
353
378
|
```python
|
|
354
379
|
from pathlib import Path
|
|
355
380
|
from langchain_openai import ChatOpenAI
|
|
356
381
|
from langchain_core.prompts import ChatPromptTemplate
|
|
357
|
-
from ctxgraph.graph.builder import
|
|
382
|
+
from ctxgraph.graph.builder import get_storage # graph already built
|
|
358
383
|
from ctxgraph.capsule.renderer import render_capsule
|
|
359
384
|
|
|
360
|
-
#
|
|
361
|
-
build_graph(Path("./my_project"))
|
|
385
|
+
# Load existing graph (zero build time)
|
|
362
386
|
storage = get_storage(Path("./my_project"))
|
|
363
387
|
|
|
364
|
-
# Generate
|
|
365
|
-
context = render_capsule(storage, "
|
|
388
|
+
# Generate capsule for the question
|
|
389
|
+
context = render_capsule(storage, "login rate limiter", max_nodes=15)
|
|
366
390
|
|
|
367
391
|
prompt = ChatPromptTemplate.from_messages([
|
|
368
|
-
("system", "You are a senior Python
|
|
392
|
+
("system", "You are a senior Python dev. Answer using the code context below.\n\n{context}"),
|
|
369
393
|
("user", "{question}"),
|
|
370
394
|
])
|
|
371
395
|
|
|
372
396
|
llm = ChatOpenAI(model="gpt-4o")
|
|
373
|
-
|
|
397
|
+
response = prompt | llm | (lambda msg: msg.content)
|
|
374
398
|
|
|
375
|
-
response
|
|
399
|
+
print(response.invoke({
|
|
376
400
|
"context": context,
|
|
377
|
-
"question": "Where is the
|
|
378
|
-
})
|
|
401
|
+
"question": "Where is the rate limiter applied in the login flow?",
|
|
402
|
+
}))
|
|
403
|
+
# → "The rate limiter is in src/auth/middleware.py at line 42.
|
|
404
|
+
# It wraps the login endpoint with a 5req/min limit per IP."
|
|
379
405
|
```
|
|
380
406
|
|
|
381
407
|
### LangGraph
|
|
382
408
|
|
|
383
|
-
|
|
409
|
+
Expose ctxgraph as a tool the agent calls on-demand. The agent fetches context only when it hits a code-related question.
|
|
384
410
|
|
|
385
411
|
```python
|
|
386
412
|
from pathlib import Path
|
|
387
|
-
from typing import Literal
|
|
388
413
|
from langgraph.graph import StateGraph, MessagesState
|
|
389
414
|
from langgraph.prebuilt import ToolNode
|
|
390
415
|
from langchain_openai import ChatOpenAI
|
|
391
416
|
from langchain_core.tools import tool
|
|
392
|
-
from ctxgraph.graph.builder import
|
|
417
|
+
from ctxgraph.graph.builder import get_storage
|
|
393
418
|
from ctxgraph.capsule.renderer import render_capsule
|
|
394
419
|
|
|
395
|
-
# Pre-
|
|
396
|
-
|
|
397
|
-
storage = get_storage(Path("./my_project"))
|
|
420
|
+
# Pre-built graph — loaded instantly
|
|
421
|
+
_storage = get_storage(Path("./my_project"))
|
|
398
422
|
|
|
399
423
|
@tool
|
|
400
424
|
def code_context(task: str) -> str:
|
|
401
|
-
"""
|
|
402
|
-
|
|
425
|
+
"""Fetch relevant source code for a development task.
|
|
426
|
+
Use this whenever the user asks about implementation details,
|
|
427
|
+
bug fixes, or architecture in the codebase."""
|
|
428
|
+
return render_capsule(_storage, task, max_nodes=20)
|
|
403
429
|
|
|
430
|
+
# --- Build LangGraph ---
|
|
404
431
|
tools = [code_context]
|
|
405
|
-
tool_node = ToolNode(tools)
|
|
406
|
-
|
|
407
432
|
model = ChatOpenAI(model="gpt-4o", temperature=0).bind_tools(tools)
|
|
408
433
|
|
|
409
|
-
def
|
|
410
|
-
return "tools" if state["messages"][-1].tool_calls else "__end__"
|
|
411
|
-
|
|
412
|
-
def call_model(state: MessagesState):
|
|
434
|
+
def agent_node(state: MessagesState):
|
|
413
435
|
return {"messages": [model.invoke(state["messages"])]}
|
|
414
436
|
|
|
415
437
|
graph = StateGraph(MessagesState)
|
|
416
|
-
graph.add_node("agent",
|
|
417
|
-
graph.add_node("tools",
|
|
438
|
+
graph.add_node("agent", agent_node)
|
|
439
|
+
graph.add_node("tools", ToolNode(tools))
|
|
418
440
|
graph.set_entry_point("agent")
|
|
419
|
-
graph.add_conditional_edges(
|
|
441
|
+
graph.add_conditional_edges(
|
|
442
|
+
"agent",
|
|
443
|
+
lambda s: "tools" if s["messages"][-1].tool_calls else "__end__",
|
|
444
|
+
)
|
|
420
445
|
graph.add_edge("tools", "agent")
|
|
421
446
|
|
|
422
447
|
app = graph.compile()
|
|
423
448
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
449
|
+
# --- Run ---
|
|
450
|
+
for chunk in app.stream({"messages": [("user", "How does payment retry work?")]}):
|
|
451
|
+
for node, vals in chunk.items():
|
|
452
|
+
msg = vals["messages"][0]
|
|
453
|
+
if hasattr(msg, "content") and msg.content:
|
|
454
|
+
print(f"[{node}]: {msg.content[:300]}")
|
|
427
455
|
```
|
|
428
456
|
|
|
457
|
+
When the user asks about code, the agent calls `code_context("payment retry")`, gets back a capsule with `[F]src/payment/retry.py`, `[F]src/payment/processor.py`, and their dependency edges, then answers with those files in context.
|
|
458
|
+
|
|
429
459
|
### OpenAI Agents SDK
|
|
430
460
|
|
|
431
|
-
|
|
461
|
+
Same pattern — ctxgraph is a function tool the agent invokes.
|
|
432
462
|
|
|
433
463
|
```python
|
|
434
464
|
from pathlib import Path
|
|
435
|
-
from openai import AzureOpenAI # or OpenAI for standard API
|
|
436
465
|
from agents import Agent, Runner, function_tool
|
|
437
|
-
from ctxgraph.graph.builder import
|
|
466
|
+
from ctxgraph.graph.builder import get_storage
|
|
438
467
|
from ctxgraph.capsule.renderer import render_capsule
|
|
439
468
|
|
|
440
|
-
|
|
441
|
-
build_graph(Path("./my_project"))
|
|
442
|
-
storage = get_storage(Path("./my_project"))
|
|
469
|
+
_storage = get_storage(Path("./my_project"))
|
|
443
470
|
|
|
444
471
|
@function_tool
|
|
445
472
|
def fetch_code_context(task_description: str) -> str:
|
|
446
|
-
"""Retrieve
|
|
447
|
-
|
|
473
|
+
"""Retrieve code context from the project's knowledge graph.
|
|
474
|
+
Provide a task description like 'JWT auth middleware' or 'payment processor'."""
|
|
475
|
+
return render_capsule(_storage, task_description, max_nodes=20)
|
|
448
476
|
|
|
449
477
|
agent = Agent(
|
|
450
478
|
name="Code Assistant",
|
|
451
|
-
instructions="You
|
|
452
|
-
model="gpt-4o",
|
|
479
|
+
instructions="You help developers understand their codebase. Use fetch_code_context to get relevant files before answering.",
|
|
480
|
+
model="gpt-4o",
|
|
453
481
|
tools=[fetch_code_context],
|
|
454
482
|
)
|
|
455
483
|
|
|
456
|
-
result = Runner.run_sync(
|
|
457
|
-
agent,
|
|
458
|
-
"How does the JWT authentication middleware work?",
|
|
459
|
-
)
|
|
484
|
+
result = Runner.run_sync(agent, "How does the notification system handle email vs SMS?")
|
|
460
485
|
print(result.final_output)
|
|
461
486
|
```
|
|
462
487
|
|
|
463
|
-
### Azure OpenAI
|
|
488
|
+
### Azure OpenAI (direct client)
|
|
464
489
|
|
|
465
|
-
For Azure OpenAI,
|
|
490
|
+
For Azure OpenAI or any OpenAI-compatible endpoint, inject the capsule directly into the system message.
|
|
466
491
|
|
|
467
492
|
```python
|
|
468
493
|
import os
|
|
469
494
|
from openai import AzureOpenAI
|
|
470
495
|
from pathlib import Path
|
|
471
|
-
from ctxgraph.graph.builder import
|
|
496
|
+
from ctxgraph.graph.builder import get_storage
|
|
472
497
|
from ctxgraph.capsule.renderer import render_capsule
|
|
473
498
|
|
|
474
|
-
# Build graph
|
|
475
|
-
build_graph(Path("./my_project"))
|
|
476
499
|
storage = get_storage(Path("./my_project"))
|
|
477
|
-
|
|
478
|
-
# Generate context capsule
|
|
479
|
-
context = render_capsule(storage, "authentication and authorization", max_nodes=25)
|
|
500
|
+
context = render_capsule(storage, "event bus architecture", max_nodes=25)
|
|
480
501
|
|
|
481
502
|
client = AzureOpenAI(
|
|
482
503
|
api_version="2024-08-01-preview",
|
|
@@ -487,8 +508,8 @@ client = AzureOpenAI(
|
|
|
487
508
|
response = client.chat.completions.create(
|
|
488
509
|
model="gpt-4o", # deployment name
|
|
489
510
|
messages=[
|
|
490
|
-
{"role": "system", "content": f"You are a senior
|
|
491
|
-
{"role": "user", "content": "
|
|
511
|
+
{"role": "system", "content": f"You are a senior developer. Code context:\n\n{context}"},
|
|
512
|
+
{"role": "user", "content": "How do I add a new event handler?"},
|
|
492
513
|
],
|
|
493
514
|
)
|
|
494
515
|
print(response.choices[0].message.content)
|
|
@@ -241,7 +241,7 @@ max_depth = 2
|
|
|
241
241
|
| `AZURE_OPENAI_API_KEY` | Azure OpenAI API |
|
|
242
242
|
|
|
243
243
|
```bash
|
|
244
|
-
# Ollama (default)
|
|
244
|
+
# Ollama (default — no env vars needed)
|
|
245
245
|
ctx capsule "query"
|
|
246
246
|
|
|
247
247
|
# Claude
|
|
@@ -261,6 +261,17 @@ CTXGRAPH_PROVIDER=azure \
|
|
|
261
261
|
CTXGRAPH_PROVIDER=custom CTXGRAPH_ENDPOINT=http://my-api/v1 ctx capsule "query"
|
|
262
262
|
```
|
|
263
263
|
|
|
264
|
+
> **Windows (PowerShell):** Use `$env:` prefix instead:
|
|
265
|
+
> ```powershell
|
|
266
|
+
> $env:CTXGRAPH_PROVIDER = "azure"; $env:CTXGRAPH_MODEL = "gpt-4o"; ctx capsule "query"
|
|
267
|
+
> ```
|
|
268
|
+
> Or set them once per session:
|
|
269
|
+
> ```powershell
|
|
270
|
+
> $env:CTXGRAPH_PROVIDER = "azure"
|
|
271
|
+
> $env:AZURE_OPENAI_API_KEY = "sk-..."
|
|
272
|
+
> ctx capsule "query"
|
|
273
|
+
> ```
|
|
274
|
+
|
|
264
275
|
---
|
|
265
276
|
|
|
266
277
|
## Use Cases
|
|
@@ -290,9 +301,15 @@ ctx capsule "extract payment processing into separate module" --mode deep
|
|
|
290
301
|
|
|
291
302
|
## Framework Integrations
|
|
292
303
|
|
|
293
|
-
ctxgraph
|
|
304
|
+
ctxgraph is a Python library first — the CLI is just a wrapper. This makes it easy to feed code context into LangChain, LangGraph, OpenAI Agents, or any LLM pipeline.
|
|
305
|
+
|
|
306
|
+
### How the Python API works
|
|
307
|
+
|
|
308
|
+
The flow is always the same:
|
|
294
309
|
|
|
295
|
-
|
|
310
|
+
1. **`build_graph(path)`** → scans your code, stores a knowledge graph in `path/.ctxgraph/graph.db`
|
|
311
|
+
2. **`get_storage(path)`** → opens that SQLite database for queries (fast, no re-scanning)
|
|
312
|
+
3. **`render_capsule(storage, query)`** → searches the graph, returns a compact text capsule
|
|
296
313
|
|
|
297
314
|
```python
|
|
298
315
|
from pathlib import Path
|
|
@@ -300,154 +317,158 @@ from ctxgraph.graph.builder import build_graph, get_storage
|
|
|
300
317
|
from ctxgraph.capsule.renderer import render_capsule
|
|
301
318
|
from ctxgraph.graph.query import search_relevant_nodes
|
|
302
319
|
|
|
303
|
-
# 1
|
|
304
|
-
stats = build_graph(Path("/path/to/
|
|
320
|
+
# --- Step 1: Build (one-time, ~0.1-1s per project) ---
|
|
321
|
+
stats = build_graph(Path("/path/to/my_project"))
|
|
305
322
|
print(f"Built: {stats['total_nodes']} nodes, {stats['total_edges']} edges")
|
|
306
323
|
|
|
307
|
-
# 2
|
|
308
|
-
storage = get_storage(Path("/path/to/
|
|
324
|
+
# --- Step 2: Use (instant — reads the .db file) ---
|
|
325
|
+
storage = get_storage(Path("/path/to/my_project"))
|
|
309
326
|
|
|
310
|
-
#
|
|
327
|
+
# Generate a capsule — a token-efficient DSL string
|
|
311
328
|
capsule = render_capsule(storage, "fix JWT token validation", max_nodes=20)
|
|
312
329
|
print(capsule)
|
|
313
|
-
|
|
314
|
-
#
|
|
330
|
+
# → [CTX]fix JWT token validation
|
|
331
|
+
# [F]src/auth/jwt.py
|
|
332
|
+
# D:JWT token creation and validation
|
|
333
|
+
# [F]src/auth/middleware.py
|
|
334
|
+
# D:Auth middleware for request validation
|
|
335
|
+
# ...
|
|
336
|
+
|
|
337
|
+
# Or search for nodes programmatically
|
|
315
338
|
results = search_relevant_nodes(storage, "auth login", max_nodes=10, max_depth=2)
|
|
316
339
|
for node, score in results:
|
|
317
340
|
print(f" {node.type}:{node.name} (score={score})")
|
|
318
341
|
```
|
|
319
342
|
|
|
343
|
+
> **Tip:** `build_graph` is a one-time setup. In production, run `ctx build` during CI/deployment and let your app code only call `get_storage` + `render_capsule`.
|
|
344
|
+
|
|
320
345
|
### LangChain
|
|
321
346
|
|
|
322
|
-
|
|
347
|
+
Pass the capsule as context in your prompt template. The LLM gets exactly the files, classes, and dependencies it needs — no token waste.
|
|
323
348
|
|
|
324
349
|
```python
|
|
325
350
|
from pathlib import Path
|
|
326
351
|
from langchain_openai import ChatOpenAI
|
|
327
352
|
from langchain_core.prompts import ChatPromptTemplate
|
|
328
|
-
from ctxgraph.graph.builder import
|
|
353
|
+
from ctxgraph.graph.builder import get_storage # graph already built
|
|
329
354
|
from ctxgraph.capsule.renderer import render_capsule
|
|
330
355
|
|
|
331
|
-
#
|
|
332
|
-
build_graph(Path("./my_project"))
|
|
356
|
+
# Load existing graph (zero build time)
|
|
333
357
|
storage = get_storage(Path("./my_project"))
|
|
334
358
|
|
|
335
|
-
# Generate
|
|
336
|
-
context = render_capsule(storage, "
|
|
359
|
+
# Generate capsule for the question
|
|
360
|
+
context = render_capsule(storage, "login rate limiter", max_nodes=15)
|
|
337
361
|
|
|
338
362
|
prompt = ChatPromptTemplate.from_messages([
|
|
339
|
-
("system", "You are a senior Python
|
|
363
|
+
("system", "You are a senior Python dev. Answer using the code context below.\n\n{context}"),
|
|
340
364
|
("user", "{question}"),
|
|
341
365
|
])
|
|
342
366
|
|
|
343
367
|
llm = ChatOpenAI(model="gpt-4o")
|
|
344
|
-
|
|
368
|
+
response = prompt | llm | (lambda msg: msg.content)
|
|
345
369
|
|
|
346
|
-
response
|
|
370
|
+
print(response.invoke({
|
|
347
371
|
"context": context,
|
|
348
|
-
"question": "Where is the
|
|
349
|
-
})
|
|
372
|
+
"question": "Where is the rate limiter applied in the login flow?",
|
|
373
|
+
}))
|
|
374
|
+
# → "The rate limiter is in src/auth/middleware.py at line 42.
|
|
375
|
+
# It wraps the login endpoint with a 5req/min limit per IP."
|
|
350
376
|
```
|
|
351
377
|
|
|
352
378
|
### LangGraph
|
|
353
379
|
|
|
354
|
-
|
|
380
|
+
Expose ctxgraph as a tool the agent calls on-demand. The agent fetches context only when it hits a code-related question.
|
|
355
381
|
|
|
356
382
|
```python
|
|
357
383
|
from pathlib import Path
|
|
358
|
-
from typing import Literal
|
|
359
384
|
from langgraph.graph import StateGraph, MessagesState
|
|
360
385
|
from langgraph.prebuilt import ToolNode
|
|
361
386
|
from langchain_openai import ChatOpenAI
|
|
362
387
|
from langchain_core.tools import tool
|
|
363
|
-
from ctxgraph.graph.builder import
|
|
388
|
+
from ctxgraph.graph.builder import get_storage
|
|
364
389
|
from ctxgraph.capsule.renderer import render_capsule
|
|
365
390
|
|
|
366
|
-
# Pre-
|
|
367
|
-
|
|
368
|
-
storage = get_storage(Path("./my_project"))
|
|
391
|
+
# Pre-built graph — loaded instantly
|
|
392
|
+
_storage = get_storage(Path("./my_project"))
|
|
369
393
|
|
|
370
394
|
@tool
|
|
371
395
|
def code_context(task: str) -> str:
|
|
372
|
-
"""
|
|
373
|
-
|
|
396
|
+
"""Fetch relevant source code for a development task.
|
|
397
|
+
Use this whenever the user asks about implementation details,
|
|
398
|
+
bug fixes, or architecture in the codebase."""
|
|
399
|
+
return render_capsule(_storage, task, max_nodes=20)
|
|
374
400
|
|
|
401
|
+
# --- Build LangGraph ---
|
|
375
402
|
tools = [code_context]
|
|
376
|
-
tool_node = ToolNode(tools)
|
|
377
|
-
|
|
378
403
|
model = ChatOpenAI(model="gpt-4o", temperature=0).bind_tools(tools)
|
|
379
404
|
|
|
380
|
-
def
|
|
381
|
-
return "tools" if state["messages"][-1].tool_calls else "__end__"
|
|
382
|
-
|
|
383
|
-
def call_model(state: MessagesState):
|
|
405
|
+
def agent_node(state: MessagesState):
|
|
384
406
|
return {"messages": [model.invoke(state["messages"])]}
|
|
385
407
|
|
|
386
408
|
graph = StateGraph(MessagesState)
|
|
387
|
-
graph.add_node("agent",
|
|
388
|
-
graph.add_node("tools",
|
|
409
|
+
graph.add_node("agent", agent_node)
|
|
410
|
+
graph.add_node("tools", ToolNode(tools))
|
|
389
411
|
graph.set_entry_point("agent")
|
|
390
|
-
graph.add_conditional_edges(
|
|
412
|
+
graph.add_conditional_edges(
|
|
413
|
+
"agent",
|
|
414
|
+
lambda s: "tools" if s["messages"][-1].tool_calls else "__end__",
|
|
415
|
+
)
|
|
391
416
|
graph.add_edge("tools", "agent")
|
|
392
417
|
|
|
393
418
|
app = graph.compile()
|
|
394
419
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
420
|
+
# --- Run ---
|
|
421
|
+
for chunk in app.stream({"messages": [("user", "How does payment retry work?")]}):
|
|
422
|
+
for node, vals in chunk.items():
|
|
423
|
+
msg = vals["messages"][0]
|
|
424
|
+
if hasattr(msg, "content") and msg.content:
|
|
425
|
+
print(f"[{node}]: {msg.content[:300]}")
|
|
398
426
|
```
|
|
399
427
|
|
|
428
|
+
When the user asks about code, the agent calls `code_context("payment retry")`, gets back a capsule with `[F]src/payment/retry.py`, `[F]src/payment/processor.py`, and their dependency edges, then answers with those files in context.
|
|
429
|
+
|
|
400
430
|
### OpenAI Agents SDK
|
|
401
431
|
|
|
402
|
-
|
|
432
|
+
Same pattern — ctxgraph is a function tool the agent invokes.
|
|
403
433
|
|
|
404
434
|
```python
|
|
405
435
|
from pathlib import Path
|
|
406
|
-
from openai import AzureOpenAI # or OpenAI for standard API
|
|
407
436
|
from agents import Agent, Runner, function_tool
|
|
408
|
-
from ctxgraph.graph.builder import
|
|
437
|
+
from ctxgraph.graph.builder import get_storage
|
|
409
438
|
from ctxgraph.capsule.renderer import render_capsule
|
|
410
439
|
|
|
411
|
-
|
|
412
|
-
build_graph(Path("./my_project"))
|
|
413
|
-
storage = get_storage(Path("./my_project"))
|
|
440
|
+
_storage = get_storage(Path("./my_project"))
|
|
414
441
|
|
|
415
442
|
@function_tool
|
|
416
443
|
def fetch_code_context(task_description: str) -> str:
|
|
417
|
-
"""Retrieve
|
|
418
|
-
|
|
444
|
+
"""Retrieve code context from the project's knowledge graph.
|
|
445
|
+
Provide a task description like 'JWT auth middleware' or 'payment processor'."""
|
|
446
|
+
return render_capsule(_storage, task_description, max_nodes=20)
|
|
419
447
|
|
|
420
448
|
agent = Agent(
|
|
421
449
|
name="Code Assistant",
|
|
422
|
-
instructions="You
|
|
423
|
-
model="gpt-4o",
|
|
450
|
+
instructions="You help developers understand their codebase. Use fetch_code_context to get relevant files before answering.",
|
|
451
|
+
model="gpt-4o",
|
|
424
452
|
tools=[fetch_code_context],
|
|
425
453
|
)
|
|
426
454
|
|
|
427
|
-
result = Runner.run_sync(
|
|
428
|
-
agent,
|
|
429
|
-
"How does the JWT authentication middleware work?",
|
|
430
|
-
)
|
|
455
|
+
result = Runner.run_sync(agent, "How does the notification system handle email vs SMS?")
|
|
431
456
|
print(result.final_output)
|
|
432
457
|
```
|
|
433
458
|
|
|
434
|
-
### Azure OpenAI
|
|
459
|
+
### Azure OpenAI (direct client)
|
|
435
460
|
|
|
436
|
-
For Azure OpenAI,
|
|
461
|
+
For Azure OpenAI or any OpenAI-compatible endpoint, inject the capsule directly into the system message.
|
|
437
462
|
|
|
438
463
|
```python
|
|
439
464
|
import os
|
|
440
465
|
from openai import AzureOpenAI
|
|
441
466
|
from pathlib import Path
|
|
442
|
-
from ctxgraph.graph.builder import
|
|
467
|
+
from ctxgraph.graph.builder import get_storage
|
|
443
468
|
from ctxgraph.capsule.renderer import render_capsule
|
|
444
469
|
|
|
445
|
-
# Build graph
|
|
446
|
-
build_graph(Path("./my_project"))
|
|
447
470
|
storage = get_storage(Path("./my_project"))
|
|
448
|
-
|
|
449
|
-
# Generate context capsule
|
|
450
|
-
context = render_capsule(storage, "authentication and authorization", max_nodes=25)
|
|
471
|
+
context = render_capsule(storage, "event bus architecture", max_nodes=25)
|
|
451
472
|
|
|
452
473
|
client = AzureOpenAI(
|
|
453
474
|
api_version="2024-08-01-preview",
|
|
@@ -458,8 +479,8 @@ client = AzureOpenAI(
|
|
|
458
479
|
response = client.chat.completions.create(
|
|
459
480
|
model="gpt-4o", # deployment name
|
|
460
481
|
messages=[
|
|
461
|
-
{"role": "system", "content": f"You are a senior
|
|
462
|
-
{"role": "user", "content": "
|
|
482
|
+
{"role": "system", "content": f"You are a senior developer. Code context:\n\n{context}"},
|
|
483
|
+
{"role": "user", "content": "How do I add a new event handler?"},
|
|
463
484
|
],
|
|
464
485
|
)
|
|
465
486
|
print(response.choices[0].message.content)
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ctxgraph"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.4"
|
|
8
8
|
description = "AI context engine for Python — cuts LLM tokens 97% via code knowledge graphs. Build, query, and generate compact context capsules for Claude, OpenAI, Ollama."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ctxgraph
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: AI context engine for Python — cuts LLM tokens 97% via code knowledge graphs. Build, query, and generate compact context capsules for Claude, OpenAI, Ollama.
|
|
5
5
|
Author: ctxgraph contributors
|
|
6
6
|
License: MIT
|
|
@@ -270,7 +270,7 @@ max_depth = 2
|
|
|
270
270
|
| `AZURE_OPENAI_API_KEY` | Azure OpenAI API |
|
|
271
271
|
|
|
272
272
|
```bash
|
|
273
|
-
# Ollama (default)
|
|
273
|
+
# Ollama (default — no env vars needed)
|
|
274
274
|
ctx capsule "query"
|
|
275
275
|
|
|
276
276
|
# Claude
|
|
@@ -290,6 +290,17 @@ CTXGRAPH_PROVIDER=azure \
|
|
|
290
290
|
CTXGRAPH_PROVIDER=custom CTXGRAPH_ENDPOINT=http://my-api/v1 ctx capsule "query"
|
|
291
291
|
```
|
|
292
292
|
|
|
293
|
+
> **Windows (PowerShell):** Use `$env:` prefix instead:
|
|
294
|
+
> ```powershell
|
|
295
|
+
> $env:CTXGRAPH_PROVIDER = "azure"; $env:CTXGRAPH_MODEL = "gpt-4o"; ctx capsule "query"
|
|
296
|
+
> ```
|
|
297
|
+
> Or set them once per session:
|
|
298
|
+
> ```powershell
|
|
299
|
+
> $env:CTXGRAPH_PROVIDER = "azure"
|
|
300
|
+
> $env:AZURE_OPENAI_API_KEY = "sk-..."
|
|
301
|
+
> ctx capsule "query"
|
|
302
|
+
> ```
|
|
303
|
+
|
|
293
304
|
---
|
|
294
305
|
|
|
295
306
|
## Use Cases
|
|
@@ -319,9 +330,15 @@ ctx capsule "extract payment processing into separate module" --mode deep
|
|
|
319
330
|
|
|
320
331
|
## Framework Integrations
|
|
321
332
|
|
|
322
|
-
ctxgraph
|
|
333
|
+
ctxgraph is a Python library first — the CLI is just a wrapper. This makes it easy to feed code context into LangChain, LangGraph, OpenAI Agents, or any LLM pipeline.
|
|
334
|
+
|
|
335
|
+
### How the Python API works
|
|
336
|
+
|
|
337
|
+
The flow is always the same:
|
|
323
338
|
|
|
324
|
-
|
|
339
|
+
1. **`build_graph(path)`** → scans your code, stores a knowledge graph in `path/.ctxgraph/graph.db`
|
|
340
|
+
2. **`get_storage(path)`** → opens that SQLite database for queries (fast, no re-scanning)
|
|
341
|
+
3. **`render_capsule(storage, query)`** → searches the graph, returns a compact text capsule
|
|
325
342
|
|
|
326
343
|
```python
|
|
327
344
|
from pathlib import Path
|
|
@@ -329,154 +346,158 @@ from ctxgraph.graph.builder import build_graph, get_storage
|
|
|
329
346
|
from ctxgraph.capsule.renderer import render_capsule
|
|
330
347
|
from ctxgraph.graph.query import search_relevant_nodes
|
|
331
348
|
|
|
332
|
-
# 1
|
|
333
|
-
stats = build_graph(Path("/path/to/
|
|
349
|
+
# --- Step 1: Build (one-time, ~0.1-1s per project) ---
|
|
350
|
+
stats = build_graph(Path("/path/to/my_project"))
|
|
334
351
|
print(f"Built: {stats['total_nodes']} nodes, {stats['total_edges']} edges")
|
|
335
352
|
|
|
336
|
-
# 2
|
|
337
|
-
storage = get_storage(Path("/path/to/
|
|
353
|
+
# --- Step 2: Use (instant — reads the .db file) ---
|
|
354
|
+
storage = get_storage(Path("/path/to/my_project"))
|
|
338
355
|
|
|
339
|
-
#
|
|
356
|
+
# Generate a capsule — a token-efficient DSL string
|
|
340
357
|
capsule = render_capsule(storage, "fix JWT token validation", max_nodes=20)
|
|
341
358
|
print(capsule)
|
|
342
|
-
|
|
343
|
-
#
|
|
359
|
+
# → [CTX]fix JWT token validation
|
|
360
|
+
# [F]src/auth/jwt.py
|
|
361
|
+
# D:JWT token creation and validation
|
|
362
|
+
# [F]src/auth/middleware.py
|
|
363
|
+
# D:Auth middleware for request validation
|
|
364
|
+
# ...
|
|
365
|
+
|
|
366
|
+
# Or search for nodes programmatically
|
|
344
367
|
results = search_relevant_nodes(storage, "auth login", max_nodes=10, max_depth=2)
|
|
345
368
|
for node, score in results:
|
|
346
369
|
print(f" {node.type}:{node.name} (score={score})")
|
|
347
370
|
```
|
|
348
371
|
|
|
372
|
+
> **Tip:** `build_graph` is a one-time setup. In production, run `ctx build` during CI/deployment and let your app code only call `get_storage` + `render_capsule`.
|
|
373
|
+
|
|
349
374
|
### LangChain
|
|
350
375
|
|
|
351
|
-
|
|
376
|
+
Pass the capsule as context in your prompt template. The LLM gets exactly the files, classes, and dependencies it needs — no token waste.
|
|
352
377
|
|
|
353
378
|
```python
|
|
354
379
|
from pathlib import Path
|
|
355
380
|
from langchain_openai import ChatOpenAI
|
|
356
381
|
from langchain_core.prompts import ChatPromptTemplate
|
|
357
|
-
from ctxgraph.graph.builder import
|
|
382
|
+
from ctxgraph.graph.builder import get_storage # graph already built
|
|
358
383
|
from ctxgraph.capsule.renderer import render_capsule
|
|
359
384
|
|
|
360
|
-
#
|
|
361
|
-
build_graph(Path("./my_project"))
|
|
385
|
+
# Load existing graph (zero build time)
|
|
362
386
|
storage = get_storage(Path("./my_project"))
|
|
363
387
|
|
|
364
|
-
# Generate
|
|
365
|
-
context = render_capsule(storage, "
|
|
388
|
+
# Generate capsule for the question
|
|
389
|
+
context = render_capsule(storage, "login rate limiter", max_nodes=15)
|
|
366
390
|
|
|
367
391
|
prompt = ChatPromptTemplate.from_messages([
|
|
368
|
-
("system", "You are a senior Python
|
|
392
|
+
("system", "You are a senior Python dev. Answer using the code context below.\n\n{context}"),
|
|
369
393
|
("user", "{question}"),
|
|
370
394
|
])
|
|
371
395
|
|
|
372
396
|
llm = ChatOpenAI(model="gpt-4o")
|
|
373
|
-
|
|
397
|
+
response = prompt | llm | (lambda msg: msg.content)
|
|
374
398
|
|
|
375
|
-
response
|
|
399
|
+
print(response.invoke({
|
|
376
400
|
"context": context,
|
|
377
|
-
"question": "Where is the
|
|
378
|
-
})
|
|
401
|
+
"question": "Where is the rate limiter applied in the login flow?",
|
|
402
|
+
}))
|
|
403
|
+
# → "The rate limiter is in src/auth/middleware.py at line 42.
|
|
404
|
+
# It wraps the login endpoint with a 5req/min limit per IP."
|
|
379
405
|
```
|
|
380
406
|
|
|
381
407
|
### LangGraph
|
|
382
408
|
|
|
383
|
-
|
|
409
|
+
Expose ctxgraph as a tool the agent calls on-demand. The agent fetches context only when it hits a code-related question.
|
|
384
410
|
|
|
385
411
|
```python
|
|
386
412
|
from pathlib import Path
|
|
387
|
-
from typing import Literal
|
|
388
413
|
from langgraph.graph import StateGraph, MessagesState
|
|
389
414
|
from langgraph.prebuilt import ToolNode
|
|
390
415
|
from langchain_openai import ChatOpenAI
|
|
391
416
|
from langchain_core.tools import tool
|
|
392
|
-
from ctxgraph.graph.builder import
|
|
417
|
+
from ctxgraph.graph.builder import get_storage
|
|
393
418
|
from ctxgraph.capsule.renderer import render_capsule
|
|
394
419
|
|
|
395
|
-
# Pre-
|
|
396
|
-
|
|
397
|
-
storage = get_storage(Path("./my_project"))
|
|
420
|
+
# Pre-built graph — loaded instantly
|
|
421
|
+
_storage = get_storage(Path("./my_project"))
|
|
398
422
|
|
|
399
423
|
@tool
|
|
400
424
|
def code_context(task: str) -> str:
|
|
401
|
-
"""
|
|
402
|
-
|
|
425
|
+
"""Fetch relevant source code for a development task.
|
|
426
|
+
Use this whenever the user asks about implementation details,
|
|
427
|
+
bug fixes, or architecture in the codebase."""
|
|
428
|
+
return render_capsule(_storage, task, max_nodes=20)
|
|
403
429
|
|
|
430
|
+
# --- Build LangGraph ---
|
|
404
431
|
tools = [code_context]
|
|
405
|
-
tool_node = ToolNode(tools)
|
|
406
|
-
|
|
407
432
|
model = ChatOpenAI(model="gpt-4o", temperature=0).bind_tools(tools)
|
|
408
433
|
|
|
409
|
-
def
|
|
410
|
-
return "tools" if state["messages"][-1].tool_calls else "__end__"
|
|
411
|
-
|
|
412
|
-
def call_model(state: MessagesState):
|
|
434
|
+
def agent_node(state: MessagesState):
|
|
413
435
|
return {"messages": [model.invoke(state["messages"])]}
|
|
414
436
|
|
|
415
437
|
graph = StateGraph(MessagesState)
|
|
416
|
-
graph.add_node("agent",
|
|
417
|
-
graph.add_node("tools",
|
|
438
|
+
graph.add_node("agent", agent_node)
|
|
439
|
+
graph.add_node("tools", ToolNode(tools))
|
|
418
440
|
graph.set_entry_point("agent")
|
|
419
|
-
graph.add_conditional_edges(
|
|
441
|
+
graph.add_conditional_edges(
|
|
442
|
+
"agent",
|
|
443
|
+
lambda s: "tools" if s["messages"][-1].tool_calls else "__end__",
|
|
444
|
+
)
|
|
420
445
|
graph.add_edge("tools", "agent")
|
|
421
446
|
|
|
422
447
|
app = graph.compile()
|
|
423
448
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
449
|
+
# --- Run ---
|
|
450
|
+
for chunk in app.stream({"messages": [("user", "How does payment retry work?")]}):
|
|
451
|
+
for node, vals in chunk.items():
|
|
452
|
+
msg = vals["messages"][0]
|
|
453
|
+
if hasattr(msg, "content") and msg.content:
|
|
454
|
+
print(f"[{node}]: {msg.content[:300]}")
|
|
427
455
|
```
|
|
428
456
|
|
|
457
|
+
When the user asks about code, the agent calls `code_context("payment retry")`, gets back a capsule with `[F]src/payment/retry.py`, `[F]src/payment/processor.py`, and their dependency edges, then answers with those files in context.
|
|
458
|
+
|
|
429
459
|
### OpenAI Agents SDK
|
|
430
460
|
|
|
431
|
-
|
|
461
|
+
Same pattern — ctxgraph is a function tool the agent invokes.
|
|
432
462
|
|
|
433
463
|
```python
|
|
434
464
|
from pathlib import Path
|
|
435
|
-
from openai import AzureOpenAI # or OpenAI for standard API
|
|
436
465
|
from agents import Agent, Runner, function_tool
|
|
437
|
-
from ctxgraph.graph.builder import
|
|
466
|
+
from ctxgraph.graph.builder import get_storage
|
|
438
467
|
from ctxgraph.capsule.renderer import render_capsule
|
|
439
468
|
|
|
440
|
-
|
|
441
|
-
build_graph(Path("./my_project"))
|
|
442
|
-
storage = get_storage(Path("./my_project"))
|
|
469
|
+
_storage = get_storage(Path("./my_project"))
|
|
443
470
|
|
|
444
471
|
@function_tool
|
|
445
472
|
def fetch_code_context(task_description: str) -> str:
|
|
446
|
-
"""Retrieve
|
|
447
|
-
|
|
473
|
+
"""Retrieve code context from the project's knowledge graph.
|
|
474
|
+
Provide a task description like 'JWT auth middleware' or 'payment processor'."""
|
|
475
|
+
return render_capsule(_storage, task_description, max_nodes=20)
|
|
448
476
|
|
|
449
477
|
agent = Agent(
|
|
450
478
|
name="Code Assistant",
|
|
451
|
-
instructions="You
|
|
452
|
-
model="gpt-4o",
|
|
479
|
+
instructions="You help developers understand their codebase. Use fetch_code_context to get relevant files before answering.",
|
|
480
|
+
model="gpt-4o",
|
|
453
481
|
tools=[fetch_code_context],
|
|
454
482
|
)
|
|
455
483
|
|
|
456
|
-
result = Runner.run_sync(
|
|
457
|
-
agent,
|
|
458
|
-
"How does the JWT authentication middleware work?",
|
|
459
|
-
)
|
|
484
|
+
result = Runner.run_sync(agent, "How does the notification system handle email vs SMS?")
|
|
460
485
|
print(result.final_output)
|
|
461
486
|
```
|
|
462
487
|
|
|
463
|
-
### Azure OpenAI
|
|
488
|
+
### Azure OpenAI (direct client)
|
|
464
489
|
|
|
465
|
-
For Azure OpenAI,
|
|
490
|
+
For Azure OpenAI or any OpenAI-compatible endpoint, inject the capsule directly into the system message.
|
|
466
491
|
|
|
467
492
|
```python
|
|
468
493
|
import os
|
|
469
494
|
from openai import AzureOpenAI
|
|
470
495
|
from pathlib import Path
|
|
471
|
-
from ctxgraph.graph.builder import
|
|
496
|
+
from ctxgraph.graph.builder import get_storage
|
|
472
497
|
from ctxgraph.capsule.renderer import render_capsule
|
|
473
498
|
|
|
474
|
-
# Build graph
|
|
475
|
-
build_graph(Path("./my_project"))
|
|
476
499
|
storage = get_storage(Path("./my_project"))
|
|
477
|
-
|
|
478
|
-
# Generate context capsule
|
|
479
|
-
context = render_capsule(storage, "authentication and authorization", max_nodes=25)
|
|
500
|
+
context = render_capsule(storage, "event bus architecture", max_nodes=25)
|
|
480
501
|
|
|
481
502
|
client = AzureOpenAI(
|
|
482
503
|
api_version="2024-08-01-preview",
|
|
@@ -487,8 +508,8 @@ client = AzureOpenAI(
|
|
|
487
508
|
response = client.chat.completions.create(
|
|
488
509
|
model="gpt-4o", # deployment name
|
|
489
510
|
messages=[
|
|
490
|
-
{"role": "system", "content": f"You are a senior
|
|
491
|
-
{"role": "user", "content": "
|
|
511
|
+
{"role": "system", "content": f"You are a senior developer. Code context:\n\n{context}"},
|
|
512
|
+
{"role": "user", "content": "How do I add a new event handler?"},
|
|
492
513
|
],
|
|
493
514
|
)
|
|
494
515
|
print(response.choices[0].message.content)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|