ctxgraph 0.2.3__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.
Files changed (47) hide show
  1. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/PKG-INFO +75 -65
  2. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/README.md +74 -64
  3. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/pyproject.toml +1 -1
  4. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/src/ctxgraph.egg-info/PKG-INFO +75 -65
  5. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/setup.cfg +0 -0
  6. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/src/ctxgraph/__init__.py +0 -0
  7. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/src/ctxgraph/analyzers/__init__.py +0 -0
  8. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/src/ctxgraph/analyzers/python/__init__.py +0 -0
  9. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/src/ctxgraph/analyzers/python/importer.py +0 -0
  10. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/src/ctxgraph/analyzers/python/semantic.py +0 -0
  11. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/src/ctxgraph/analyzers/python/symbols.py +0 -0
  12. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/src/ctxgraph/capsule/__init__.py +0 -0
  13. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/src/ctxgraph/capsule/renderer.py +0 -0
  14. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/src/ctxgraph/cli/__init__.py +0 -0
  15. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/src/ctxgraph/cli/main.py +0 -0
  16. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/src/ctxgraph/clients/__init__.py +0 -0
  17. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/src/ctxgraph/clients/models.py +0 -0
  18. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/src/ctxgraph/config/__init__.py +0 -0
  19. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/src/ctxgraph/config/providers.py +0 -0
  20. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/src/ctxgraph/config/settings.py +0 -0
  21. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/src/ctxgraph/exclude/__init__.py +0 -0
  22. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/src/ctxgraph/exclude/patterns.py +0 -0
  23. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/src/ctxgraph/graph/__init__.py +0 -0
  24. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/src/ctxgraph/graph/builder.py +0 -0
  25. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/src/ctxgraph/graph/models.py +0 -0
  26. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/src/ctxgraph/graph/query.py +0 -0
  27. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/src/ctxgraph/graph/storage.py +0 -0
  28. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/src/ctxgraph/mcp/__init__.py +0 -0
  29. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/src/ctxgraph/mcp/server.py +0 -0
  30. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/src/ctxgraph/view/__init__.py +0 -0
  31. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/src/ctxgraph/view/visualizer.py +0 -0
  32. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/src/ctxgraph/wrapper/__init__.py +0 -0
  33. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/src/ctxgraph/wrapper/claude.py +0 -0
  34. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/src/ctxgraph.egg-info/SOURCES.txt +0 -0
  35. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/src/ctxgraph.egg-info/dependency_links.txt +0 -0
  36. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/src/ctxgraph.egg-info/entry_points.txt +0 -0
  37. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/src/ctxgraph.egg-info/requires.txt +0 -0
  38. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/src/ctxgraph.egg-info/top_level.txt +0 -0
  39. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/tests/test_analyzers.py +0 -0
  40. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/tests/test_benchmark.py +0 -0
  41. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/tests/test_capsule.py +0 -0
  42. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/tests/test_config.py +0 -0
  43. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/tests/test_integration.py +0 -0
  44. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/tests/test_model_mode.py +0 -0
  45. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/tests/test_models.py +0 -0
  46. {ctxgraph-0.2.3 → ctxgraph-0.2.4}/tests/test_query.py +0 -0
  47. {ctxgraph-0.2.3 → 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
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
@@ -330,9 +330,15 @@ ctx capsule "extract payment processing into separate module" --mode deep
330
330
 
331
331
  ## Framework Integrations
332
332
 
333
- 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.
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
334
 
335
- ### Python API
335
+ ### How the Python API works
336
+
337
+ The flow is always the same:
338
+
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
336
342
 
337
343
  ```python
338
344
  from pathlib import Path
@@ -340,154 +346,158 @@ from ctxgraph.graph.builder import build_graph, get_storage
340
346
  from ctxgraph.capsule.renderer import render_capsule
341
347
  from ctxgraph.graph.query import search_relevant_nodes
342
348
 
343
- # 1. Build the graph (one-time setup)
344
- stats = build_graph(Path("/path/to/project"))
349
+ # --- Step 1: Build (one-time, ~0.1-1s per project) ---
350
+ stats = build_graph(Path("/path/to/my_project"))
345
351
  print(f"Built: {stats['total_nodes']} nodes, {stats['total_edges']} edges")
346
352
 
347
- # 2. Get storage for an existing graph
348
- storage = get_storage(Path("/path/to/project"))
353
+ # --- Step 2: Use (instant reads the .db file) ---
354
+ storage = get_storage(Path("/path/to/my_project"))
349
355
 
350
- # 3. Generate a context capsule (token-efficient text)
356
+ # Generate a capsule — a token-efficient DSL string
351
357
  capsule = render_capsule(storage, "fix JWT token validation", max_nodes=20)
352
358
  print(capsule)
353
-
354
- # 4. Search for relevant nodes programmatically
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
355
367
  results = search_relevant_nodes(storage, "auth login", max_nodes=10, max_depth=2)
356
368
  for node, score in results:
357
369
  print(f" {node.type}:{node.name} (score={score})")
358
370
  ```
359
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
+
360
374
  ### LangChain
361
375
 
362
- Inject ctxgraph capsules directly into your LangChain prompts dramatically reducing token usage while providing precise code context.
376
+ Pass the capsule as context in your prompt template. The LLM gets exactly the files, classes, and dependencies it needs — no token waste.
363
377
 
364
378
  ```python
365
379
  from pathlib import Path
366
380
  from langchain_openai import ChatOpenAI
367
381
  from langchain_core.prompts import ChatPromptTemplate
368
- from ctxgraph.graph.builder import build_graph, get_storage
382
+ from ctxgraph.graph.builder import get_storage # graph already built
369
383
  from ctxgraph.capsule.renderer import render_capsule
370
384
 
371
- # Build graph once
372
- build_graph(Path("./my_project"))
385
+ # Load existing graph (zero build time)
373
386
  storage = get_storage(Path("./my_project"))
374
387
 
375
- # Generate context for a specific task
376
- context = render_capsule(storage, "user authentication flow", max_nodes=20)
388
+ # Generate capsule for the question
389
+ context = render_capsule(storage, "login rate limiter", max_nodes=15)
377
390
 
378
391
  prompt = ChatPromptTemplate.from_messages([
379
- ("system", "You are a senior Python developer. Use the code context below to answer the question.\n\n{context}"),
392
+ ("system", "You are a senior Python dev. Answer using the code context below.\n\n{context}"),
380
393
  ("user", "{question}"),
381
394
  ])
382
395
 
383
396
  llm = ChatOpenAI(model="gpt-4o")
384
- chain = prompt | llm
397
+ response = prompt | llm | (lambda msg: msg.content)
385
398
 
386
- response = chain.invoke({
399
+ print(response.invoke({
387
400
  "context": context,
388
- "question": "Where is the login rate limiter implemented?",
389
- })
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."
390
405
  ```
391
406
 
392
407
  ### LangGraph
393
408
 
394
- Use ctxgraph as a tool within a LangGraph agent the agent requests context capsules when it needs to understand the codebase.
409
+ Expose ctxgraph as a tool the agent calls on-demand. The agent fetches context only when it hits a code-related question.
395
410
 
396
411
  ```python
397
412
  from pathlib import Path
398
- from typing import Literal
399
413
  from langgraph.graph import StateGraph, MessagesState
400
414
  from langgraph.prebuilt import ToolNode
401
415
  from langchain_openai import ChatOpenAI
402
416
  from langchain_core.tools import tool
403
- from ctxgraph.graph.builder import build_graph, get_storage
417
+ from ctxgraph.graph.builder import get_storage
404
418
  from ctxgraph.capsule.renderer import render_capsule
405
419
 
406
- # Pre-build graph
407
- build_graph(Path("./my_project"))
408
- storage = get_storage(Path("./my_project"))
420
+ # Pre-built graph — loaded instantly
421
+ _storage = get_storage(Path("./my_project"))
409
422
 
410
423
  @tool
411
424
  def code_context(task: str) -> str:
412
- """Get code context relevant to a task. Use this before answering code questions."""
413
- return render_capsule(storage, task, max_nodes=20)
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)
414
429
 
430
+ # --- Build LangGraph ---
415
431
  tools = [code_context]
416
- tool_node = ToolNode(tools)
417
-
418
432
  model = ChatOpenAI(model="gpt-4o", temperature=0).bind_tools(tools)
419
433
 
420
- def should_continue(state: MessagesState) -> Literal["tools", "__end__"]:
421
- return "tools" if state["messages"][-1].tool_calls else "__end__"
422
-
423
- def call_model(state: MessagesState):
434
+ def agent_node(state: MessagesState):
424
435
  return {"messages": [model.invoke(state["messages"])]}
425
436
 
426
437
  graph = StateGraph(MessagesState)
427
- graph.add_node("agent", call_model)
428
- graph.add_node("tools", tool_node)
438
+ graph.add_node("agent", agent_node)
439
+ graph.add_node("tools", ToolNode(tools))
429
440
  graph.set_entry_point("agent")
430
- graph.add_conditional_edges("agent", should_continue)
441
+ graph.add_conditional_edges(
442
+ "agent",
443
+ lambda s: "tools" if s["messages"][-1].tool_calls else "__end__",
444
+ )
431
445
  graph.add_edge("tools", "agent")
432
446
 
433
447
  app = graph.compile()
434
448
 
435
- for chunk in app.stream({"messages": [("user", "Find the bug in the payment processor")]}):
436
- for node, msg in chunk.items():
437
- print(f"[{node}]: {msg['messages'][0].content[:200] if msg.get('messages') else ''}")
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]}")
438
455
  ```
439
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
+
440
459
  ### OpenAI Agents SDK
441
460
 
442
- Use ctxgraph with the official OpenAI Agents SDK (also works with Azure OpenAI via `AzureOpenAIChatCompletionAgent`).
461
+ Same pattern ctxgraph is a function tool the agent invokes.
443
462
 
444
463
  ```python
445
464
  from pathlib import Path
446
- from openai import AzureOpenAI # or OpenAI for standard API
447
465
  from agents import Agent, Runner, function_tool
448
- from ctxgraph.graph.builder import build_graph, get_storage
466
+ from ctxgraph.graph.builder import get_storage
449
467
  from ctxgraph.capsule.renderer import render_capsule
450
468
 
451
- # Pre-build the graph
452
- build_graph(Path("./my_project"))
453
- storage = get_storage(Path("./my_project"))
469
+ _storage = get_storage(Path("./my_project"))
454
470
 
455
471
  @function_tool
456
472
  def fetch_code_context(task_description: str) -> str:
457
- """Retrieve relevant code context for a development task."""
458
- return render_capsule(storage, task_description, max_nodes=20)
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)
459
476
 
460
477
  agent = Agent(
461
478
  name="Code Assistant",
462
- instructions="You are a helpful coding assistant. Use the code context tool to understand the codebase before answering.",
463
- model="gpt-4o", # or AzureOpenAIChatCompletionAgent(deployment="gpt-4o", ...)
479
+ instructions="You help developers understand their codebase. Use fetch_code_context to get relevant files before answering.",
480
+ model="gpt-4o",
464
481
  tools=[fetch_code_context],
465
482
  )
466
483
 
467
- result = Runner.run_sync(
468
- agent,
469
- "How does the JWT authentication middleware work?",
470
- )
484
+ result = Runner.run_sync(agent, "How does the notification system handle email vs SMS?")
471
485
  print(result.final_output)
472
486
  ```
473
487
 
474
- ### Azure OpenAI with Custom Agent
488
+ ### Azure OpenAI (direct client)
475
489
 
476
- For Azure OpenAI, configure the client directly and inject ctxgraph context:
490
+ For Azure OpenAI or any OpenAI-compatible endpoint, inject the capsule directly into the system message.
477
491
 
478
492
  ```python
479
493
  import os
480
494
  from openai import AzureOpenAI
481
495
  from pathlib import Path
482
- from ctxgraph.graph.builder import build_graph, get_storage
496
+ from ctxgraph.graph.builder import get_storage
483
497
  from ctxgraph.capsule.renderer import render_capsule
484
498
 
485
- # Build graph
486
- build_graph(Path("./my_project"))
487
499
  storage = get_storage(Path("./my_project"))
488
-
489
- # Generate context capsule
490
- context = render_capsule(storage, "authentication and authorization", max_nodes=25)
500
+ context = render_capsule(storage, "event bus architecture", max_nodes=25)
491
501
 
492
502
  client = AzureOpenAI(
493
503
  api_version="2024-08-01-preview",
@@ -498,8 +508,8 @@ client = AzureOpenAI(
498
508
  response = client.chat.completions.create(
499
509
  model="gpt-4o", # deployment name
500
510
  messages=[
501
- {"role": "system", "content": f"You are a senior Python developer. Use the code context below.\n\n{context}"},
502
- {"role": "user", "content": "Explain the role-based access control (RBAC) implementation."},
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?"},
503
513
  ],
504
514
  )
505
515
  print(response.choices[0].message.content)
@@ -301,9 +301,15 @@ ctx capsule "extract payment processing into separate module" --mode deep
301
301
 
302
302
  ## Framework Integrations
303
303
 
304
- 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.
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
305
 
306
- ### Python API
306
+ ### How the Python API works
307
+
308
+ The flow is always the same:
309
+
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
307
313
 
308
314
  ```python
309
315
  from pathlib import Path
@@ -311,154 +317,158 @@ from ctxgraph.graph.builder import build_graph, get_storage
311
317
  from ctxgraph.capsule.renderer import render_capsule
312
318
  from ctxgraph.graph.query import search_relevant_nodes
313
319
 
314
- # 1. Build the graph (one-time setup)
315
- stats = build_graph(Path("/path/to/project"))
320
+ # --- Step 1: Build (one-time, ~0.1-1s per project) ---
321
+ stats = build_graph(Path("/path/to/my_project"))
316
322
  print(f"Built: {stats['total_nodes']} nodes, {stats['total_edges']} edges")
317
323
 
318
- # 2. Get storage for an existing graph
319
- storage = get_storage(Path("/path/to/project"))
324
+ # --- Step 2: Use (instant reads the .db file) ---
325
+ storage = get_storage(Path("/path/to/my_project"))
320
326
 
321
- # 3. Generate a context capsule (token-efficient text)
327
+ # Generate a capsule — a token-efficient DSL string
322
328
  capsule = render_capsule(storage, "fix JWT token validation", max_nodes=20)
323
329
  print(capsule)
324
-
325
- # 4. Search for relevant nodes programmatically
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
326
338
  results = search_relevant_nodes(storage, "auth login", max_nodes=10, max_depth=2)
327
339
  for node, score in results:
328
340
  print(f" {node.type}:{node.name} (score={score})")
329
341
  ```
330
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
+
331
345
  ### LangChain
332
346
 
333
- Inject ctxgraph capsules directly into your LangChain prompts dramatically reducing token usage while providing precise code context.
347
+ Pass the capsule as context in your prompt template. The LLM gets exactly the files, classes, and dependencies it needs — no token waste.
334
348
 
335
349
  ```python
336
350
  from pathlib import Path
337
351
  from langchain_openai import ChatOpenAI
338
352
  from langchain_core.prompts import ChatPromptTemplate
339
- from ctxgraph.graph.builder import build_graph, get_storage
353
+ from ctxgraph.graph.builder import get_storage # graph already built
340
354
  from ctxgraph.capsule.renderer import render_capsule
341
355
 
342
- # Build graph once
343
- build_graph(Path("./my_project"))
356
+ # Load existing graph (zero build time)
344
357
  storage = get_storage(Path("./my_project"))
345
358
 
346
- # Generate context for a specific task
347
- context = render_capsule(storage, "user authentication flow", max_nodes=20)
359
+ # Generate capsule for the question
360
+ context = render_capsule(storage, "login rate limiter", max_nodes=15)
348
361
 
349
362
  prompt = ChatPromptTemplate.from_messages([
350
- ("system", "You are a senior Python developer. Use the code context below to answer the question.\n\n{context}"),
363
+ ("system", "You are a senior Python dev. Answer using the code context below.\n\n{context}"),
351
364
  ("user", "{question}"),
352
365
  ])
353
366
 
354
367
  llm = ChatOpenAI(model="gpt-4o")
355
- chain = prompt | llm
368
+ response = prompt | llm | (lambda msg: msg.content)
356
369
 
357
- response = chain.invoke({
370
+ print(response.invoke({
358
371
  "context": context,
359
- "question": "Where is the login rate limiter implemented?",
360
- })
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."
361
376
  ```
362
377
 
363
378
  ### LangGraph
364
379
 
365
- Use ctxgraph as a tool within a LangGraph agent the agent requests context capsules when it needs to understand the codebase.
380
+ Expose ctxgraph as a tool the agent calls on-demand. The agent fetches context only when it hits a code-related question.
366
381
 
367
382
  ```python
368
383
  from pathlib import Path
369
- from typing import Literal
370
384
  from langgraph.graph import StateGraph, MessagesState
371
385
  from langgraph.prebuilt import ToolNode
372
386
  from langchain_openai import ChatOpenAI
373
387
  from langchain_core.tools import tool
374
- from ctxgraph.graph.builder import build_graph, get_storage
388
+ from ctxgraph.graph.builder import get_storage
375
389
  from ctxgraph.capsule.renderer import render_capsule
376
390
 
377
- # Pre-build graph
378
- build_graph(Path("./my_project"))
379
- storage = get_storage(Path("./my_project"))
391
+ # Pre-built graph — loaded instantly
392
+ _storage = get_storage(Path("./my_project"))
380
393
 
381
394
  @tool
382
395
  def code_context(task: str) -> str:
383
- """Get code context relevant to a task. Use this before answering code questions."""
384
- return render_capsule(storage, task, max_nodes=20)
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)
385
400
 
401
+ # --- Build LangGraph ---
386
402
  tools = [code_context]
387
- tool_node = ToolNode(tools)
388
-
389
403
  model = ChatOpenAI(model="gpt-4o", temperature=0).bind_tools(tools)
390
404
 
391
- def should_continue(state: MessagesState) -> Literal["tools", "__end__"]:
392
- return "tools" if state["messages"][-1].tool_calls else "__end__"
393
-
394
- def call_model(state: MessagesState):
405
+ def agent_node(state: MessagesState):
395
406
  return {"messages": [model.invoke(state["messages"])]}
396
407
 
397
408
  graph = StateGraph(MessagesState)
398
- graph.add_node("agent", call_model)
399
- graph.add_node("tools", tool_node)
409
+ graph.add_node("agent", agent_node)
410
+ graph.add_node("tools", ToolNode(tools))
400
411
  graph.set_entry_point("agent")
401
- graph.add_conditional_edges("agent", should_continue)
412
+ graph.add_conditional_edges(
413
+ "agent",
414
+ lambda s: "tools" if s["messages"][-1].tool_calls else "__end__",
415
+ )
402
416
  graph.add_edge("tools", "agent")
403
417
 
404
418
  app = graph.compile()
405
419
 
406
- for chunk in app.stream({"messages": [("user", "Find the bug in the payment processor")]}):
407
- for node, msg in chunk.items():
408
- print(f"[{node}]: {msg['messages'][0].content[:200] if msg.get('messages') else ''}")
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]}")
409
426
  ```
410
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
+
411
430
  ### OpenAI Agents SDK
412
431
 
413
- Use ctxgraph with the official OpenAI Agents SDK (also works with Azure OpenAI via `AzureOpenAIChatCompletionAgent`).
432
+ Same pattern ctxgraph is a function tool the agent invokes.
414
433
 
415
434
  ```python
416
435
  from pathlib import Path
417
- from openai import AzureOpenAI # or OpenAI for standard API
418
436
  from agents import Agent, Runner, function_tool
419
- from ctxgraph.graph.builder import build_graph, get_storage
437
+ from ctxgraph.graph.builder import get_storage
420
438
  from ctxgraph.capsule.renderer import render_capsule
421
439
 
422
- # Pre-build the graph
423
- build_graph(Path("./my_project"))
424
- storage = get_storage(Path("./my_project"))
440
+ _storage = get_storage(Path("./my_project"))
425
441
 
426
442
  @function_tool
427
443
  def fetch_code_context(task_description: str) -> str:
428
- """Retrieve relevant code context for a development task."""
429
- return render_capsule(storage, task_description, max_nodes=20)
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)
430
447
 
