ctxgraph 0.2.0__tar.gz → 0.2.2__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.
Files changed (47) hide show
  1. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/PKG-INFO +192 -3
  2. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/README.md +191 -2
  3. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/pyproject.toml +1 -1
  4. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/src/ctxgraph/cli/main.py +16 -7
  5. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/src/ctxgraph/config/providers.py +34 -0
  6. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/src/ctxgraph/config/settings.py +13 -2
  7. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/src/ctxgraph/view/visualizer.py +114 -0
  8. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/src/ctxgraph.egg-info/PKG-INFO +192 -3
  9. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/setup.cfg +0 -0
  10. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/src/ctxgraph/__init__.py +0 -0
  11. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/src/ctxgraph/analyzers/__init__.py +0 -0
  12. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/src/ctxgraph/analyzers/python/__init__.py +0 -0
  13. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/src/ctxgraph/analyzers/python/importer.py +0 -0
  14. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/src/ctxgraph/analyzers/python/semantic.py +0 -0
  15. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/src/ctxgraph/analyzers/python/symbols.py +0 -0
  16. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/src/ctxgraph/capsule/__init__.py +0 -0
  17. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/src/ctxgraph/capsule/renderer.py +0 -0
  18. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/src/ctxgraph/cli/__init__.py +0 -0
  19. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/src/ctxgraph/clients/__init__.py +0 -0
  20. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/src/ctxgraph/clients/models.py +0 -0
  21. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/src/ctxgraph/config/__init__.py +0 -0
  22. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/src/ctxgraph/exclude/__init__.py +0 -0
  23. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/src/ctxgraph/exclude/patterns.py +0 -0
  24. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/src/ctxgraph/graph/__init__.py +0 -0
  25. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/src/ctxgraph/graph/builder.py +0 -0
  26. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/src/ctxgraph/graph/models.py +0 -0
  27. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/src/ctxgraph/graph/query.py +0 -0
  28. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/src/ctxgraph/graph/storage.py +0 -0
  29. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/src/ctxgraph/mcp/__init__.py +0 -0
  30. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/src/ctxgraph/mcp/server.py +0 -0
  31. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/src/ctxgraph/view/__init__.py +0 -0
  32. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/src/ctxgraph/wrapper/__init__.py +0 -0
  33. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/src/ctxgraph/wrapper/claude.py +0 -0
  34. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/src/ctxgraph.egg-info/SOURCES.txt +0 -0
  35. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/src/ctxgraph.egg-info/dependency_links.txt +0 -0
  36. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/src/ctxgraph.egg-info/entry_points.txt +0 -0
  37. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/src/ctxgraph.egg-info/requires.txt +0 -0
  38. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/src/ctxgraph.egg-info/top_level.txt +0 -0
  39. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/tests/test_analyzers.py +0 -0
  40. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/tests/test_benchmark.py +0 -0
  41. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/tests/test_capsule.py +0 -0
  42. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/tests/test_config.py +0 -0
  43. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/tests/test_integration.py +0 -0
  44. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/tests/test_model_mode.py +0 -0
  45. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/tests/test_models.py +0 -0
  46. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/tests/test_query.py +0 -0
  47. {ctxgraph-0.2.0 → ctxgraph-0.2.2}/tests/test_storage.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ctxgraph
3
- Version: 0.2.0
3
+ Version: 0.2.2
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
@@ -37,9 +37,11 @@ pip install ctxgraph
37
37
  ctx build # Build knowledge graph
38
38
  ctx capsule "fix JWT expiry" # 92-99% fewer tokens vs raw code
39
39
  ccg "fix the login redirect bug" # Launch Claude with context pre-loaded
40
- ctx view # Interactive D3.js visualization
40
+ ctx view # Interactive D3.js visualization (or --svg for static)
41
41
  ```
42
42
 
43
+ <img src="https://raw.githubusercontent.com/shashi3070/ctxgraph/master/docs/graph.svg" alt="ctxgraph knowledge graph visualization" width="100%">
44
+
43
45
  ---
44
46
 
45
47
  ## Why ctxgraph?
@@ -248,7 +250,7 @@ ccg --mode deep "redesign the database schema" # Deep mode
248
250
  exclude = ["legacy/*", "vendor/*"]
249
251
 
250
252
  [ai]
251
- provider = "ollama" # ollama, claude, openai, custom
253
+ provider = "ollama" # ollama, claude, openai, azure, custom
252
254
  model = "qwen2.5-coder:7b"
253
255
  endpoint = "http://localhost:11434"
254
256
 
@@ -265,6 +267,7 @@ max_depth = 2
265
267
  | `CTXGRAPH_ENDPOINT` | `ai.endpoint` |
266
268
  | `ANTHROPIC_API_KEY` | Claude API |
267
269
  | `OPENAI_API_KEY` | OpenAI API |
270
+ | `AZURE_OPENAI_API_KEY` | Azure OpenAI API |
268
271
 
269
272
  ```bash
270
273
  # Ollama (default)
@@ -276,6 +279,13 @@ CTXGRAPH_PROVIDER=claude CTXGRAPH_MODEL=claude-sonnet-4-20250514 ctx capsule "qu
276
279
  # OpenAI
277
280
  CTXGRAPH_PROVIDER=openai CTXGRAPH_MODEL=gpt-4o ctx capsule "query"
278
281
 
282
+ # Azure OpenAI
283
+ CTXGRAPH_PROVIDER=azure \
284
+ CTXGRAPH_MODEL=gpt-4o \
285
+ CTXGRAPH_ENDPOINT=https://my-resource.openai.azure.com \
286
+ AZURE_OPENAI_API_KEY=sk-... \
287
+ ctx capsule "query"
288
+
279
289
  # Custom (OpenAI-compatible)
280
290
  CTXGRAPH_PROVIDER=custom CTXGRAPH_ENDPOINT=http://my-api/v1 ctx capsule "query"
