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
docugraph/__init__.py ADDED
@@ -0,0 +1,24 @@
1
+ """
2
+ DocuGraph AI - Intelligent coding assistant with knowledge graphs and vector search.
3
+
4
+ A powerful coding agent platform combining:
5
+ - Knowledge graphs (Graphiti + Kuzu)
6
+ - Vector search (LanceDB)
7
+ - Web crawling (Crawl4AI)
8
+ - Agent memory (MCP-compatible)
9
+ """
10
+
11
+ from docugraph._version import __version__
12
+ from docugraph.core.config import Config, get_config
13
+ from docugraph.core.models import Chunk, Document, Entity, MemoryEntry, Relationship
14
+
15
+ __all__ = [
16
+ "__version__",
17
+ "Config",
18
+ "get_config",
19
+ "Document",
20
+ "Chunk",
21
+ "Entity",
22
+ "Relationship",
23
+ "MemoryEntry",
24
+ ]
docugraph/_version.py ADDED
@@ -0,0 +1,28 @@
1
+ """The package version, read from installed distribution metadata.
2
+
3
+ `pyproject.toml` is the one home for the version number. Everything that
4
+ reports it -- ``__version__``, the CLI's ``--version``, the MCP handshake's
5
+ ``serverInfo.version``, and the crawler's User-Agent -- reads it from here,
6
+ so the number cannot drift into a second place and start disagreeing with
7
+ itself. (It did: the distribution said 1.0.0, the CLI said 0.1.0, and the MCP
8
+ handshake reported the *mcp SDK's* version because the server was constructed
9
+ without one.)
10
+
11
+ ``DISTRIBUTION_NAME`` must match ``name`` in pyproject.toml; a test asserts
12
+ that, so renaming the package fails loudly here instead of silently falling
13
+ back to the placeholder below.
14
+ """
15
+
16
+ from importlib.metadata import PackageNotFoundError, version
17
+
18
+ DISTRIBUTION_NAME = "docugraph"
19
+
20
+ _UNKNOWN = "0.0.0.dev0"
21
+
22
+ try:
23
+ __version__ = version(DISTRIBUTION_NAME)
24
+ except PackageNotFoundError: # pragma: no cover -- source tree, never installed
25
+ # Running from a checkout that was never installed (no metadata to read).
26
+ # A placeholder is better than crashing on import, and better than a
27
+ # plausible-looking number that would be a lie.
28
+ __version__ = _UNKNOWN
@@ -0,0 +1,27 @@
1
+ """Agent orchestration and tool implementations.
2
+
3
+ The agents module provides:
4
+ - CodingAgent: Main agent for code assistance with RAG
5
+ - Tools: Modular tools for search, graph queries, and memory
6
+ """
7
+
8
+ from docugraph.agents.coding_agent import (
9
+ AgentAction,
10
+ AgentConfig,
11
+ AgentContext,
12
+ AgentMessage,
13
+ CodingAgent,
14
+ ToolResult,
15
+ get_coding_agent,
16
+ )
17
+
18
+ __all__ = [
19
+ # Agent
20
+ "CodingAgent",
21
+ "AgentConfig",
22
+ "AgentContext",
23
+ "AgentMessage",
24
+ "AgentAction",
25
+ "ToolResult",
26
+ "get_coding_agent",
27
+ ]
@@ -0,0 +1,532 @@
1
+ """Main coding agent orchestration.
2
+
3
+ The CodingAgent provides intelligent code assistance by:
4
+ - Searching indexed documentation
5
+ - Querying knowledge graphs for relationships
6
+ - Maintaining session memory for context
7
+ - Analyzing code structure
8
+
9
+ LLM-agnostic: Works with Ollama (local), OpenAI, or Anthropic.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ from collections.abc import Callable
16
+ from dataclasses import dataclass, field
17
+ from datetime import UTC, datetime
18
+ from enum import Enum
19
+ from typing import Any
20
+
21
+ from docugraph.core.llm import LLMProviderBase, get_llm_provider
22
+
23
+
24
+ class AgentAction(Enum):
25
+ """Actions the agent can take."""
26
+
27
+ SEARCH_DOCS = "search_docs"
28
+ SEARCH_CODE = "search_code"
29
+ GRAPH_QUERY = "graph_query"
30
+ MEMORY_STORE = "memory_store"
31
+ MEMORY_RECALL = "memory_recall"
32
+ RESPOND = "respond"
33
+
34
+
35
+ @dataclass
36
+ class AgentContext:
37
+ """Context for agent execution."""
38
+
39
+ repository: str = "default"
40
+ branch: str = "main"
41
+ session_id: str | None = None
42
+ working_directory: str | None = None
43
+ language: str | None = None # Primary programming language
44
+ framework: str | None = None # Primary framework
45
+ metadata: dict[str, Any] = field(default_factory=dict)
46
+
47
+
48
+ @dataclass
49
+ class AgentMessage:
50
+ """A message in the agent conversation."""
51
+
52
+ role: str # "user", "assistant", "system", "tool"
53
+ content: str
54
+ timestamp: datetime = field(default_factory=lambda: datetime.now(UTC))
55
+ tool_name: str | None = None
56
+ tool_result: Any = None
57
+ metadata: dict[str, Any] = field(default_factory=dict)
58
+
59
+
60
+ @dataclass
61
+ class ToolResult:
62
+ """Result from a tool execution."""
63
+
64
+ success: bool
65
+ data: Any
66
+ error: str | None = None
67
+ took_ms: float = 0.0
68
+
69
+
70
+ @dataclass
71
+ class AgentConfig:
72
+ """Configuration for the coding agent."""
73
+
74
+ # LLM settings
75
+ llm_temperature: float = 0.1
76
+ llm_max_tokens: int = 4000
77
+
78
+ # Search settings
79
+ search_top_k: int = 5
80
+ code_search_top_k: int = 10
81
+
82
+ # Memory settings
83
+ max_context_messages: int = 20
84
+ include_memory_context: bool = True
85
+
86
+ # Behavior
87
+ auto_search_threshold: float = 0.7 # Confidence to auto-search
88
+ verbose: bool = False
89
+
90
+
91
+ class CodingAgent:
92
+ """Intelligent coding assistant with RAG capabilities.
93
+
94
+ Combines multiple retrieval methods to provide contextual assistance:
95
+ - Vector search over indexed documentation
96
+ - Knowledge graph queries for relationships
97
+ - Session memory for conversation context
98
+ - Code analysis for structure understanding
99
+
100
+ Example:
101
+ agent = CodingAgent()
102
+ response = await agent.query("How do I use asyncio.gather?")
103
+ print(response)
104
+ """
105
+
106
+ def __init__(
107
+ self,
108
+ config: AgentConfig | None = None,
109
+ context: AgentContext | None = None,
110
+ ) -> None:
111
+ """Initialize the coding agent.
112
+
113
+ Args:
114
+ config: Agent configuration
115
+ context: Initial execution context
116
+ """
117
+ self._config = config or AgentConfig()
118
+ self._context = context or AgentContext()
119
+ self._messages: list[AgentMessage] = []
120
+ self._llm: LLMProviderBase | None = None
121
+ self._tools: dict[str, Callable[..., Any]] = {}
122
+
123
+ # Register default tools
124
+ self._register_default_tools()
125
+
126
+ def _get_llm(self) -> LLMProviderBase:
127
+ """Lazy load LLM provider."""
128
+ if self._llm is None:
129
+ self._llm = get_llm_provider()
130
+ return self._llm
131
+
132
+ def _register_default_tools(self) -> None:
133
+ """Register the default tools."""
134
+ # Import via explicit submodule paths — the tools package re-exports
135
+ # same-named functions that shadow the module names.
136
+ from docugraph.agents.tools.graph_query import graph_query
137
+ from docugraph.agents.tools.memory_ops import memory_recall, memory_store
138
+ from docugraph.agents.tools.search_code import search_code_async
139
+ from docugraph.agents.tools.search_docs import search_docs_async
140
+
141
+ # The async implementations: this loop is async and `execute_tool`
142
+ # awaits coroutine functions, so a sync wrapper that drives its own
143
+ # loop would raise here.
144
+ self._tools = {
145
+ "search_docs": search_docs_async,
146
+ "search_code": search_code_async,
147
+ "graph_query": graph_query,
148
+ "memory_store": memory_store,
149
+ "memory_recall": memory_recall,
150
+ }
151
+
152
+ def register_tool(self, name: str, func: Callable[..., Any]) -> None:
153
+ """Register a custom tool.
154
+
155
+ Args:
156
+ name: Tool name
157
+ func: Tool function (sync or async)
158
+ """
159
+ self._tools[name] = func
160
+
161
+ @property
162
+ def context(self) -> AgentContext:
163
+ """Get current execution context."""
164
+ return self._context
165
+
166
+ @context.setter
167
+ def context(self, ctx: AgentContext) -> None:
168
+ """Set execution context."""
169
+ self._context = ctx
170
+
171
+ @property
172
+ def messages(self) -> list[AgentMessage]:
173
+ """Get conversation messages."""
174
+ return self._messages.copy()
175
+
176
+ def add_message(self, role: str, content: str, **kwargs: Any) -> None:
177
+ """Add a message to the conversation.
178
+
179
+ Args:
180
+ role: Message role (user, assistant, system, tool)
181
+ content: Message content
182
+ **kwargs: Additional message metadata
183
+ """
184
+ self._messages.append(
185
+ AgentMessage(
186
+ role=role,
187
+ content=content,
188
+ **kwargs,
189
+ )
190
+ )
191
+
192
+ # Trim old messages if needed
193
+ if len(self._messages) > self._config.max_context_messages:
194
+ # Keep system messages and recent messages
195
+ system_msgs = [m for m in self._messages if m.role == "system"]
196
+ other_msgs = [m for m in self._messages if m.role != "system"]
197
+ keep_count = self._config.max_context_messages - len(system_msgs)
198
+ self._messages = system_msgs + other_msgs[-keep_count:]
199
+
200
+ def clear_messages(self) -> None:
201
+ """Clear conversation history."""
202
+ self._messages.clear()
203
+
204
+ async def execute_tool(
205
+ self,
206
+ tool_name: str,
207
+ arguments: dict[str, Any],
208
+ ) -> ToolResult:
209
+ """Execute a tool by name.
210
+
211
+ Args:
212
+ tool_name: Name of the tool to execute
213
+ arguments: Tool arguments
214
+
215
+ Returns:
216
+ Tool execution result
217
+ """
218
+ import time
219
+
220
+ if tool_name not in self._tools:
221
+ return ToolResult(
222
+ success=False,
223
+ data=None,
224
+ error=f"Unknown tool: {tool_name}",
225
+ )
226
+
227
+ start_time = time.time()
228
+ tool_func = self._tools[tool_name]
229
+
230
+ try:
231
+ # Add context to arguments
232
+ arguments["_context"] = self._context
233
+
234
+ # Check if async
235
+ import asyncio
236
+
237
+ if asyncio.iscoroutinefunction(tool_func):
238
+ result = await tool_func(**arguments)
239
+ else:
240
+ result = tool_func(**arguments)
241
+
242
+ elapsed_ms = (time.time() - start_time) * 1000
243
+
244
+ return ToolResult(
245
+ success=True,
246
+ data=result,
247
+ took_ms=elapsed_ms,
248
+ )
249
+
250
+ except Exception as e:
251
+ elapsed_ms = (time.time() - start_time) * 1000
252
+ return ToolResult(
253
+ success=False,
254
+ data=None,
255
+ error=str(e),
256
+ took_ms=elapsed_ms,
257
+ )
258
+
259
+ def _build_system_prompt(self) -> str:
260
+ """Build the system prompt for the agent."""
261
+ tools_desc = []
262
+ for name in self._tools:
263
+ if name == "search_docs":
264
+ tools_desc.append("- search_docs(query, top_k): Search indexed documentation")
265
+ elif name == "search_code":
266
+ tools_desc.append("- search_code(query, language, top_k): Search for code examples")
267
+ elif name == "graph_query":
268
+ tools_desc.append("- graph_query(query): Query knowledge graph for relationships")
269
+ elif name == "memory_store":
270
+ tools_desc.append("- memory_store(key, value): Store information for later")
271
+ elif name == "memory_recall":
272
+ tools_desc.append("- memory_recall(key): Retrieve stored information")
273
+
274
+ return f"""You are an intelligent coding assistant with access to indexed documentation and a knowledge graph.
275
+
276
+ Your capabilities:
277
+ {chr(10).join(tools_desc)}
278
+
279
+ Context:
280
+ - Repository: {self._context.repository}
281
+ - Branch: {self._context.branch}
282
+ - Language: {self._context.language or "Not specified"}
283
+ - Framework: {self._context.framework or "Not specified"}
284
+
285
+ Guidelines:
286
+ 1. Search documentation before answering technical questions
287
+ 2. Use the knowledge graph for understanding relationships between concepts
288
+ 3. Store important decisions and context in memory for future reference
289
+ 4. Provide clear, accurate answers with references to sources
290
+ 5. If unsure, acknowledge uncertainty and suggest where to find more information
291
+
292
+ When you need to use a tool, respond with a JSON object:
293
+ {{"tool": "tool_name", "arguments": {{"arg1": "value1"}}}}
294
+
295
+ After receiving tool results, synthesize the information into a helpful response."""
296
+
297
+ async def _decide_action(self, user_input: str) -> tuple[AgentAction, dict[str, Any]]:
298
+ """Decide what action to take based on user input.
299
+
300
+ Args:
301
+ user_input: User's message
302
+
303
+ Returns:
304
+ Tuple of (action, arguments)
305
+ """
306
+ # Simple heuristics for action selection
307
+ user_lower = user_input.lower()
308
+
309
+ # Memory-related queries
310
+ if any(kw in user_lower for kw in ["remember", "recall", "what did"]):
311
+ return AgentAction.MEMORY_RECALL, {"key": "*"}
312
+
313
+ # Explicit search requests
314
+ if any(kw in user_lower for kw in ["search", "find", "look up", "how to", "what is"]):
315
+ return AgentAction.SEARCH_DOCS, {"query": user_input}
316
+
317
+ # Code-related queries
318
+ if any(kw in user_lower for kw in ["code example", "show code", "implement", "function"]):
319
+ return AgentAction.SEARCH_CODE, {"query": user_input}
320
+
321
+ # Relationship queries
322
+ if any(kw in user_lower for kw in ["related to", "depends on", "uses", "relationship"]):
323
+ return AgentAction.GRAPH_QUERY, {"query": user_input}
324
+
325
+ # Default: Search docs for technical questions
326
+ if "?" in user_input or any(kw in user_lower for kw in ["explain", "describe", "tell me"]):
327
+ return AgentAction.SEARCH_DOCS, {"query": user_input}
328
+
329
+ # Otherwise, respond directly
330
+ return AgentAction.RESPOND, {}
331
+
332
+ async def query(
333
+ self,
334
+ user_input: str,
335
+ auto_search: bool = True,
336
+ ) -> str:
337
+ """Query the agent with a user message.
338
+
339
+ Args:
340
+ user_input: User's question or request
341
+ auto_search: Automatically search if relevant
342
+
343
+ Returns:
344
+ Agent's response
345
+ """
346
+ # Add user message
347
+ self.add_message("user", user_input)
348
+
349
+ # Decide action
350
+ action, action_args = await self._decide_action(user_input)
351
+
352
+ tool_context = ""
353
+
354
+ if action != AgentAction.RESPOND and auto_search:
355
+ # Execute the relevant tool
356
+ tool_name = action.value
357
+ result = await self.execute_tool(tool_name, action_args)
358
+
359
+ if result.success and result.data:
360
+ tool_context = self._format_tool_result(tool_name, result.data)
361
+
362
+ # Add tool message
363
+ self.add_message(
364
+ "tool",
365
+ tool_context,
366
+ tool_name=tool_name,
367
+ tool_result=result.data,
368
+ )
369
+
370
+ # Build prompt for LLM
371
+ system_prompt = self._build_system_prompt()
372
+
373
+ # Build conversation context
374
+ messages_context = []
375
+ for msg in self._messages[-self._config.max_context_messages :]:
376
+ if msg.role == "user":
377
+ messages_context.append(f"User: {msg.content}")
378
+ elif msg.role == "assistant":
379
+ messages_context.append(f"Assistant: {msg.content}")
380
+ elif msg.role == "tool":
381
+ messages_context.append(f"[Tool Result: {msg.tool_name}]\n{msg.content}")
382
+
383
+ full_prompt = f"""Based on the conversation and any tool results, provide a helpful response.
384
+
385
+ {chr(10).join(messages_context)}
386
+
387
+ Provide a clear, helpful response to the user's question."""
388
+
389
+ try:
390
+ llm = self._get_llm()
391
+ response = await llm.generate(
392
+ prompt=full_prompt,
393
+ system_prompt=system_prompt,
394
+ temperature=self._config.llm_temperature,
395
+ max_tokens=self._config.llm_max_tokens,
396
+ )
397
+
398
+ # Check if LLM wants to use a tool
399
+ response = response.strip()
400
+ if response.startswith("{") and '"tool"' in response:
401
+ try:
402
+ tool_call = json.loads(response)
403
+ if "tool" in tool_call and "arguments" in tool_call:
404
+ tool_result = await self.execute_tool(
405
+ tool_call["tool"],
406
+ tool_call["arguments"],
407
+ )
408
+ if tool_result.success:
409
+ # Add tool result and re-query
410
+ tool_context = self._format_tool_result(
411
+ tool_call["tool"],
412
+ tool_result.data,
413
+ )
414
+ self.add_message(
415
+ "tool",
416
+ tool_context,
417
+ tool_name=tool_call["tool"],
418
+ tool_result=tool_result.data,
419
+ )
420
+ # Get final response
421
+ response = await llm.generate(
422
+ prompt=f"{full_prompt}\n\n[Tool Result]\n{tool_context}\n\nNow provide the final response:",
423
+ system_prompt=system_prompt,
424
+ temperature=self._config.llm_temperature,
425
+ max_tokens=self._config.llm_max_tokens,
426
+ )
427
+ except json.JSONDecodeError:
428
+ pass # Not a tool call
429
+
430
+ # Add assistant response
431
+ self.add_message("assistant", response)
432
+
433
+ return response
434
+
435
+ except Exception as e:
436
+ error_msg = f"Error generating response: {str(e)}"
437
+ self.add_message("assistant", error_msg)
438
+ return error_msg
439
+
440
+ def _format_tool_result(self, tool_name: str, data: Any) -> str:
441
+ """Format tool result for context.
442
+
443
+ Args:
444
+ tool_name: Name of the tool
445
+ data: Tool result data
446
+
447
+ Returns:
448
+ Formatted string
449
+ """
450
+ if isinstance(data, list):
451
+ if tool_name == "search_docs":
452
+ # Format search results
453
+ parts = []
454
+ for i, item in enumerate(data[:5], 1):
455
+ if isinstance(item, dict):
456
+ content = item.get("content", "")[:500]
457
+ source = item.get("source", "Unknown")
458
+ score = item.get("score", 0)
459
+ parts.append(f"[{i}] (score: {score:.2f}) {source}\n{content}")
460
+ else:
461
+ parts.append(f"[{i}] {str(item)[:500]}")
462
+ return "\n\n".join(parts)
463
+
464
+ elif tool_name == "search_code":
465
+ # Format code results
466
+ parts = []
467
+ for i, item in enumerate(data[:5], 1):
468
+ if isinstance(item, dict):
469
+ code = item.get("code", "")[:1000]
470
+ path = item.get("path", "Unknown")
471
+ lang = item.get("language", "")
472
+ parts.append(f"[{i}] {path}\n```{lang}\n{code}\n```")
473
+ else:
474
+ parts.append(f"[{i}] {str(item)[:500]}")
475
+ return "\n\n".join(parts)
476
+
477
+ elif tool_name == "graph_query":
478
+ # Format graph results
479
+ parts = []
480
+ for item in data[:10]:
481
+ if isinstance(item, dict):
482
+ fact = item.get("fact", str(item))
483
+ parts.append(f"- {fact}")
484
+ else:
485
+ parts.append(f"- {str(item)}")
486
+ return "\n".join(parts)
487
+
488
+ else:
489
+ return json.dumps(data, indent=2, default=str)[:2000]
490
+
491
+ elif isinstance(data, dict):
492
+ return json.dumps(data, indent=2, default=str)[:2000]
493
+
494
+ else:
495
+ return str(data)[:2000]
496
+
497
+ async def summarize_session(self) -> str:
498
+ """Summarize the current session.
499
+
500
+ Returns:
501
+ Summary of the conversation
502
+ """
503
+ if not self._messages:
504
+ return "No messages in session."
505
+
506
+ user_questions = [m.content for m in self._messages if m.role == "user"]
507
+ tool_calls = [m.tool_name for m in self._messages if m.tool_name]
508
+
509
+ summary_parts = [
510
+ f"Session with {len(self._messages)} messages",
511
+ f"Repository: {self._context.repository}/{self._context.branch}",
512
+ f"Questions asked: {len(user_questions)}",
513
+ f"Tools used: {', '.join(set(tool_calls)) or 'None'}",
514
+ ]
515
+
516
+ return "\n".join(summary_parts)
517
+
518
+
519
+ def get_coding_agent(
520
+ config: AgentConfig | None = None,
521
+ context: AgentContext | None = None,
522
+ ) -> CodingAgent:
523
+ """Factory function to get a CodingAgent instance.
524
+
525
+ Args:
526
+ config: Optional agent configuration
527
+ context: Optional execution context
528
+
529
+ Returns:
530
+ CodingAgent instance
531
+ """
532
+ return CodingAgent(config=config, context=context)
@@ -0,0 +1,62 @@
1
+ """Agent tools for the coding agent.
2
+
3
+ Provides modular tools for:
4
+ - Documentation search (vector and hybrid)
5
+ - Code search with language awareness
6
+ - Knowledge graph queries
7
+ - Session memory operations
8
+ """
9
+
10
+ from docugraph.agents.tools.graph_query import (
11
+ find_related_entities,
12
+ get_entity_context,
13
+ graph_add_content,
14
+ graph_query,
15
+ query_entities_sync,
16
+ )
17
+ from docugraph.agents.tools.memory_ops import (
18
+ memory_delete,
19
+ memory_recall,
20
+ memory_search,
21
+ memory_store,
22
+ recall_context,
23
+ recall_decisions,
24
+ store_context,
25
+ store_decision,
26
+ )
27
+ from docugraph.agents.tools.search_code import (
28
+ search_code,
29
+ search_code_async,
30
+ search_symbols,
31
+ )
32
+ from docugraph.agents.tools.search_docs import (
33
+ search_by_source,
34
+ search_docs,
35
+ search_docs_async,
36
+ )
37
+
38
+ __all__ = [
39
+ # Search docs
40
+ "search_docs",
41
+ "search_docs_async",
42
+ "search_by_source",
43
+ # Search code
44
+ "search_code",
45
+ "search_code_async",
46
+ "search_symbols",
47
+ # Graph query
48
+ "graph_query",
49
+ "graph_add_content",
50
+ "find_related_entities",
51
+ "get_entity_context",
52
+ "query_entities_sync",
53
+ # Memory ops
54
+ "memory_store",
55
+ "memory_recall",
56
+ "memory_delete",
57
+ "memory_search",
58
+ "store_decision",
59
+ "recall_decisions",
60
+ "store_context",
61
+ "recall_context",
62
+ ]