docugraph 0.1.0__py3-none-any.whl

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 (37) hide show
  1. docugraph/__init__.py +24 -0
  2. docugraph/_version.py +28 -0
  3. docugraph/agents/__init__.py +27 -0
  4. docugraph/agents/coding_agent.py +532 -0
  5. docugraph/agents/tools/__init__.py +62 -0
  6. docugraph/agents/tools/graph_query.py +203 -0
  7. docugraph/agents/tools/memory_ops.py +305 -0
  8. docugraph/agents/tools/search_code.py +305 -0
  9. docugraph/agents/tools/search_docs.py +148 -0
  10. docugraph/core/__init__.py +34 -0
  11. docugraph/core/config.py +183 -0
  12. docugraph/core/embeddings.py +394 -0
  13. docugraph/core/llm.py +892 -0
  14. docugraph/core/models.py +145 -0
  15. docugraph/ingestion/__init__.py +64 -0
  16. docugraph/ingestion/api_docs.py +646 -0
  17. docugraph/ingestion/chunker.py +382 -0
  18. docugraph/ingestion/crawler.py +305 -0
  19. docugraph/ingestion/git_indexer.py +556 -0
  20. docugraph/ingestion/local_files.py +516 -0
  21. docugraph/interfaces/__init__.py +1 -0
  22. docugraph/interfaces/cli.py +983 -0
  23. docugraph/interfaces/mcp_server.py +918 -0
  24. docugraph/retrieval/__init__.py +54 -0
  25. docugraph/retrieval/context_builder.py +522 -0
  26. docugraph/retrieval/hybrid_search.py +534 -0
  27. docugraph/retrieval/reranker.py +532 -0
  28. docugraph/storage/__init__.py +14 -0
  29. docugraph/storage/graph_store.py +602 -0
  30. docugraph/storage/memory_store.py +529 -0
  31. docugraph/storage/vector_store.py +367 -0
  32. docugraph-0.1.0.dist-info/METADATA +354 -0
  33. docugraph-0.1.0.dist-info/RECORD +37 -0
  34. docugraph-0.1.0.dist-info/WHEEL +4 -0
  35. docugraph-0.1.0.dist-info/entry_points.txt +3 -0
  36. docugraph-0.1.0.dist-info/licenses/LICENSE +201 -0
  37. docugraph-0.1.0.dist-info/licenses/NOTICE +10 -0