281
291
  ```
@@ -307,6 +317,185 @@ ctx capsule "extract payment processing into separate module" --mode deep
307
317
 
308
318
  ---
309
319
 
320
+ ## Framework Integrations
321
+
322
+ ctxgraph can be used as a Python library — not just a CLI. This makes it easy to plug into LangChain, LangGraph, OpenAI Agents, or any custom AI pipeline.
323
+
324
+ ### Python API
325
+
326
+ ```python
327
+ from pathlib import Path
328
+ from ctxgraph.graph.builder import build_graph, get_storage
329
+ from ctxgraph.capsule.renderer import render_capsule
330
+ from ctxgraph.graph.query import search_relevant_nodes
331
+
332
+ # 1. Build the graph (one-time setup)
333
+ stats = build_graph(Path("/path/to/project"))
334
+ print(f"Built: {stats['total_nodes']} nodes, {stats['total_edges']} edges")
335
+
336
+ # 2. Get storage for an existing graph
337
+ storage = get_storage(Path("/path/to/project"))
338
+
339
+ # 3. Generate a context capsule (token-efficient text)
340
+ capsule = render_capsule(storage, "fix JWT token validation", max_nodes=20)
341
+ print(capsule)
342
+
343
+ # 4. Search for relevant nodes programmatically
344
+ results = search_relevant_nodes(storage, "auth login", max_nodes=10, max_depth=2)
345
+ for node, score in results:
346
+ print(f" {node.type}:{node.name} (score={score})")
347
+ ```
348
+
349
+ ### LangChain
350
+
351
+ Inject ctxgraph capsules directly into your LangChain prompts — dramatically reducing token usage while providing precise code context.
352
+
353
+ ```python
354
+ from pathlib import Path
355
+ from langchain_openai import ChatOpenAI
356
+ from langchain_core.prompts import ChatPromptTemplate
357
+ from ctxgraph.graph.builder import build_graph, get_storage
358
+ from ctxgraph.capsule.renderer import render_capsule
359
+
360
+ # Build graph once
361
+ build_graph(Path("./my_project"))
362
+ storage = get_storage(Path("./my_project"))
363
+
364
+ # Generate context for a specific task
365
+ context = render_capsule(storage, "user authentication flow", max_nodes=20)
366
+
367
+ prompt = ChatPromptTemplate.from_messages([
368
+ ("system", "You are a senior Python developer. Use the code context below to answer the question.\n\n{context}"),
369
+ ("user", "{question}"),
370
+ ])
371
+
372
+ llm = ChatOpenAI(model="gpt-4o")
373
+ chain = prompt | llm
374
+
375
+ response = chain.invoke({
376
+ "context": context,
377
+ "question": "Where is the login rate limiter implemented?",
378
+ })
379
+ ```
380
+
381
+ ### LangGraph
382
+
383
+ Use ctxgraph as a tool within a LangGraph agent — the agent requests context capsules when it needs to understand the codebase.
384
+
385
+ ```python
386
+ from pathlib import Path
387
+ from typing import Literal
388
+ from langgraph.graph import StateGraph, MessagesState
389
+ from langgraph.prebuilt import ToolNode
390
+ from langchain_openai import ChatOpenAI
391
+ from langchain_core.tools import tool
392
+ from ctxgraph.graph.builder import build_graph, get_storage
393
+ from ctxgraph.capsule.renderer import render_capsule
394
+
395
+ # Pre-build graph
396
+ build_graph(Path("./my_project"))
397
+ storage = get_storage(Path("./my_project"))
398
+
399
+ @tool
400
+ def code_context(task: str) -> str:
401
+ """Get code context relevant to a task. Use this before answering code questions."""
402
+ return render_capsule(storage, task, max_nodes=20)
403
+
404
+ tools = [code_context]
405
+ tool_node = ToolNode(tools)
406
+
407
+ model = ChatOpenAI(model="gpt-4o", temperature=0).bind_tools(tools)
408
+
409
+ def should_continue(state: MessagesState) -> Literal["tools", "__end__"]:
410
+ return "tools" if state["messages"][-1].tool_calls else "__end__"
411
+
412
+ def call_model(state: MessagesState):
413
+ return {"messages": [model.invoke(state["messages"])]}
414
+
415
+ graph = StateGraph(MessagesState)
416
+ graph.add_node("agent", call_model)
417
+ graph.add_node("tools", tool_node)
418
+ graph.set_entry_point("agent")
419
+ graph.add_conditional_edges("agent", should_continue)
420
+ graph.add_edge("tools", "agent")
421
+
422
+ app = graph.compile()
423
+
424
+ for chunk in app.stream({"messages": [("user", "Find the bug in the payment processor")]}):
425
+ for node, msg in chunk.items():
426
+ print(f"[{node}]: {msg['messages'][0].content[:200] if msg.get('messages') else ''}")
427
+ ```
428
+
429
+ ### OpenAI Agents SDK
430
+
431
+ Use ctxgraph with the official OpenAI Agents SDK (also works with Azure OpenAI via `AzureOpenAIChatCompletionAgent`).
432
+
433
+ ```python
434
+ from pathlib import Path
435
+ from openai import AzureOpenAI # or OpenAI for standard API
436
+ from agents import Agent, Runner, function_tool
437
+ from ctxgraph.graph.builder import build_graph, get_storage
438
+ from ctxgraph.capsule.renderer import render_capsule
439
+
440
+ # Pre-build the graph
441
+ build_graph(Path("./my_project"))
442
+ storage = get_storage(Path("./my_project"))
443
+
444
+ @function_tool
445
+ def fetch_code_context(task_description: str) -> str:
446
+ """Retrieve relevant code context for a development task."""
447
+ return render_capsule(storage, task_description, max_nodes=20)
448
+
449
+ agent = Agent(
450
+ name="Code Assistant",
451
+ instructions="You are a helpful coding assistant. Use the code context tool to understand the codebase before answering.",
452
+ model="gpt-4o", # or AzureOpenAIChatCompletionAgent(deployment="gpt-4o", ...)
453
+ tools=[fetch_code_context],
454
+ )
455
+
456
+ result = Runner.run_sync(
457
+ agent,
458
+ "How does the JWT authentication middleware work?",
459
+ )
460
+ print(result.final_output)
461
+ ```
462
+
463
+ ### Azure OpenAI with Custom Agent
464
+
465
+ For Azure OpenAI, configure the client directly and inject ctxgraph context:
466
+
467
+ ```python
468
+ import os
469
+ from openai import AzureOpenAI
470
+ from pathlib import Path
471
+ from ctxgraph.graph.builder import build_graph, get_storage
472
+ from ctxgraph.capsule.renderer import render_capsule
473
+
474
+ # Build graph
475
+ build_graph(Path("./my_project"))
476
+ storage = get_storage(Path("./my_project"))
477
+
478
+ # Generate context capsule
479
+ context = render_capsule(storage, "authentication and authorization", max_nodes=25)
480
+
481
+ client = AzureOpenAI(
482
+ api_version="2024-08-01-preview",
483
+ azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
484
+ api_key=os.environ["AZURE_OPENAI_API_KEY"],
485
+ )
486
+
487
+ response = client.chat.completions.create(
488
+ model="gpt-4o", # deployment name
489
+ messages=[
490
+ {"role": "system", "content": f"You are a senior Python developer. Use the code context below.\n\n{context}"},
491
+ {"role": "user", "content": "Explain the role-based access control (RBAC) implementation."},
492
+ ],
493
+ )
494
+ print(response.choices[0].message.content)
495
+ ```
496
+
497
+ ---
498
+
310
499
  ## Development
311
500
 
312
501
  ```bash