431
448
  agent = Agent(
432
449
  name="Code Assistant",
433
- instructions="You are a helpful coding assistant. Use the code context tool to understand the codebase before answering.",
434
- model="gpt-4o", # or AzureOpenAIChatCompletionAgent(deployment="gpt-4o", ...)
450
+ instructions="You help developers understand their codebase. Use fetch_code_context to get relevant files before answering.",
451
+ model="gpt-4o",
435
452
  tools=[fetch_code_context],
436
453
  )
437
454
 
438
- result = Runner.run_sync(
439
- agent,
440
- "How does the JWT authentication middleware work?",
441
- )
455
+ result = Runner.run_sync(agent, "How does the notification system handle email vs SMS?")
442
456
  print(result.final_output)
443
457
  ```
444
458
 
445
- ### Azure OpenAI with Custom Agent
459
+ ### Azure OpenAI (direct client)
446
460
 
447
- For Azure OpenAI, configure the client directly and inject ctxgraph context:
461
+ For Azure OpenAI or any OpenAI-compatible endpoint, inject the capsule directly into the system message.
448
462
 
449
463
  ```python
450
464
  import os
451
465
  from openai import AzureOpenAI
452
466
  from pathlib import Path
453
- from ctxgraph.graph.builder import build_graph, get_storage
467
+ from ctxgraph.graph.builder import get_storage
454
468
  from ctxgraph.capsule.renderer import render_capsule