@@ -0,0 +1,203 @@
1
+ """Knowledge graph query tool for the coding agent.
2
+
3
+ Provides access to the knowledge graph for entity and relationship queries.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import asyncio
9
+ from typing import Any
10
+
11
+
12
+ async def graph_query(
13
+ query: str,
14
+ num_results: int = 10,
15
+ group_id: str | None = None,
16
+ _context: Any = None,
17
+ **_kwargs: Any,
18
+ ) -> list[dict[str, Any]]:
19
+ """Query the knowledge graph for facts and relationships.
20
+
21
+ Args:
22
+ query: Natural language query
23
+ num_results: Maximum number of results
24
+ group_id: Optional group ID filter
25
+ _context: Agent execution context (injected)
26
+ **kwargs: Additional arguments
27
+
28
+ Returns:
29
+ List of facts from the knowledge graph
30
+ """
31
+ from docugraph.storage.graph_store import GraphStore
32
+
33
+ store = GraphStore()
34
+ group_ids = [group_id] if group_id else None
35
+
36
+ try:
37
+ results = await store.search(
38
+ query=query,
39
+ group_ids=group_ids,
40
+ num_results=num_results,
41
+ )
42
+ return results
43
+ finally:
44
+ await store.close()
45
+
46
+
47
+ async def graph_add_content(
48
+ content: str,
49
+ name: str | None = None,
50
+ group_id: str = "default",
51
+ source_type: str = "text",
52
+ _context: Any = None,
53
+ **_kwargs: Any,
54
+ ) -> dict[str, Any]:
55
+ """Add content to the knowledge graph.
56
+
57
+ Args:
58
+ content: Content to extract entities from
59
+ name: Episode name (optional)
60
+ group_id: Group ID for scoping
61
+ source_type: Content type (text, json)
62
+ _context: Agent execution context (injected)
63
+ **kwargs: Additional arguments
64
+
65
+ Returns:
66
+ Information about extracted entities and relationships
67
+ """
68
+ from docugraph.storage.graph_store import GraphStore
69
+
70
+ store = GraphStore()
71
+
72
+ try:
73
+ episode_name = name or f"episode_{hash(content) % 10000}"
74
+ result = await store.add_episode(
75
+ name=episode_name,
76
+ content=content,
77
+ source_type=source_type,
78
+ group_id=group_id,
79
+ )
80
+ return result
81
+ finally:
82
+ await store.close()
83
+
84
+
85
+ async def find_related_entities(
86
+ entity_name: str,
87
+ relationship_type: str | None = None,
88
+ max_depth: int = 2,
89
+ _context: Any = None,
90
+ **_kwargs: Any,
91
+ ) -> list[dict[str, Any]]:
92
+ """Find entities related to a given entity.
93
+
94
+ Args:
95
+ entity_name: Name of the entity to start from
96
+ relationship_type: Filter by relationship type (USES, DEPENDS_ON, etc.)
97
+ max_depth: How deep to look — widens the result pool per hop
98
+ _context: Agent execution context (injected)
99
+ **kwargs: Additional arguments
100
+
101
+ Returns:
102
+ List of related entities with relationship information
103
+ """
104
+ # Query for the entity and its relationships
105
+ query = f"What is related to {entity_name}?"
106
+ if relationship_type:
107
+ query = f"What does {entity_name} {relationship_type.lower().replace('_', ' ')}?"
108
+
109
+ # Deeper searches pull a proportionally wider pool of graph facts.
110
+ results = await graph_query(
111
+ query=query,
112
+ num_results=10 * max(1, max_depth),
113
+ _context=_context,
114
+ )
115
+
116
+ # Filter results that mention the entity
117
+ related = []
118
+ entity_lower = entity_name.lower()
119
+
120
+ for r in results:
121
+ fact = r.get("fact", "").lower()
122
+ if entity_lower in fact:
123
+ related.append(r)
124
+
125
+ return related
126
+
127
+
128
+ async def get_entity_context(
129
+ entity_name: str,
130
+ include_relationships: bool = True,
131
+ _context: Any = None,
132
+ **_kwargs: Any,
133
+ ) -> dict[str, Any]:
134
+ """Get comprehensive context about an entity.
135
+
136
+ Args:
137
+ entity_name: Name of the entity
138
+ include_relationships: Include relationship information
139
+ _context: Agent execution context (injected)
140
+ **kwargs: Additional arguments
141
+
142
+ Returns:
143
+ Entity context including facts, relationships, and documentation
144
+ """
145
+ # Query for entity facts
146
+ entity_facts = await graph_query(
147
+ query=f"What is {entity_name}?",
148
+ num_results=10,
149
+ _context=_context,
150
+ )
151
+
152
+ context = {
153
+ "entity": entity_name,
154
+ "facts": entity_facts,
155
+ "relationships": [],
156
+ "documentation": [],
157
+ }
158
+
159
+ if include_relationships:
160
+ # Get relationships
161
+ relationships = await find_related_entities(
162
+ entity_name=entity_name,
163
+ _context=_context,
164
+ )
165
+ context["relationships"] = relationships
166
+
167
+ # Also search documentation. This function is async, so it awaits the
168
+ # async entry point rather than driving a loop of its own.
169
+ from docugraph.agents.tools.search_docs import search_docs_async
170
+
171
+ doc_results = await search_docs_async(
172
+ query=entity_name,
173
+ top_k=3,
174
+ _context=_context,
175
+ )
176
+ context["documentation"] = doc_results
177
+
178
+ return context
179
+
180
+
181
+ def query_entities_sync(
182
+ query: str,
183
+ num_results: int = 10,
184
+ group_id: str | None = None,
185
+ _context: Any = None,
186
+ **_kwargs: Any,
187
+ ) -> list[dict[str, Any]]:
188
+ """Synchronous wrapper for graph_query, for callers with no event loop.
189
+
190
+ Async callers await ``graph_query`` itself -- this drives a fresh loop and
191
+ raises if one is already running, which is the signal to await instead.
192
+
193
+ Args:
194
+ query: Natural language query
195
+ num_results: Maximum number of results
196
+ group_id: Optional group ID filter
197
+ _context: Agent execution context (injected)
198
+ **kwargs: Additional arguments
199
+
200
+ Returns:
201
+ List of facts from the knowledge graph
202
+ """
203
+ return asyncio.run(graph_query(query, num_results, group_id, _context))
@@ -0,0 +1,305 @@
1
+ """Memory operations tool for the coding agent.
2
+
3
+ Provides session memory for storing and retrieving context, decisions,
4
+ and other information scoped by repository and branch.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from datetime import UTC, datetime
10
+ from typing import Any
11
+
12
+
13
+ def _scope_or_defaults(
14
+ repository: str | None,
15
+ branch: str | None,
16
+ _context: Any,
17
+ ) -> tuple[str, str]:
18
+ """Resolve repository/branch scope: explicit arg, then context, then defaults."""
19
+ repo = repository or (getattr(_context, "repository", "default") if _context else "default")
20
+ br = branch or (getattr(_context, "branch", "main") if _context else "main")
21
+ return str(repo), str(br)
22
+
23
+
24
+ def memory_store(
25
+ key: str,
26
+ value: Any,
27
+ repository: str | None = None,
28
+ branch: str | None = None,
29
+ _context: Any = None,
30
+ **_kwargs: Any,
31
+ ) -> dict[str, Any]:
32
+ """Store a value in agent memory.
33
+
34
+ Args:
35
+ key: Key to store the value under
36
+ value: Value to store (can be any JSON-serializable type)
37
+ repository: Repository scope (defaults to context or 'default')
38
+ branch: Branch scope (defaults to context or 'main')
39
+ _context: Agent execution context (injected)
40
+ **kwargs: Additional arguments
41
+
42
+ Returns:
43
+ Confirmation with stored entry details
44
+ """
45
+ from docugraph.storage.memory_store import MemoryStore
46
+
47
+ # Get scope from context or defaults
48
+ repository, branch = _scope_or_defaults(repository, branch, _context)
49
+
50
+ store = MemoryStore()
51
+ entry = store.set(
52
+ key=key,
53
+ value=value,
54
+ repository=repository,
55
+ branch=branch,
56
+ )
57
+
58
+ return {
59
+ "key": entry.key,
60
+ "value": entry.value,
61
+ "repository": entry.repository,
62
+ "branch": entry.branch,
63
+ "created_at": entry.created_at.isoformat() if entry.created_at else None,
64
+ }
65
+
66
+
67
+ def memory_recall(
68
+ key: str,
69
+ repository: str | None = None,
70
+ branch: str | None = None,
71
+ default: Any = None,
72
+ _context: Any = None,
73
+ **_kwargs: Any,
74
+ ) -> Any:
75
+ """Retrieve a value from agent memory.
76
+
77
+ Args:
78
+ key: Key to retrieve (or '*' for all keys)
79
+ repository: Repository scope (defaults to context or 'default')
80
+ branch: Branch scope (defaults to context or 'main')
81
+ default: Default value if key not found
82
+ _context: Agent execution context (injected)
83
+ **kwargs: Additional arguments
84
+
85
+ Returns:
86
+ Stored value or default if not found
87
+ """
88
+ from docugraph.storage.memory_store import MemoryStore
89
+
90
+ # Get scope from context or defaults
91
+ repository, branch = _scope_or_defaults(repository, branch, _context)
92
+
93
+ store = MemoryStore()
94
+
95
+ # List all keys
96
+ if key == "*":
97
+ keys = store.list_keys(repository=repository, branch=branch)
98
+ return {
99
+ "keys": keys,
100
+ "repository": repository,
101
+ "branch": branch,
102
+ "total": len(keys),
103
+ }
104
+
105
+ # Get specific key
106
+ value = store.get(key, repository=repository, branch=branch)
107
+ if value is None:
108
+ return default
109
+
110
+ return value
111
+
112
+
113
+ def memory_delete(
114
+ key: str,
115
+ repository: str | None = None,
116
+ branch: str | None = None,
117
+ _context: Any = None,
118
+ **_kwargs: Any,
119
+ ) -> dict[str, Any]:
120
+ """Delete a value from agent memory.
121
+
122
+ Args:
123
+ key: Key to delete
124
+ repository: Repository scope
125
+ branch: Branch scope
126
+ _context: Agent execution context (injected)
127
+ **kwargs: Additional arguments
128
+
129
+ Returns:
130
+ Confirmation with deletion status
131
+ """
132
+ from docugraph.storage.memory_store import MemoryStore
133
+
134
+ # Get scope from context or defaults
135
+ repository, branch = _scope_or_defaults(repository, branch, _context)
136
+
137
+ store = MemoryStore()
138
+ deleted = store.delete(key, repository=repository, branch=branch)
139
+
140
+ return {
141
+ "key": key,
142
+ "deleted": deleted,
143
+ "repository": repository,
144
+ "branch": branch,
145
+ }
146
+
147
+
148
+ def memory_search(
149
+ pattern: str,
150
+ repository: str | None = None,
151
+ branch: str | None = None,
152
+ _context: Any = None,
153
+ **_kwargs: Any,
154
+ ) -> list[dict[str, Any]]:
155
+ """Search memory keys by pattern.
156
+
157
+ Args:
158
+ pattern: Pattern to search for (supports % wildcard)
159
+ repository: Repository scope
160
+ branch: Branch scope
161
+ _context: Agent execution context (injected)
162
+ **kwargs: Additional arguments
163
+
164
+ Returns:
165
+ List of matching entries with keys and values
166
+ """
167
+ from docugraph.storage.memory_store import MemoryStore
168
+
169
+ # Get scope from context or defaults
170
+ repository, branch = _scope_or_defaults(repository, branch, _context)
171
+
172
+ store = MemoryStore()
173
+ keys = store.list_keys(repository=repository, branch=branch)
174
+
175
+ # Filter by pattern
176
+ import fnmatch
177
+
178
+ # Convert SQL-style wildcard to glob-style
179
+ glob_pattern = pattern.replace("%", "*")
180
+ matching_keys = fnmatch.filter(keys, glob_pattern)
181
+
182
+ # Get values for matching keys
183
+ results = []
184
+ for key in matching_keys:
185
+ value = store.get(key, repository=repository, branch=branch)
186
+ results.append(
187
+ {
188
+ "key": key,
189
+ "value": value,
190
+ "repository": repository,
191
+ "branch": branch,
192
+ }
193
+ )
194
+
195
+ return results
196
+
197
+
198
+ def store_decision(
199
+ decision: str,
200
+ reasoning: str,
201
+ context: dict[str, Any] | None = None,
202
+ _context: Any = None,
203
+ **_kwargs: Any,
204
+ ) -> dict[str, Any]:
205
+ """Store a decision with reasoning for future reference.
206
+
207
+ Args:
208
+ decision: The decision made
209
+ reasoning: Why this decision was made
210
+ context: Additional context
211
+ _context: Agent execution context (injected)
212
+ **kwargs: Additional arguments
213
+
214
+ Returns:
215
+ Stored decision entry
216
+ """
217
+ # Generate a unique key for the decision
218
+ timestamp = datetime.now(UTC).strftime("%Y%m%d_%H%M%S")
219
+ key = f"decision_{timestamp}"
220
+
221
+ value = {
222
+ "decision": decision,
223
+ "reasoning": reasoning,
224
+ "context": context or {},
225
+ "timestamp": datetime.now(UTC).isoformat(),
226
+ }
227
+
228
+ return memory_store(key=key, value=value, _context=_context)
229
+
230
+
231
+ def recall_decisions(
232
+ limit: int = 10,
233
+ _context: Any = None,
234
+ **_kwargs: Any,
235
+ ) -> list[dict[str, Any]]:
236
+ """Recall recent decisions.
237
+
238
+ Args:
239
+ limit: Maximum number of decisions to return
240
+ _context: Agent execution context (injected)
241
+ **kwargs: Additional arguments
242
+
243
+ Returns:
244
+ List of recent decisions
245
+ """
246
+ results = memory_search(pattern="decision_%", _context=_context)
247
+
248
+ # Sort by key (which includes timestamp) and return most recent
249
+ results.sort(key=lambda x: x["key"], reverse=True)
250
+
251
+ decisions = []
252
+ for r in results[:limit]:
253
+ value = r.get("value", {})
254
+ if isinstance(value, dict):
255
+ decisions.append(
256
+ {
257
+ "key": r["key"],
258
+ "decision": value.get("decision"),
259
+ "reasoning": value.get("reasoning"),
260
+ "timestamp": value.get("timestamp"),
261
+ }
262
+ )
263
+
264
+ return decisions
265
+
266
+
267
+ def store_context(
268
+ context_type: str,
269
+ data: dict[str, Any],
270
+ _context: Any = None,
271
+ **_kwargs: Any,
272
+ ) -> dict[str, Any]:
273
+ """Store contextual information.
274
+
275
+ Args:
276
+ context_type: Type of context (e.g., 'architecture', 'dependencies')
277
+ data: Context data
278
+ _context: Agent execution context (injected)
279
+ **kwargs: Additional arguments
280
+
281
+ Returns:
282
+ Stored context entry
283
+ """
284
+ key = f"context_{context_type}"
285
+ return memory_store(key=key, value=data, _context=_context)
286
+
287
+
288
+ def recall_context(
289
+ context_type: str,
290
+ _context: Any = None,
291
+ **_kwargs: Any,
292
+ ) -> dict[str, Any] | None:
293
+ """Recall contextual information.
294
+
295
+ Args:
296
+ context_type: Type of context to recall
297
+ _context: Agent execution context (injected)
298
+ **kwargs: Additional arguments
299
+
300
+ Returns:
301
+ Context data or None if not found
302
+ """
303
+ key = f"context_{context_type}"
304
+ result: dict[str, Any] | None = memory_recall(key=key, default=None, _context=_context)
305
+ return result