@@ -8,9 +8,11 @@ pip install ctxgraph
8
8
  ctx build # Build knowledge graph
9
9
  ctx capsule "fix JWT expiry" # 92-99% fewer tokens vs raw code
10
10
  ccg "fix the login redirect bug" # Launch Claude with context pre-loaded
11
- ctx view # Interactive D3.js visualization
11
+ ctx view # Interactive D3.js visualization (or --svg for static)
12
12
  ```
13
13
 
14
+ <img src="https://raw.githubusercontent.com/shashi3070/ctxgraph/master/docs/graph.svg" alt="ctxgraph knowledge graph visualization" width="100%">
15
+
14
16
  ---
15
17
 
16
18
  ## Why ctxgraph?
@@ -219,7 +221,7 @@ ccg --mode deep "redesign the database schema" # Deep mode
219
221
  exclude = ["legacy/*", "vendor/*"]
220
222
 
221
223
  [ai]
222
- provider = "ollama" # ollama, claude, openai, custom
224
+ provider = "ollama" # ollama, claude, openai, azure, custom
223
225
  model = "qwen2.5-coder:7b"
224
226
  endpoint = "http://localhost:11434"
225
227
 
@@ -236,6 +238,7 @@ max_depth = 2
236
238
  | `CTXGRAPH_ENDPOINT` | `ai.endpoint` |
237
239
  | `ANTHROPIC_API_KEY` | Claude API |
238
240
  | `OPENAI_API_KEY` | OpenAI API |
241
+ | `AZURE_OPENAI_API_KEY` | Azure OpenAI API |
239
242
 
240
243
  ```bash
241
244
  # Ollama (default)
@@ -247,6 +250,13 @@ CTXGRAPH_PROVIDER=claude CTXGRAPH_MODEL=claude-sonnet-4-20250514 ctx capsule "qu
247
250
  # OpenAI
248
251
  CTXGRAPH_PROVIDER=openai CTXGRAPH_MODEL=gpt-4o ctx capsule "query"
249
252
 
253
+ # Azure OpenAI
254
+ CTXGRAPH_PROVIDER=azure \
255
+ CTXGRAPH_MODEL=gpt-4o \
256
+ CTXGRAPH_ENDPOINT=https://my-resource.openai.azure.com \
257
+ AZURE_OPENAI_API_KEY=sk-... \
258
+ ctx capsule "query"
259
+
250
260
  # Custom (OpenAI-compatible)
251
261
  CTXGRAPH_PROVIDER=custom CTXGRAPH_ENDPOINT=http://my-api/v1 ctx capsule "query"