455
469
 
456
- # Build graph
457
- build_graph(Path("./my_project"))
458
470
  storage = get_storage(Path("./my_project"))
459
-
460
- # Generate context capsule
461
- context = render_capsule(storage, "authentication and authorization", max_nodes=25)
471
+ context = render_capsule(storage, "event bus architecture", max_nodes=25)
462
472
 
463
473
  client = AzureOpenAI(
464
474
  api_version="2024-08-01-preview",
@@ -469,8 +479,8 @@ client = AzureOpenAI(
469
479
  response = client.chat.completions.create(
470
480
  model="gpt-4o", # deployment name
471
481
  messages=[
472
- {"role": "system", "content": f"You are a senior Python developer. Use the code context below.\n\n{context}"},
473
- {"role": "user", "content": "Explain the role-based access control (RBAC) implementation."},
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?"},
474
484
  ],
475
485
  )
476
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.3"
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
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
@@ -330,9 +330,15 @@ ctx capsule "extract payment processing into separate module" --mode deep
330
330
 
331
331
  ## Framework Integrations
332
332
 
333
- 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.
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
334
 
335
- ### Python API
335
+ ### How the Python API works
336
+
337
+ The flow is always the same:
338
+
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
336
342
 
337
343
  ```python
338
344
  from pathlib import Path
@@ -340,154 +346,158 @@ from ctxgraph.graph.builder import build_graph, get_storage
340
346
  from ctxgraph.capsule.renderer import render_capsule
341
347
  from ctxgraph.graph.query import search_relevant_nodes
342
348
 
343
- # 1. Build the graph (one-time setup)
344
- stats = build_graph(Path("/path/to/project"))
349
+ # --- Step 1: Build (one-time, ~0.1-1s per project) ---
350
+ stats = build_graph(Path("/path/to/my_project"))
345
351
  print(f"Built: {stats['total_nodes']} nodes, {stats['total_edges']} edges")
346
352
 
347
- # 2. Get storage for an existing graph
348
- storage = get_storage(Path("/path/to/project"))
353
+ # --- Step 2: Use (instant reads the .db file) ---
354
+ storage = get_storage(Path("/path/to/my_project"))
349
355
 
350
- # 3. Generate a context capsule (token-efficient text)
356
+ # Generate a capsule — a token-efficient DSL string
351
357
  capsule = render_capsule(storage, "fix JWT token validation", max_nodes=20)
352
358
  print(capsule)
353
-
354
- # 4. Search for relevant nodes programmatically
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
355
367
  results = search_relevant_nodes(storage, "auth login", max_nodes=10, max_depth=2)
356
368
  for node, score in results:
357
369
  print(f" {node.type}:{node.name} (score={score})")
358
370
  ```
359
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
+
360
374
  ### LangChain
361
375
 
362
- Inject ctxgraph capsules directly into your LangChain prompts dramatically reducing token usage while providing precise code context.
376
+ Pass the capsule as context in your prompt template. The LLM gets exactly the files, classes, and dependencies it needs — no token waste.
363
377
 
364
378
  ```python
365
379
  from pathlib import Path
366
380
  from langchain_openai import ChatOpenAI
367
381
  from langchain_core.prompts import ChatPromptTemplate
368
- from ctxgraph.graph.builder import build_graph, get_storage
382
+ from ctxgraph.graph.builder import get_storage # graph already built
369
383
  from ctxgraph.capsule.renderer import render_capsule
370
384
 
371
- # Build graph once
372
- build_graph(Path("./my_project"))
385
+ # Load existing graph (zero build time)
373
386
  storage = get_storage(Path("./my_project"))
374
387
 
375
- # Generate context for a specific task
376
- context = render_capsule(storage, "user authentication flow", max_nodes=20)
388
+ # Generate capsule for the question
389
+ context = render_capsule(storage, "login rate limiter", max_nodes=15)
377
390
 
378
391
  prompt = ChatPromptTemplate.from_messages([
379
- ("system", "You are a senior Python developer. Use the code context below to answer the question.\n\n{context}"),
392
+ ("system", "You are a senior Python dev. Answer using the code context below.\n\n{context}"),
380
393
  ("user", "{question}"),
381
394
  ])
382
395
 
383
396
  llm = ChatOpenAI(model="gpt-4o")
384
- chain = prompt | llm
397
+ response = prompt | llm | (lambda msg: msg.content)
385
398
 
386
- response = chain.invoke({
399
+ print(response.invoke({
387
400
  "context": context,
388
- "question": "Where is the login rate limiter implemented?",
389
- })
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."
390
405
  ```
391
406
 
392
407
  ### LangGraph
393
408
 
394
- Use ctxgraph as a tool within a LangGraph agent the agent requests context capsules when it needs to understand the codebase.
409
+ Expose ctxgraph as a tool the agent calls on-demand. The agent fetches context only when it hits a code-related question.
395
410
 
396
411
  ```python
397
412
  from pathlib import Path
398
- from typing import Literal
399
413
  from langgraph.graph import StateGraph, MessagesState
400
414
  from langgraph.prebuilt import ToolNode
401
415
  from langchain_openai import ChatOpenAI
402
416
  from langchain_core.tools import tool
403
- from ctxgraph.graph.builder import build_graph, get_storage
417
+ from ctxgraph.graph.builder import get_storage
404
418
  from ctxgraph.capsule.renderer import render_capsule
405
419
 
406
- # Pre-build graph
407
- build_graph(Path("./my_project"))
408
- storage = get_storage(Path("./my_project"))
420
+ # Pre-built graph — loaded instantly
421
+ _storage = get_storage(Path("./my_project"))
409
422
 
410
423
  @tool
411
424
  def code_context(task: str) -> str:
412
- """Get code context relevant to a task. Use this before answering code questions."""
413
- return render_capsule(storage, task, max_nodes=20)
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)
414
429
 
430
+ # --- Build LangGraph ---
415
431
  tools = [code_context]
416
- tool_node = ToolNode(tools)
417
-
418
432
  model = ChatOpenAI(model="gpt-4o", temperature=0).bind_tools(tools)
419
433
 
420
- def should_continue(state: MessagesState) -> Literal["tools", "__end__"]:
421
- return "tools" if state["messages"][-1].tool_calls else "__end__"
422
-
423
- def call_model(state: MessagesState):
434
+ def agent_node(state: MessagesState):
424
435
  return {"messages": [model.invoke(state["messages"])]}
425
436
 
426
437
  graph = StateGraph(MessagesState)
427
- graph.add_node("agent", call_model)
428
- graph.add_node("tools", tool_node)
438
+ graph.add_node("agent", agent_node)
439
+ graph.add_node("tools", ToolNode(tools))
429
440
  graph.set_entry_point("agent")
430
- graph.add_conditional_edges("agent", should_continue)
441
+ graph.add_conditional_edges(
442
+ "agent",
443
+ lambda s: "tools" if s["messages"][-1].tool_calls else "__end__",
444
+ )
431
445
  graph.add_edge("tools", "agent")
432
446
 
433
447
  app = graph.compile()
434
448
 
435
- for chunk in app.stream({"messages": [("user", "Find the bug in the payment processor")]}):
436
- for node, msg in chunk.items():
437
- print(f"[{node}]: {msg['messages'][0].content[:200] if msg.get('messages') else ''}")
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]}")
438
455
  ```
439
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
+
440
459
  ### OpenAI Agents SDK
441
460
 
442
- Use ctxgraph with the official OpenAI Agents SDK (also works with Azure OpenAI via `AzureOpenAIChatCompletionAgent`).
461
+ Same pattern ctxgraph is a function tool the agent invokes.
443
462
 
444
463
  ```python
445
464
  from pathlib import Path
446
- from openai import AzureOpenAI # or OpenAI for standard API
447
465
  from agents import Agent, Runner, function_tool
448
- from ctxgraph.graph.builder import build_graph, get_storage
466
+ from ctxgraph.graph.builder import get_storage
449
467
  from ctxgraph.capsule.renderer import render_capsule
450
468
 
451
- # Pre-build the graph
452
- build_graph(Path("./my_project"))
453
- storage = get_storage(Path("./my_project"))
469
+ _storage = get_storage(Path("./my_project"))
454
470
 
455
471
  @function_tool
456
472
  def fetch_code_context(task_description: str) -> str:
457
- """Retrieve relevant code context for a development task."""
458
- return render_capsule(storage, task_description, max_nodes=20)
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)
459
476
 
460
477
  agent = Agent(
461
478
  name="Code Assistant",
462
- instructions="You are a helpful coding assistant. Use the code context tool to understand the codebase before answering.",
463
- model="gpt-4o", # or AzureOpenAIChatCompletionAgent(deployment="gpt-4o", ...)
479
+ instructions="You help developers understand their codebase. Use fetch_code_context to get relevant files before answering.",
480
+ model="gpt-4o",
464
481
  tools=[fetch_code_context],
465
482
  )
466
483
 
467
- result = Runner.run_sync(
468
- agent,
469
- "How does the JWT authentication middleware work?",
470
- )
484
+ result = Runner.run_sync(agent, "How does the notification system handle email vs SMS?")
471
485
  print(result.final_output)
472
486
  ```
473
487
 
474
- ### Azure OpenAI with Custom Agent
488
+ ### Azure OpenAI (direct client)
475
489
 
476
- For Azure OpenAI, configure the client directly and inject ctxgraph context:
490
+ For Azure OpenAI or any OpenAI-compatible endpoint, inject the capsule directly into the system message.
477
491
 
478
492
  ```python
479
493
  import os
480
494
  from openai import AzureOpenAI
481
495
  from pathlib import Path
482
- from ctxgraph.graph.builder import build_graph, get_storage
496
+ from ctxgraph.graph.builder import get_storage
483
497
  from ctxgraph.capsule.renderer import render_capsule
484
498
 
485
- # Build graph
486
- build_graph(Path("./my_project"))
487
499
  storage = get_storage(Path("./my_project"))
488
-
489
- # Generate context capsule
490
- context = render_capsule(storage, "authentication and authorization", max_nodes=25)
500
+ context = render_capsule(storage, "event bus architecture", max_nodes=25)
491
501
 
492
502
  client = AzureOpenAI(
493
503
  api_version="2024-08-01-preview",
@@ -498,8 +508,8 @@ client = AzureOpenAI(
498
508
  response = client.chat.completions.create(
499
509
  model="gpt-4o", # deployment name
500
510
  messages=[
501
- {"role": "system", "content": f"You are a senior Python developer. Use the code context below.\n\n{context}"},
502
- {"role": "user", "content": "Explain the role-based access control (RBAC) implementation."},
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?"},
503
513
  ],
504
514
  )
505
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