252
262
  ```
@@ -278,6 +288,185 @@ ctx capsule "extract payment processing into separate module" --mode deep
278
288
 
279
289
  ---
280
290
 
291
+ ## Framework Integrations
292
+
293
+ ctxgraph can be used as a Python library — not just a CLI. This makes it easy to plug into LangChain, LangGraph, OpenAI Agents, or any custom AI pipeline.
294
+
295
+ ### Python API
296
+
297
+ ```python
298
+ from pathlib import Path
299
+ from ctxgraph.graph.builder import build_graph, get_storage
300
+ from ctxgraph.capsule.renderer import render_capsule
301
+ from ctxgraph.graph.query import search_relevant_nodes
302
+
303
+ # 1. Build the graph (one-time setup)
304
+ stats = build_graph(Path("/path/to/project"))
305
+ print(f"Built: {stats['total_nodes']} nodes, {stats['total_edges']} edges")
306
+
307
+ # 2. Get storage for an existing graph
308
+ storage = get_storage(Path("/path/to/project"))
309
+
310
+ # 3. Generate a context capsule (token-efficient text)
311
+ capsule = render_capsule(storage, "fix JWT token validation", max_nodes=20)
312
+ print(capsule)
313
+
314
+ # 4. Search for relevant nodes programmatically
315
+ results = search_relevant_nodes(storage, "auth login", max_nodes=10, max_depth=2)
316
+ for node, score in results:
317
+ print(f" {node.type}:{node.name} (score={score})")
318
+ ```
319
+
320
+ ### LangChain
321
+
322
+ Inject ctxgraph capsules directly into your LangChain prompts — dramatically reducing token usage while providing precise code context.
323
+
324
+ ```python
325
+ from pathlib import Path
326
+ from langchain_openai import ChatOpenAI
327
+ from langchain_core.prompts import ChatPromptTemplate
328
+ from ctxgraph.graph.builder import build_graph, get_storage
329
+ from ctxgraph.capsule.renderer import render_capsule
330
+
331
+ # Build graph once
332
+ build_graph(Path("./my_project"))
333
+ storage = get_storage(Path("./my_project"))
334
+
335
+ # Generate context for a specific task
336
+ context = render_capsule(storage, "user authentication flow", max_nodes=20)
337
+
338
+ prompt = ChatPromptTemplate.from_messages([
339
+ ("system", "You are a senior Python developer. Use the code context below to answer the question.\n\n{context}"),
340
+ ("user", "{question}"),
341
+ ])
342
+
343
+ llm = ChatOpenAI(model="gpt-4o")
344
+ chain = prompt | llm
345
+
346
+ response = chain.invoke({
347
+ "context": context,
348
+ "question": "Where is the login rate limiter implemented?",
349
+ })
350
+ ```
351
+
352
+ ### LangGraph
353
+
354
+ Use ctxgraph as a tool within a LangGraph agent — the agent requests context capsules when it needs to understand the codebase.
355
+
356
+ ```python
357
+ from pathlib import Path
358
+ from typing import Literal
359
+ from langgraph.graph import StateGraph, MessagesState
360
+ from langgraph.prebuilt import ToolNode
361
+ from langchain_openai import ChatOpenAI
362
+ from langchain_core.tools import tool
363
+ from ctxgraph.graph.builder import build_graph, get_storage
364
+ from ctxgraph.capsule.renderer import render_capsule
365
+
366
+ # Pre-build graph
367
+ build_graph(Path("./my_project"))
368
+ storage = get_storage(Path("./my_project"))
369
+
370
+ @tool
371
+ def code_context(task: str) -> str:
372
+ """Get code context relevant to a task. Use this before answering code questions."""
373
+ return render_capsule(storage, task, max_nodes=20)
374
+
375
+ tools = [code_context]
376
+ tool_node = ToolNode(tools)
377
+
378
+ model = ChatOpenAI(model="gpt-4o", temperature=0).bind_tools(tools)
379
+
380
+ def should_continue(state: MessagesState) -> Literal["tools", "__end__"]:
381
+ return "tools" if state["messages"][-1].tool_calls else "__end__"
382
+
383
+ def call_model(state: MessagesState):
384
+ return {"messages": [model.invoke(state["messages"])]}
385
+
386
+ graph = StateGraph(MessagesState)
387
+ graph.add_node("agent", call_model)
388
+ graph.add_node("tools", tool_node)
389
+ graph.set_entry_point("agent")
390
+ graph.add_conditional_edges("agent", should_continue)
391
+ graph.add_edge("tools", "agent")
392
+
393
+ app = graph.compile()
394
+
395
+ for chunk in app.stream({"messages": [("user", "Find the bug in the payment processor")]}):
396
+ for node, msg in chunk.items():
397
+ print(f"[{node}]: {msg['messages'][0].content[:200] if msg.get('messages') else ''}")
398
+ ```
399
+
400
+ ### OpenAI Agents SDK
401
+
402
+ Use ctxgraph with the official OpenAI Agents SDK (also works with Azure OpenAI via `AzureOpenAIChatCompletionAgent`).
403
+
404
+ ```python
405
+ from pathlib import Path
406
+ from openai import AzureOpenAI # or OpenAI for standard API
407
+ from agents import Agent, Runner, function_tool
408
+ from ctxgraph.graph.builder import build_graph, get_storage
409
+ from ctxgraph.capsule.renderer import render_capsule
410
+
411
+ # Pre-build the graph
412
+ build_graph(Path("./my_project"))
413
+ storage = get_storage(Path("./my_project"))
414
+
415
+ @function_tool
416
+ def fetch_code_context(task_description: str) -> str:
417
+ """Retrieve relevant code context for a development task."""
418
+ return render_capsule(storage, task_description, max_nodes=20)
419
+
420
+ agent = Agent(
421
+ name="Code Assistant",
422
+ instructions="You are a helpful coding assistant. Use the code context tool to understand the codebase before answering.",
423
+ model="gpt-4o", # or AzureOpenAIChatCompletionAgent(deployment="gpt-4o", ...)
424
+ tools=[fetch_code_context],
425
+ )
426
+
427
+ result = Runner.run_sync(
428
+ agent,
429
+ "How does the JWT authentication middleware work?",
430
+ )
431
+ print(result.final_output)
432
+ ```
433
+
434
+ ### Azure OpenAI with Custom Agent
435
+
436
+ For Azure OpenAI, configure the client directly and inject ctxgraph context:
437
+
438
+ ```python
439
+ import os
440
+ from openai import AzureOpenAI
441
+ from pathlib import Path
442
+ from ctxgraph.graph.builder import build_graph, get_storage
443
+ from ctxgraph.capsule.renderer import render_capsule
444
+
445
+ # Build graph
446
+ build_graph(Path("./my_project"))
447
+ storage = get_storage(Path("./my_project"))
448
+
449
+ # Generate context capsule
450
+ context = render_capsule(storage, "authentication and authorization", max_nodes=25)
451
+
452
+ client = AzureOpenAI(
453
+ api_version="2024-08-01-preview",
454
+ azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
455
+ api_key=os.environ["AZURE_OPENAI_API_KEY"],
456
+ )
457
+
458
+ response = client.chat.completions.create(
459
+ model="gpt-4o", # deployment name
460
+ messages=[
461
+ {"role": "system", "content": f"You are a senior Python developer. Use the code context below.\n\n{context}"},
462
+ {"role": "user", "content": "Explain the role-based access control (RBAC) implementation."},
463
+ ],
464
+ )
465
+ print(response.choices[0].message.content)
466
+ ```
467
+
468
+ ---
469
+
281
470
  ## Development
282
471
 
283
472
  ```bash
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ctxgraph"
7
- version = "0.2.0"
7
+ version = "0.2.2"
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"}
@@ -160,14 +160,17 @@ def view(
160
160
  ),
161
161
  port: Optional[int] = typer.Option(None, "--port", "-p", help="Port for server"),
162
162
  output: Optional[str] = typer.Option(
163
- None, "--output", "-o", help="Save HTML to file"
163
+ None, "--output", "-o", help="Save HTML/SVG to file"
164
164
  ),
165
165
  open_browser: bool = typer.Option(
166
166
  True, "--open/--no-open", help="Open in browser automatically"
167
167
  ),
168
+ svg: bool = typer.Option(
169
+ False, "--svg", help="Generate static SVG instead of interactive HTML"
170
+ ),
168
171
  ):
169
172
  """Visualize the dependency graph in a browser."""
170
- from ctxgraph.view.visualizer import render_view
173
+ from ctxgraph.view.visualizer import render_view, render_svg
171
174
 
172
175
  path = Path(repo_path).resolve() if repo_path else Path.cwd()
173
176
  storage = get_storage(path)
@@ -177,19 +180,25 @@ def view(
177
180
  )
178
181
  raise typer.Exit(1)
179
182
 
180
- html = render_view(storage)
183
+ if svg:
184
+ content = render_svg(storage)
185
+ suffix = ".svg"
186
+ else:
187
+ content = render_view(storage)
188
+ suffix = ".html"
181
189
 
182
190
  if output:
183
191
  out_path = Path(output)
184
- out_path.write_text(html, encoding="utf-8")
192
+ out_path.write_text(content, encoding="utf-8")
185
193
  console.print(f"Saved to [bold]{out_path}[/bold]")
186
194
  else:
187
- out_path = path / ".ctxgraph" / "graph.html"
195
+ filename = f"graph{suffix}"
196
+ out_path = path / ".ctxgraph" / filename
188
197
  out_path.parent.mkdir(parents=True, exist_ok=True)
189
- out_path.write_text(html, encoding="utf-8")
198
+ out_path.write_text(content, encoding="utf-8")
190
199
  console.print(f"Saved to [bold]{out_path}[/bold]")
191
200
 
192
- if open_browser:
201
+ if open_browser and not svg:
193
202
  import webbrowser
194
203
 
195
204
  webbrowser.open(f"file://{out_path.absolute()}")
@@ -21,6 +21,8 @@ def chat_completion(
21
21
  return _claude_chat(settings, system_prompt, user_prompt)
22
22
  elif provider == "openai":
23
23
  return _openai_chat(settings, system_prompt, user_prompt)
24
+ elif provider == "azure":
25
+ return _azure_chat(settings, system_prompt, user_prompt)
24
26
  else:
25
27
  return _custom_chat(settings, system_prompt, user_prompt)
26
28
 
@@ -125,6 +127,38 @@ def _openai_chat(settings: Settings, system: str, user: str) -> Optional[str]:
125
127
  return None
126
128
 
127
129
 
130
+ def _azure_chat(settings: Settings, system: str, user: str) -> Optional[str]:
131
+ api_key = settings.api_key
132
+ if not api_key:
133
+ return None
134
+
135
+ url = settings.get_chat_url()
136
+ payload = {
137
+ "messages": [
138
+ {"role": "system", "content": system},
139
+ {"role": "user", "content": user},
140
+ ],
141
+ "max_tokens": settings.max_tokens,
142
+ "temperature": settings.temperature,
143
+ }
144
+
145
+ try:
146
+ data = json.dumps(payload).encode()
147
+ req = urllib.request.Request(
148
+ url,
149
+ data=data,
150
+ headers={
151
+ "Content-Type": "application/json",
152
+ "api-key": api_key,
153
+ },
154
+ )
155
+ with urllib.request.urlopen(req, timeout=60) as resp:
156
+ result = json.loads(resp.read())
157
+ return result.get("choices", [{}])[0].get("message", {}).get("content", "")
158
+ except (urllib.error.URLError, json.JSONDecodeError, TimeoutError):
159
+ return None
160
+
161
+
128
162
  def _custom_chat(settings: Settings, system: str, user: str) -> Optional[str]:
129
163
  url = settings.get_chat_url()
130
164
  payload = {
@@ -50,6 +50,14 @@ PROVIDER_CONFIGS = {
50
50
  "chat_endpoint": "/chat/completions",
51
51
  "api_key_env": "OPENAI_API_KEY",
52
52
  },
53
+ "azure": {
54
+ "endpoint_default": "https://YOUR_RESOURCE.openai.azure.com",
55
+ "api_key_required": True,
56
+ "models": ["gpt-4o", "gpt-4o-mini"],
57
+ "chat_endpoint": "/openai/deployments/{deployment}/chat/completions?api-version=2024-08-01-preview",
58
+ "api_key_env": "AZURE_OPENAI_API_KEY",
59
+ "api_key_header": "api-key",
60
+ },
53
61
  "custom": {
54
62
  "endpoint_default": None,
55
63
  "api_key_required": False,
@@ -158,7 +166,10 @@ class Settings:
158
166
  def get_chat_url(self) -> str:
159
167
  pconfig = self.get_provider_config()
160
168
  endpoint = self.endpoint.rstrip("/")
161
- return f"{endpoint}{pconfig.get('chat_endpoint', '/v1/chat/completions')}"
169
+ chat_ep = pconfig.get("chat_endpoint", "/v1/chat/completions")
170
+ if "{deployment}" in chat_ep:
171
+ chat_ep = chat_ep.replace("{deployment}", self.model)
172
+ return f"{endpoint}{chat_ep}"
162
173
 
163
174
  def to_dict(self) -> dict:
164
175
  return dict(self._data)
@@ -210,7 +221,7 @@ def create_default_config(repo_path: Path):
210
221
  exclude = []
211
222
 
212
223
  [ai]
213
- # Provider: ollama, claude, openai, or custom
224
+ # Provider: ollama, claude, openai, azure, or custom
214
225
  provider = "ollama"
215
226
  # Model name
216
227
  model = "qwen2.5-coder:7b"
@@ -1,10 +1,27 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import json
4
+ import math
4
5
  from pathlib import Path
5
6
 
6
7
  from ctxgraph.graph.storage import Storage
7
8
 
9
+ # SVG constants
10
+ SVG_WIDTH = 960
11
+ SVG_HEIGHT = 680
12
+ SVG_CENTER_X = SVG_WIDTH // 2
13
+ SVG_CENTER_Y = SVG_HEIGHT // 2
14
+ SVG_RADIUS = 260
15
+
16
+ COLORS = {
17
+ "file": "#58a6ff",
18
+ "class": "#d29922",
19
+ "function": "#3fb950",
20
+ "module": "#bc8cff",
21
+ }
22
+
23
+ NODE_ORDER = {"file": 0, "class": 1, "function": 2, "module": 3}
24
+
8
25
 
9
26
  def render_view(storage: Storage) -> str:
10
27
  nodes = storage.get_all_nodes()
@@ -62,6 +79,103 @@ def render_view(storage: Storage) -> str:
62
79
  return template.replace("/* GRAPH_DATA */", json_data)
63
80
 
64
81
 
82
+ def render_svg(storage: Storage) -> str:
83
+ """Generate a static SVG visualization of the knowledge graph."""
84
+ nodes = storage.get_all_nodes()
85
+ edges = storage.get_all_edges()
86
+
87
+ file_nodes = [n for n in nodes if n.type == "file"]
88
+ symbol_nodes = [n for n in nodes if n.type != "file"]
89
+ file_node_ids = {n.id for n in file_nodes}
90
+
91
+ # Layout: files in a circle, symbols at parent file position
92
+ n_files = len(file_nodes)
93
+ positions: dict[str, tuple[float, float]] = {}
94
+
95
+ for i, node in enumerate(file_nodes):
96
+ angle = (2 * math.pi * i / n_files) - math.pi / 2
97
+ x = SVG_CENTER_X + SVG_RADIUS * math.cos(angle)
98
+ y = SVG_CENTER_Y + SVG_RADIUS * math.sin(angle)
99
+ positions[node.id] = (x, y)
100
+
101
+ # Place symbol nodes near their parent file (offset slightly)
102
+ for node in symbol_nodes:
103
+ parent_id = f"file:{node.path}" if node.path else None
104
+ if parent_id and parent_id in positions:
105
+ px, py = positions[parent_id]
106
+ offset = 22
107
+ positions[node.id] = (px + offset, py + offset)
108
+ else:
109
+ positions[node.id] = (SVG_CENTER_X, SVG_CENTER_Y)
110
+
111
+ # Build edge lines
112
+ edge_lines: list[str] = []
113
+ for edge in edges:
114
+ src = positions.get(edge.source_id)
115
+ tgt = positions.get(edge.target_id)
116
+ if src and tgt:
117
+ edge_lines.append(
118
+ f'<line x1="{src[0]:.1f}" y1="{src[1]:.1f}" '
119
+ f'x2="{tgt[0]:.1f}" y2="{tgt[1]:.1f}" '
120
+ f'class="edge edge-{edge.relation}" />'
121
+ )
122
+
123
+ # Build node circles + labels
124
+ all_nodes = file_nodes + symbol_nodes
125
+ all_nodes.sort(key=lambda n: NODE_ORDER.get(n.type, 9))
126
+
127
+ node_groups: list[str] = []
128
+ for node in all_nodes:
129
+ x, y = positions.get(node.id, (SVG_CENTER_X, SVG_CENTER_Y))
130
+ r = 5 + (node.importance or 0.5) * 5
131
+ color = COLORS.get(node.type, "#8b949e")
132
+ label = node.name
133
+ node_groups.append(
134
+ f'<g class="node node-{node.type}">'
135
+ f'<circle cx="{x:.1f}" cy="{y:.1f}" r="{r:.1f}" fill="{color}" stroke="#161b22" stroke-width="1.5" />'
136
+ f'<text x="{x + r + 6:.1f}" y="{y + 4:.1f}" class="label">{_esc(label)}</text>'
137
+ f'</g>'
138
+ )
139
+
140
+ # Stats
141
+ n_edges = len(set((e.source_id, e.target_id) for e in edges))
142
+
143
+ return f"""<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {SVG_WIDTH} {SVG_HEIGHT}" width="100%" height="100%" style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#0d1117;color:#c9d1d9;">
144
+ <style>
145
+ .edge {{ stroke-opacity:0.4; stroke-width:1.2; }}
146
+ .edge-imports {{ stroke:#58a6ff; stroke-dasharray:4,2; }}
147
+ .edge-calls {{ stroke:#3fb950; stroke-dasharray:2,2; }}
148
+ .edge-defines {{ stroke:#d29922; }}
149
+ .edge-extends {{ stroke:#bc8cff; }}
150
+ .node {{ cursor:pointer; }}
151
+ .node-file circle {{ filter: drop-shadow(0 0 4px #58a6ff55); }}
152
+ .label {{ fill:#8b949e; font-size:10px; }}
153
+ .node-file .label {{ fill:#c9d1d9; font-weight:600; }}
154
+ .title {{ fill:#c9d1d9; font-size:18px; font-weight:700; }}
155
+ .subtitle {{ fill:#8b949e; font-size:12px; }}
156
+ .legend-item text {{ fill:#8b949e; font-size:11px; }}
157
+ </style>
158
+ <rect width="100%" height="100%" fill="#0d1117" />
159
+ <text x="20" y="30" class="title">ctxgraph — Knowledge Graph</text>
160
+ <text x="20" y="48" class="subtitle">{len(nodes)} nodes, {n_edges} edges</text>
161
+ <g id="edges">{''.join(edge_lines)}</g>
162
+ <g id="nodes">{''.join(node_groups)}</g>
163
+ <g id="legend" transform="translate(20, {SVG_HEIGHT - 100})">
164
+ <rect x="0" y="0" width="220" height="80" rx="6" fill="#161b22" stroke="#30363d" stroke-width="1" />
165
+ <circle cx="16" cy="18" r="5" fill="#58a6ff" /><text x="28" y="22" class="legend-item">File</text>
166
+ <circle cx="16" cy="38" r="5" fill="#d29922" /><text x="28" y="42" class="legend-item">Class</text>
167
+ <circle cx="16" cy="58" r="5" fill="#3fb950" /><text x="28" y="62" class="legend-item">Function</text>
168
+ <line x1="110" y1="16" x2="140" y2="16" stroke="#58a6ff" stroke-dasharray="4,2" stroke-width="1.5"/><text x="146" y="20" class="legend-item">Import</text>
169
+ <line x1="110" y1="36" x2="140" y2="36" stroke="#3fb950" stroke-dasharray="2,2" stroke-width="1.5"/><text x="146" y="40" class="legend-item">Call</text>
170
+ <line x1="110" y1="56" x2="140" y2="56" stroke="#d29922" stroke-width="1.5"/><text x="146" y="60" class="legend-item">Defines</text>
171
+ </g>
172
+ </svg>"""
173
+
174
+
175
+ def _esc(text: str) -> str:
176
+ return text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace('"', "&quot;")
177
+
178
+
65
179
  def _short_path(path: str) -> str:
66
180
  parts = path.split("/")
67
181
  if len(parts) > 3:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ctxgraph
3
- Version: 0.2.0
3
+ Version: 0.2.2
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
@@ -37,9 +37,11 @@ pip install ctxgraph
37
37
  ctx build # Build knowledge graph
38
38
  ctx capsule "fix JWT expiry" # 92-99% fewer tokens vs raw code
39
39
  ccg "fix the login redirect bug" # Launch Claude with context pre-loaded
40
- ctx view # Interactive D3.js visualization
40
+ ctx view # Interactive D3.js visualization (or --svg for static)
41
41
  ```
42
42
 
43
+ <img src="https://raw.githubusercontent.com/shashi3070/ctxgraph/master/docs/graph.svg" alt="ctxgraph knowledge graph visualization" width="100%">
44
+
43
45
  ---
44
46
 
45
47
  ## Why ctxgraph?
@@ -248,7 +250,7 @@ ccg --mode deep "redesign the database schema" # Deep mode
248
250
  exclude = ["legacy/*", "vendor/*"]
249
251
 
250
252
  [ai]
251
- provider = "ollama" # ollama, claude, openai, custom
253
+ provider = "ollama" # ollama, claude, openai, azure, custom
252
254
  model = "qwen2.5-coder:7b"
253
255
  endpoint = "http://localhost:11434"
254
256
 
@@ -265,6 +267,7 @@ max_depth = 2
265
267
  | `CTXGRAPH_ENDPOINT` | `ai.endpoint` |
266
268
  | `ANTHROPIC_API_KEY` | Claude API |
267
269
  | `OPENAI_API_KEY` | OpenAI API |
270
+ | `AZURE_OPENAI_API_KEY` | Azure OpenAI API |
268
271
 
269
272
  ```bash
270
273
  # Ollama (default)
@@ -276,6 +279,13 @@ CTXGRAPH_PROVIDER=claude CTXGRAPH_MODEL=claude-sonnet-4-20250514 ctx capsule "qu
276
279
  # OpenAI
277
280
  CTXGRAPH_PROVIDER=openai CTXGRAPH_MODEL=gpt-4o ctx capsule "query"
278
281
 
282
+ # Azure OpenAI
283
+ CTXGRAPH_PROVIDER=azure \
284
+ CTXGRAPH_MODEL=gpt-4o \
285
+ CTXGRAPH_ENDPOINT=https://my-resource.openai.azure.com \
286
+ AZURE_OPENAI_API_KEY=sk-... \
287
+ ctx capsule "query"
288
+
279
289
  # Custom (OpenAI-compatible)
280
290
  CTXGRAPH_PROVIDER=custom CTXGRAPH_ENDPOINT=http://my-api/v1 ctx capsule "query"
281
291
  ```
@@ -307,6 +317,185 @@ ctx capsule "extract payment processing into separate module" --mode deep
307
317
 
308
318
  ---
309
319
 
320
+ ## Framework Integrations
321
+
322
+ ctxgraph can be used as a Python library — not just a CLI. This makes it easy to plug into LangChain, LangGraph, OpenAI Agents, or any custom AI pipeline.
323
+
324
+ ### Python API
325
+
326
+ ```python
327
+ from pathlib import Path
328
+ from ctxgraph.graph.builder import build_graph, get_storage
329
+ from ctxgraph.capsule.renderer import render_capsule
330
+ from ctxgraph.graph.query import search_relevant_nodes
331
+
332
+ # 1. Build the graph (one-time setup)
333
+ stats = build_graph(Path("/path/to/project"))
334
+ print(f"Built: {stats['total_nodes']} nodes, {stats['total_edges']} edges")
335
+
336
+ # 2. Get storage for an existing graph
337
+ storage = get_storage(Path("/path/to/project"))
338
+
339
+ # 3. Generate a context capsule (token-efficient text)
340
+ capsule = render_capsule(storage, "fix JWT token validation", max_nodes=20)
341
+ print(capsule)
342
+
343
+ # 4. Search for relevant nodes programmatically
344
+ results = search_relevant_nodes(storage, "auth login", max_nodes=10, max_depth=2)
345
+ for node, score in results:
346
+ print(f" {node.type}:{node.name} (score={score})")
347
+ ```
348
+
349
+ ### LangChain
350
+
351
+ Inject ctxgraph capsules directly into your LangChain prompts — dramatically reducing token usage while providing precise code context.
352
+
353
+ ```python
354
+ from pathlib import Path
355
+ from langchain_openai import ChatOpenAI
356
+ from langchain_core.prompts import ChatPromptTemplate
357
+ from ctxgraph.graph.builder import build_graph, get_storage
358
+ from ctxgraph.capsule.renderer import render_capsule
359
+
360
+ # Build graph once
361
+ build_graph(Path("./my_project"))
362
+ storage = get_storage(Path("./my_project"))
363
+
364
+ # Generate context for a specific task
365
+ context = render_capsule(storage, "user authentication flow", max_nodes=20)
366
+
367
+ prompt = ChatPromptTemplate.from_messages([
368
+ ("system", "You are a senior Python developer. Use the code context below to answer the question.\n\n{context}"),
369
+ ("user", "{question}"),
370
+ ])
371
+
372
+ llm = ChatOpenAI(model="gpt-4o")
373
+ chain = prompt | llm
374
+
375
+ response = chain.invoke({
376
+ "context": context,
377
+ "question": "Where is the login rate limiter implemented?",
378
+ })
379
+ ```
380
+
381
+ ### LangGraph
382
+
383
+ Use ctxgraph as a tool within a LangGraph agent — the agent requests context capsules when it needs to understand the codebase.
384
+
385
+ ```python
386
+ from pathlib import Path
387
+ from typing import Literal
388
+ from langgraph.graph import StateGraph, MessagesState
389
+ from langgraph.prebuilt import ToolNode
390
+ from langchain_openai import ChatOpenAI
391
+ from langchain_core.tools import tool
392
+ from ctxgraph.graph.builder import build_graph, get_storage
393
+ from ctxgraph.capsule.renderer import render_capsule
394
+
395
+ # Pre-build graph
396
+ build_graph(Path("./my_project"))
397
+ storage = get_storage(Path("./my_project"))
398
+
399
+ @tool
400
+ def code_context(task: str) -> str:
401
+ """Get code context relevant to a task. Use this before answering code questions."""
402
+ return render_capsule(storage, task, max_nodes=20)
403
+
404
+ tools = [code_context]
405
+ tool_node = ToolNode(tools)
406
+
407
+ model = ChatOpenAI(model="gpt-4o", temperature=0).bind_tools(tools)
408
+
409
+ def should_continue(state: MessagesState) -> Literal["tools", "__end__"]:
410
+ return "tools" if state["messages"][-1].tool_calls else "__end__"
411
+
412
+ def call_model(state: MessagesState):
413
+ return {"messages": [model.invoke(state["messages"])]}
414
+
415
+ graph = StateGraph(MessagesState)
416
+ graph.add_node("agent", call_model)
417
+ graph.add_node("tools", tool_node)
418
+ graph.set_entry_point("agent")
419
+ graph.add_conditional_edges("agent", should_continue)
420
+ graph.add_edge("tools", "agent")
421
+
422
+ app = graph.compile()
423
+
424
+ for chunk in app.stream({"messages": [("user", "Find the bug in the payment processor")]}):
425
+ for node, msg in chunk.items():
426
+ print(f"[{node}]: {msg['messages'][0].content[:200] if msg.get('messages') else ''}")
427
+ ```
428
+
429
+ ### OpenAI Agents SDK
430
+
431
+ Use ctxgraph with the official OpenAI Agents SDK (also works with Azure OpenAI via `AzureOpenAIChatCompletionAgent`).
432
+
433
+ ```python
434
+ from pathlib import Path
435
+ from openai import AzureOpenAI # or OpenAI for standard API
436
+ from agents import Agent, Runner, function_tool
437
+ from ctxgraph.graph.builder import build_graph, get_storage
438
+ from ctxgraph.capsule.renderer import render_capsule
439
+
440
+ # Pre-build the graph
441
+ build_graph(Path("./my_project"))
442
+ storage = get_storage(Path("./my_project"))
443
+
444
+ @function_tool
445
+ def fetch_code_context(task_description: str) -> str:
446
+ """Retrieve relevant code context for a development task."""
447
+ return render_capsule(storage, task_description, max_nodes=20)
448
+
449
+ agent = Agent(
450
+ name="Code Assistant",
451
+ instructions="You are a helpful coding assistant. Use the code context tool to understand the codebase before answering.",
452
+ model="gpt-4o", # or AzureOpenAIChatCompletionAgent(deployment="gpt-4o", ...)
453
+ tools=[fetch_code_context],
454
+ )
455
+
456
+ result = Runner.run_sync(
457
+ agent,
458
+ "How does the JWT authentication middleware work?",
459
+ )
460
+ print(result.final_output)
461
+ ```
462
+
463
+ ### Azure OpenAI with Custom Agent
464
+
465
+ For Azure OpenAI, configure the client directly and inject ctxgraph context:
466
+
467
+ ```python
468
+ import os
469
+ from openai import AzureOpenAI
470
+ from pathlib import Path
471
+ from ctxgraph.graph.builder import build_graph, get_storage
472
+ from ctxgraph.capsule.renderer import render_capsule
473
+
474
+ # Build graph
475
+ build_graph(Path("./my_project"))
476
+ storage = get_storage(Path("./my_project"))
477
+
478
+ # Generate context capsule
479
+ context = render_capsule(storage, "authentication and authorization", max_nodes=25)
480
+
481
+ client = AzureOpenAI(
482
+ api_version="2024-08-01-preview",
483
+ azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
484
+ api_key=os.environ["AZURE_OPENAI_API_KEY"],
485
+ )
486
+
487
+ response = client.chat.completions.create(
488
+ model="gpt-4o", # deployment name
489
+ messages=[
490
+ {"role": "system", "content": f"You are a senior Python developer. Use the code context below.\n\n{context}"},
491
+ {"role": "user", "content": "Explain the role-based access control (RBAC) implementation."},
492
+ ],
493
+ )
494
+ print(response.choices[0].message.content)
495
+ ```
496
+
497
+ ---
498
+
310
499
  ## Development
311
500
 
312
501
  ```bash
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes