10xgraph-api 0.7.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 (203) hide show
  1. 10xgraph_api-0.7.0.dist-info/METADATA +618 -0
  2. 10xgraph_api-0.7.0.dist-info/RECORD +203 -0
  3. 10xgraph_api-0.7.0.dist-info/WHEEL +5 -0
  4. 10xgraph_api-0.7.0.dist-info/entry_points.txt +3 -0
  5. 10xgraph_api-0.7.0.dist-info/licenses/LICENSE +21 -0
  6. 10xgraph_api-0.7.0.dist-info/top_level.txt +2 -0
  7. agentflow_cli/__init__.py +69 -0
  8. tenxgraph_api/__init__.py +23 -0
  9. tenxgraph_api/cli/__init__.py +6 -0
  10. tenxgraph_api/cli/capabilities.py +127 -0
  11. tenxgraph_api/cli/commands/__init__.py +49 -0
  12. tenxgraph_api/cli/commands/api.py +262 -0
  13. tenxgraph_api/cli/commands/audit.py +213 -0
  14. tenxgraph_api/cli/commands/build.py +305 -0
  15. tenxgraph_api/cli/commands/config.py +71 -0
  16. tenxgraph_api/cli/commands/demo.py +93 -0
  17. tenxgraph_api/cli/commands/eval.py +909 -0
  18. tenxgraph_api/cli/commands/init.py +531 -0
  19. tenxgraph_api/cli/commands/skills.py +532 -0
  20. tenxgraph_api/cli/commands/test.py +113 -0
  21. tenxgraph_api/cli/commands/version.py +47 -0
  22. tenxgraph_api/cli/config_editor/__init__.py +15 -0
  23. tenxgraph_api/cli/config_editor/schema.py +566 -0
  24. tenxgraph_api/cli/config_editor/server.py +240 -0
  25. tenxgraph_api/cli/config_editor/static/app.css +2 -0
  26. tenxgraph_api/cli/config_editor/static/app.js +3 -0
  27. tenxgraph_api/cli/config_editor/static/favicon.svg +11 -0
  28. tenxgraph_api/cli/config_editor/static/index.html +20 -0
  29. tenxgraph_api/cli/config_editor/store.py +120 -0
  30. tenxgraph_api/cli/config_editor/validation.py +377 -0
  31. tenxgraph_api/cli/constants.py +112 -0
  32. tenxgraph_api/cli/context.py +46 -0
  33. tenxgraph_api/cli/core/__init__.py +0 -0
  34. tenxgraph_api/cli/core/animations.py +550 -0
  35. tenxgraph_api/cli/core/config.py +279 -0
  36. tenxgraph_api/cli/core/output.py +625 -0
  37. tenxgraph_api/cli/core/prompts.py +220 -0
  38. tenxgraph_api/cli/core/screen.py +287 -0
  39. tenxgraph_api/cli/core/steps.py +621 -0
  40. tenxgraph_api/cli/core/theme.py +242 -0
  41. tenxgraph_api/cli/core/validation.py +251 -0
  42. tenxgraph_api/cli/exceptions.py +129 -0
  43. tenxgraph_api/cli/logger.py +84 -0
  44. tenxgraph_api/cli/main.py +936 -0
  45. tenxgraph_api/cli/templates/__init__.py +0 -0
  46. tenxgraph_api/cli/templates/defaults.py +844 -0
  47. tenxgraph_api/cli/templates/dev/.env.example +1 -0
  48. tenxgraph_api/cli/templates/dev/10xgraph.json +16 -0
  49. tenxgraph_api/cli/templates/dev/graph/__init__.py +0 -0
  50. tenxgraph_api/cli/templates/dev/graph/agent.py +63 -0
  51. tenxgraph_api/cli/templates/prod/.env.example +75 -0
  52. tenxgraph_api/cli/templates/prod/.pre-commit-config.yaml +50 -0
  53. tenxgraph_api/cli/templates/prod/.python-version +1 -0
  54. tenxgraph_api/cli/templates/prod/10xgraph.json +42 -0
  55. tenxgraph_api/cli/templates/prod/auth/__init__.py +0 -0
  56. tenxgraph_api/cli/templates/prod/auth/agent_auth.py +46 -0
  57. tenxgraph_api/cli/templates/prod/evals/__init__.py +0 -0
  58. tenxgraph_api/cli/templates/prod/evals/user_simulator_eval.py +76 -0
  59. tenxgraph_api/cli/templates/prod/evals/weather_agents_eval.py +53 -0
  60. tenxgraph_api/cli/templates/prod/graph/__init__.py +0 -0
  61. tenxgraph_api/cli/templates/prod/graph/agent.py +50 -0
  62. tenxgraph_api/cli/templates/prod/graph/state.py +5 -0
  63. tenxgraph_api/cli/templates/prod/graph/thread_name_generator.py +7 -0
  64. tenxgraph_api/cli/templates/prod/graph/tools/__init__.py +0 -0
  65. tenxgraph_api/cli/templates/prod/graph/tools/weather_tool.py +43 -0
  66. tenxgraph_api/cli/templates/prod/graph/validators/__init__.py +0 -0
  67. tenxgraph_api/cli/templates/prod/graph/validators/lifecyle.py +107 -0
  68. tenxgraph_api/cli/templates/prod/graph/validators/manager.py +9 -0
  69. tenxgraph_api/cli/templates/prod/graph/validators/validators.py +21 -0
  70. tenxgraph_api/cli/templates/prod/pyproject.toml +79 -0
  71. tenxgraph_api/cli/templates/prod/tests/__init__.py +0 -0
  72. tenxgraph_api/cli/templates/prod/tests/conftest.py +6 -0
  73. tenxgraph_api/cli/templates/prod/tests/test_agent_eval.py +78 -0
  74. tenxgraph_api/cli/templates/prod/tests/test_catalog_tools.py +47 -0
  75. tenxgraph_api/cli/templates/prod/tests/test_graph_nodes.py +57 -0
  76. tenxgraph_api/cli/templates/skills/10xgraph/SKILL.md +92 -0
  77. tenxgraph_api/cli/templates/skills/10xgraph/references/agents-and-tools.md +389 -0
  78. tenxgraph_api/cli/templates/skills/10xgraph/references/api-client.md +61 -0
  79. tenxgraph_api/cli/templates/skills/10xgraph/references/api-configuration.md +75 -0
  80. tenxgraph_api/cli/templates/skills/10xgraph/references/api-settings-and-middleware.md +89 -0
  81. tenxgraph_api/cli/templates/skills/10xgraph/references/architecture.md +91 -0
  82. tenxgraph_api/cli/templates/skills/10xgraph/references/auth-and-authorization.md +149 -0
  83. tenxgraph_api/cli/templates/skills/10xgraph/references/callbacks-and-command.md +283 -0
  84. tenxgraph_api/cli/templates/skills/10xgraph/references/checkpointing-and-threads.md +63 -0
  85. tenxgraph_api/cli/templates/skills/10xgraph/references/cli-commands.md +73 -0
  86. tenxgraph_api/cli/templates/skills/10xgraph/references/client-auth-and-errors.md +94 -0
  87. tenxgraph_api/cli/templates/skills/10xgraph/references/client-messages-invoke-stream.md +115 -0
  88. tenxgraph_api/cli/templates/skills/10xgraph/references/client-threads-memory-files.md +80 -0
  89. tenxgraph_api/cli/templates/skills/10xgraph/references/context-id-background.md +80 -0
  90. tenxgraph_api/cli/templates/skills/10xgraph/references/dependency-injection.md +58 -0
  91. tenxgraph_api/cli/templates/skills/10xgraph/references/evaluation.md +500 -0
  92. tenxgraph_api/cli/templates/skills/10xgraph/references/id-and-thread-name-generators.md +69 -0
  93. tenxgraph_api/cli/templates/skills/10xgraph/references/media-and-files.md +66 -0
  94. tenxgraph_api/cli/templates/skills/10xgraph/references/memory-and-store.md +70 -0
  95. tenxgraph_api/cli/templates/skills/10xgraph/references/prebuilt-agents-and-tools.md +394 -0
  96. tenxgraph_api/cli/templates/skills/10xgraph/references/production-runtime.md +56 -0
  97. tenxgraph_api/cli/templates/skills/10xgraph/references/providers-and-adapters.md +89 -0
  98. tenxgraph_api/cli/templates/skills/10xgraph/references/publishers-and-runtime-protocols.md +84 -0
  99. tenxgraph_api/cli/templates/skills/10xgraph/references/rate-limiting.md +194 -0
  100. tenxgraph_api/cli/templates/skills/10xgraph/references/realtime.md +172 -0
  101. tenxgraph_api/cli/templates/skills/10xgraph/references/remote-tools.md +55 -0
  102. tenxgraph_api/cli/templates/skills/10xgraph/references/rest-api-and-errors.md +97 -0
  103. tenxgraph_api/cli/templates/skills/10xgraph/references/security-and-validators.md +86 -0
  104. tenxgraph_api/cli/templates/skills/10xgraph/references/state-and-messages.md +68 -0
  105. tenxgraph_api/cli/templates/skills/10xgraph/references/state-graph.md +232 -0
  106. tenxgraph_api/cli/templates/skills/10xgraph/references/stream-emitter.md +144 -0
  107. tenxgraph_api/cli/templates/skills/10xgraph/references/streaming.md +53 -0
  108. tenxgraph_api/cli/templates/skills/10xgraph/references/testing-and-evaluation.md +64 -0
  109. tenxgraph_api/cli/templates/skills/10xgraph/references/unit-testing.md +202 -0
  110. tenxgraph_api/cli/templates/skills/copilot/10xgraph.instructions.md +108 -0
  111. tenxgraph_api/py.typed +0 -0
  112. tenxgraph_api/src/__init__.py +0 -0
  113. tenxgraph_api/src/app/__init__.py +13 -0
  114. tenxgraph_api/src/app/core/__init__.py +15 -0
  115. tenxgraph_api/src/app/core/auth/__init__.py +0 -0
  116. tenxgraph_api/src/app/core/auth/auth_backend.py +38 -0
  117. tenxgraph_api/src/app/core/auth/authorization.py +379 -0
  118. tenxgraph_api/src/app/core/auth/base_auth.py +27 -0
  119. tenxgraph_api/src/app/core/auth/jwt_auth.py +122 -0
  120. tenxgraph_api/src/app/core/auth/ownership_resolver.py +170 -0
  121. tenxgraph_api/src/app/core/auth/permissions.py +354 -0
  122. tenxgraph_api/src/app/core/auth/request_config.py +130 -0
  123. tenxgraph_api/src/app/core/auth/route_guard.py +105 -0
  124. tenxgraph_api/src/app/core/config/__init__.py +0 -0
  125. tenxgraph_api/src/app/core/config/graph_config.py +532 -0
  126. tenxgraph_api/src/app/core/config/media_settings.py +72 -0
  127. tenxgraph_api/src/app/core/config/sentry_config.py +60 -0
  128. tenxgraph_api/src/app/core/config/settings.py +202 -0
  129. tenxgraph_api/src/app/core/config/setup_logs.py +65 -0
  130. tenxgraph_api/src/app/core/config/setup_middleware.py +483 -0
  131. tenxgraph_api/src/app/core/config/worker_middleware.py +99 -0
  132. tenxgraph_api/src/app/core/exceptions/__init__.py +17 -0
  133. tenxgraph_api/src/app/core/exceptions/general_exception.py +58 -0
  134. tenxgraph_api/src/app/core/exceptions/handle_errors.py +363 -0
  135. tenxgraph_api/src/app/core/exceptions/resources_exceptions.py +72 -0
  136. tenxgraph_api/src/app/core/exceptions/user_exception.py +58 -0
  137. tenxgraph_api/src/app/core/middleware/__init__.py +25 -0
  138. tenxgraph_api/src/app/core/middleware/rate_limit/__init__.py +15 -0
  139. tenxgraph_api/src/app/core/middleware/rate_limit/base.py +27 -0
  140. tenxgraph_api/src/app/core/middleware/rate_limit/factory.py +98 -0
  141. tenxgraph_api/src/app/core/middleware/rate_limit/keying.py +133 -0
  142. tenxgraph_api/src/app/core/middleware/rate_limit/memory.py +98 -0
  143. tenxgraph_api/src/app/core/middleware/rate_limit/middleware.py +90 -0
  144. tenxgraph_api/src/app/core/middleware/rate_limit/redis.py +115 -0
  145. tenxgraph_api/src/app/core/middleware/request_limits.py +154 -0
  146. tenxgraph_api/src/app/core/middleware/security_headers.py +239 -0
  147. tenxgraph_api/src/app/core/utils/__init__.py +14 -0
  148. tenxgraph_api/src/app/core/utils/log_sanitizer.py +216 -0
  149. tenxgraph_api/src/app/loader.py +516 -0
  150. tenxgraph_api/src/app/main.py +180 -0
  151. tenxgraph_api/src/app/routers/__init__.py +4 -0
  152. tenxgraph_api/src/app/routers/ag_ui/__init__.py +10 -0
  153. tenxgraph_api/src/app/routers/ag_ui/converter.py +195 -0
  154. tenxgraph_api/src/app/routers/ag_ui/event_mapper.py +353 -0
  155. tenxgraph_api/src/app/routers/ag_ui/router.py +90 -0
  156. tenxgraph_api/src/app/routers/ag_ui/service.py +193 -0
  157. tenxgraph_api/src/app/routers/checkpointer/__init__.py +1 -0
  158. tenxgraph_api/src/app/routers/checkpointer/router.py +482 -0
  159. tenxgraph_api/src/app/routers/checkpointer/schemas/__init__.py +1 -0
  160. tenxgraph_api/src/app/routers/checkpointer/schemas/checkpointer_schemas.py +121 -0
  161. tenxgraph_api/src/app/routers/checkpointer/services/__init__.py +1 -0
  162. tenxgraph_api/src/app/routers/checkpointer/services/checkpointer_service.py +300 -0
  163. tenxgraph_api/src/app/routers/evals/__init__.py +6 -0
  164. tenxgraph_api/src/app/routers/evals/router.py +38 -0
  165. tenxgraph_api/src/app/routers/evals/services/__init__.py +1 -0
  166. tenxgraph_api/src/app/routers/evals/services/eval_report_service.py +354 -0
  167. tenxgraph_api/src/app/routers/graph/__init__.py +6 -0
  168. tenxgraph_api/src/app/routers/graph/realtime_guard.py +161 -0
  169. tenxgraph_api/src/app/routers/graph/router.py +821 -0
  170. tenxgraph_api/src/app/routers/graph/schemas/__init__.py +1 -0
  171. tenxgraph_api/src/app/routers/graph/schemas/graph_schemas.py +386 -0
  172. tenxgraph_api/src/app/routers/graph/services/__init__.py +1 -0
  173. tenxgraph_api/src/app/routers/graph/services/graph_service.py +1219 -0
  174. tenxgraph_api/src/app/routers/graph/services/multimodal_preprocessor.py +164 -0
  175. tenxgraph_api/src/app/routers/media/__init__.py +506 -0
  176. tenxgraph_api/src/app/routers/media/router.py +242 -0
  177. tenxgraph_api/src/app/routers/media/schemas.py +39 -0
  178. tenxgraph_api/src/app/routers/ping/__init__.py +4 -0
  179. tenxgraph_api/src/app/routers/ping/router.py +28 -0
  180. tenxgraph_api/src/app/routers/setup_router.py +60 -0
  181. tenxgraph_api/src/app/routers/store/__init__.py +4 -0
  182. tenxgraph_api/src/app/routers/store/router.py +200 -0
  183. tenxgraph_api/src/app/routers/store/schemas/__init__.py +28 -0
  184. tenxgraph_api/src/app/routers/store/schemas/store_schemas.py +200 -0
  185. tenxgraph_api/src/app/routers/store/services/__init__.py +4 -0
  186. tenxgraph_api/src/app/routers/store/services/store_service.py +203 -0
  187. tenxgraph_api/src/app/tasks/__init__.py +0 -0
  188. tenxgraph_api/src/app/tasks/user_tasks.py +38 -0
  189. tenxgraph_api/src/app/utils/__init__.py +12 -0
  190. tenxgraph_api/src/app/utils/callable_helper.py +26 -0
  191. tenxgraph_api/src/app/utils/media/__init__.py +15 -0
  192. tenxgraph_api/src/app/utils/media/extractor.py +172 -0
  193. tenxgraph_api/src/app/utils/media/pipeline.py +83 -0
  194. tenxgraph_api/src/app/utils/parse_output.py +17 -0
  195. tenxgraph_api/src/app/utils/response_helper.py +129 -0
  196. tenxgraph_api/src/app/utils/schemas/__init__.py +11 -0
  197. tenxgraph_api/src/app/utils/schemas/output_schemas.py +28 -0
  198. tenxgraph_api/src/app/utils/schemas/user_schemas.py +13 -0
  199. tenxgraph_api/src/app/utils/snowflake_id_generator.py +90 -0
  200. tenxgraph_api/src/app/utils/swagger_helper.py +160 -0
  201. tenxgraph_api/src/app/utils/telemetry_store.py +123 -0
  202. tenxgraph_api/src/app/utils/thread_name_generator.py +304 -0
  203. tenxgraph_api/src/app/worker.py +91 -0
@@ -0,0 +1,844 @@
1
+ """Default templates for CLI initialization."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from typing import Final
7
+
8
+
9
+ # How long a worker gets to finish in-flight work after SIGTERM.
10
+ #
11
+ # An agent run is not a typical web request: the LLM client alone allows up to
12
+ # 600s, and tools add more on top. Gunicorn's default graceful timeout is 30s, so
13
+ # every rolling deploy was hard-killing runs that were still in progress, ~30s in.
14
+ # Matching the LLM budget means a run in flight gets to finish rather than being
15
+ # truncated. Kubernetes needs a slightly larger window than the app, so the pod is
16
+ # not killed while the app is still draining.
17
+ GRACEFUL_TIMEOUT_SECONDS: Final[int] = 600
18
+
19
+ # Gunicorn kills a worker it considers hung. Long LLM calls must not look hung.
20
+ WORKER_TIMEOUT_SECONDS: Final[int] = 660
21
+
22
+ # terminationGracePeriodSeconds must exceed the app's graceful timeout, plus the
23
+ # preStop sleep that lets the load balancer stop sending new traffic first.
24
+ K8S_TERMINATION_GRACE_SECONDS: Final[int] = GRACEFUL_TIMEOUT_SECONDS + 60
25
+ K8S_PRESTOP_SLEEP_SECONDS: Final[int] = 15
26
+
27
+
28
+ # Default configuration template
29
+ DEFAULT_CONFIG_JSON: Final[str] = json.dumps(
30
+ {
31
+ "agent": "graph.react:app",
32
+ "env": ".env",
33
+ "auth": None,
34
+ "checkpointer": None,
35
+ "injectq": None,
36
+ "store": None,
37
+ "remote_tools": [],
38
+ "thread_name_generator": None,
39
+ "observability": None,
40
+ },
41
+ indent=2,
42
+ )
43
+
44
+ # Template for the default react agent graph
45
+ DEFAULT_REACT_PY: Final[str] = '''
46
+ """
47
+ Graph-based React Agent Implementation
48
+
49
+ This module implements a reactive agent system using 10xGraph's StateGraph.
50
+ The agent can interact with tools (like weather checking) and maintain conversation
51
+ state through a checkpointer. The graph orchestrates the flow between the main
52
+ agent logic and tool execution.
53
+
54
+ Key Components:
55
+ - Weather tool: Demonstrates tool calling with dependency injection
56
+ - Main agent: AI-powered assistant that can use tools
57
+ - Graph flow: Conditional routing based on tool usage
58
+ - Checkpointer: Maintains conversation state across interactions
59
+
60
+ Architecture:
61
+ The system uses a state graph with two main nodes:
62
+ 1. MAIN: Processes user input and generates AI responses
63
+ 2. TOOL: Executes tool calls when requested by the AI
64
+
65
+ The graph conditionally routes between these nodes based on whether
66
+ the AI response contains tool calls. Conversation history is maintained
67
+ through the checkpointer, allowing for multi-turn conversations.
68
+
69
+ Tools are defined as functions with JSON schema docstrings that describe
70
+ their interface for the AI model. The ToolNode automatically extracts
71
+ these schemas for tool selection.
72
+
73
+ Dependencies:
74
+ - 10xGraph: For graph and state management
75
+ - LiteLLM: For AI model interactions
76
+ - InjectQ: For dependency injection
77
+ - Python logging: For debug and info messages
78
+ """
79
+
80
+ import logging
81
+ from typing import Any
82
+
83
+ from tenxgraph.adapters.llm.model_response_converter import ModelResponseConverter
84
+ from tenxgraph.checkpointer import InMemoryCheckpointer
85
+ from tenxgraph.graph import StateGraph, ToolNode
86
+ from tenxgraph.state.agent_state import AgentState
87
+ from tenxgraph.utils.callbacks import CallbackManager
88
+ from tenxgraph.utils.constants import END
89
+ from tenxgraph.utils.converter import convert_messages
90
+ from dotenv import load_dotenv
91
+ from injectq import Inject
92
+ from litellm import acompletion
93
+
94
+
95
+ # Configure logging for the module
96
+ logging.basicConfig(
97
+ level=logging.INFO,
98
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
99
+ handlers=[logging.StreamHandler()],
100
+ )
101
+ logger = logging.getLogger(__name__)
102
+
103
+ # Load environment variables from .env file
104
+ load_dotenv()
105
+
106
+ # Initialize in-memory checkpointer for maintaining conversation state
107
+ checkpointer = InMemoryCheckpointer()
108
+
109
+
110
+ """
111
+ Note: The docstring below will be used as the tool description and it will be
112
+ passed to the AI model for tool selection, so keep it relevant and concise.
113
+ This function will be converted to a tool with the following schema:
114
+ [
115
+ {
116
+ 'type': 'function',
117
+ 'function': {
118
+ 'name': 'get_weather',
119
+ 'description': 'Retrieve current weather information for a specified location.',
120
+ 'parameters': {
121
+ 'type': 'object',
122
+ 'properties': {
123
+ 'location': {'type': 'string'}
124
+ },
125
+ 'required': ['location']
126
+ }
127
+ }
128
+ }
129
+ ]
130
+
131
+ Parameters like tool_call_id, state, and checkpointer are injected automatically
132
+ by InjectQ when the tool is called by the agent.
133
+ Available injected parameters:
134
+ The following parameters are automatically injected by InjectQ when the tool is called,
135
+ but need to keep them as same name and type for proper injection:
136
+ - tool_call_id: Unique ID for the tool call
137
+ - state: Current AgentState containing conversation context
138
+ - config: Configuration dictionary passed during graph invocation
139
+
140
+ Below fields need to be used with Inject[] to get the instances:
141
+ - context_manager: ContextManager instance for managing context, like trimming
142
+ - publisher: Publisher instance for publishing events and logs
143
+ - checkpointer: InMemoryCheckpointer instance for state management
144
+ - store: InMemoryStore instance for temporary data storage
145
+ - callback: CallbackManager instance for handling callbacks
146
+
147
+ """
148
+
149
+
150
+ def get_weather(
151
+ location: str,
152
+ tool_call_id: str,
153
+ state: AgentState,
154
+ checkpointer: InMemoryCheckpointer = Inject[InMemoryCheckpointer],
155
+ ) -> str:
156
+ """Retrieve current weather information for a specified location."""
157
+ # Demonstrate access to injected parameters
158
+ logger.debug("***** Checkpointer instance: %s", checkpointer)
159
+ if tool_call_id:
160
+ logger.debug("Tool call ID: %s", tool_call_id)
161
+ if state and hasattr(state, "context"):
162
+ logger.debug("Number of messages in context: %d", len(state.context))
163
+
164
+ # Mock weather response - in production, this would call a real weather API
165
+ return f"The weather in {location} is sunny"
166
+
167
+
168
+ # Create a tool node containing all available tools
169
+ tool_node = ToolNode([get_weather])
170
+
171
+
172
+ async def main_agent(
173
+ state: AgentState,
174
+ config: dict,
175
+ checkpointer: InMemoryCheckpointer = Inject[InMemoryCheckpointer],
176
+ callback: CallbackManager = Inject[CallbackManager],
177
+ ) -> Any:
178
+ """
179
+ Main agent logic that processes user messages and generates responses.
180
+
181
+ This function implements the core AI agent behavior, handling both regular
182
+ conversation and tool-augmented responses. It uses LiteLLM for AI completion
183
+ and can access conversation history through the checkpointer.
184
+
185
+ Args:
186
+ state: Current agent state containing conversation context
187
+ config: Configuration dictionary containing thread_id and other settings
188
+ checkpointer: Checkpointer for retrieving conversation history (injected)
189
+ callback: Callback manager for handling events (injected)
190
+
191
+ Returns:
192
+ dict: AI completion response containing the agent's reply
193
+
194
+ The agent follows this logic:
195
+ 1. If the last message was a tool result, generate a final response without tools
196
+ 2. Otherwise, generate a response with available tools for potential tool usage
197
+ """
198
+ # System prompt defining the agent's role and capabilities
199
+ system_prompt = """
200
+ You are a helpful assistant.
201
+ Your task is to assist the user in finding information and answering questions.
202
+ You have access to various tools that can help you provide accurate information.
203
+ """
204
+
205
+ # Convert state messages to the format expected by the AI model
206
+ messages = convert_messages(
207
+ system_prompts=[{"role": "system", "content": system_prompt}],
208
+ state=state,
209
+ )
210
+
211
+ # Retrieve conversation history from checkpointer
212
+ try:
213
+ thread_messages = await checkpointer.aget_thread({"thread_id": config["thread_id"]})
214
+ logger.debug("Messages from checkpointer: %s", thread_messages)
215
+ except Exception as e:
216
+ logger.warning("Could not retrieve thread messages: %s", e)
217
+ thread_messages = []
218
+
219
+ # Log injected dependencies for debugging
220
+ logger.debug("Checkpointer in main_agent: %s", checkpointer)
221
+ logger.debug("CallbackManager in main_agent: %s", callback)
222
+
223
+ # Placeholder for MCP (Model Context Protocol) tools
224
+ # These would be additional tools from external sources
225
+ mcp_tools = []
226
+ is_stream = config.get("is_stream", False)
227
+
228
+ # Determine response strategy based on conversation context
229
+ if state.context and len(state.context) > 0 and state.context[-1].role == "tool":
230
+ # Last message was a tool result - generate final response without tools
231
+ logger.info("Generating final response after tool execution")
232
+ response = await acompletion(
233
+ model="gemini/gemini-2.0-flash-exp", # Updated model name
234
+ messages=messages,
235
+ stream=is_stream,
236
+ )
237
+ else:
238
+ # Regular response with tools available for potential usage
239
+ logger.info("Generating response with tools available")
240
+ tools = await tool_node.all_tools()
241
+ response = await acompletion(
242
+ model="gemini/gemini-2.0-flash-exp", # Updated model name
243
+ messages=messages,
244
+ tools=tools + mcp_tools,
245
+ stream=is_stream,
246
+ )
247
+
248
+ return ModelResponseConverter(
249
+ response,
250
+ converter="litellm",
251
+ )
252
+
253
+
254
+ def should_use_tools(state: AgentState) -> str:
255
+ """
256
+ Determine the next step in the graph execution based on the current state.
257
+
258
+ This routing function decides whether to continue with tool execution,
259
+ end the conversation, or proceed with the main agent logic.
260
+
261
+ Args:
262
+ state: Current agent state containing the conversation context
263
+
264
+ Returns:
265
+ str: Next node to execute ("TOOL" or END constant)
266
+
267
+ Routing Logic:
268
+ - If last message is from assistant and contains tool calls -> "TOOL"
269
+ - If last message is a tool result -> END (conversation complete)
270
+ - Otherwise -> END (default fallback)
271
+ """
272
+ if not state.context or len(state.context) == 0:
273
+ return END
274
+
275
+ last_message = state.context[-1]
276
+ if not last_message:
277
+ return END
278
+
279
+ # Check if assistant wants to use tools
280
+ if (
281
+ hasattr(last_message, "tools_calls")
282
+ and last_message.tools_calls
283
+ and len(last_message.tools_calls) > 0
284
+ and last_message.role == "assistant"
285
+ ):
286
+ logger.debug("Routing to TOOL node for tool execution")
287
+ return "TOOL"
288
+
289
+ # Check if we just received tool results
290
+ if last_message.role == "tool":
291
+ logger.info("Tool execution complete, ending conversation")
292
+ return END
293
+
294
+ # Default case: end conversation
295
+ logger.debug("Default routing: ending conversation")
296
+ return END
297
+
298
+
299
+ # Initialize the state graph for orchestrating agent flow
300
+ graph = StateGraph()
301
+
302
+ # Add nodes to the graph
303
+ graph.add_node("MAIN", main_agent) # Main agent processing node
304
+ graph.add_node("TOOL", tool_node) # Tool execution node
305
+
306
+ # Define conditional edges from MAIN node
307
+ # Routes to TOOL if tools should be used, otherwise ends
308
+ graph.add_conditional_edges(
309
+ "MAIN",
310
+ should_use_tools,
311
+ {"TOOL": "TOOL", END: END},
312
+ )
313
+
314
+ # Define edge from TOOL back to MAIN for continued conversation
315
+ graph.add_edge("TOOL", "MAIN")
316
+
317
+ # Set the entry point for graph execution
318
+ graph.set_entry_point("MAIN")
319
+
320
+ # Compile the graph with checkpointer for state management
321
+ app = graph.compile(
322
+ checkpointer=checkpointer,
323
+ )
324
+
325
+
326
+
327
+ '''
328
+
329
+ # Production templates (mirroring root repo tooling for convenience)
330
+ DEFAULT_PRE_COMMIT: Final[str] = """repos:
331
+ - repo: https://github.com/pre-commit/pre-commit-hooks
332
+ rev: v6.0.0
333
+ hooks:
334
+ - id: check-yaml
335
+ exclude: ^(tests|docs|examples)/
336
+ - id: trailing-whitespace
337
+ exclude: ^(tests|docs|examples)/
338
+ - id: check-added-large-files
339
+ args: [--maxkb=100]
340
+ exclude: ^(tests|docs|examples)/
341
+ - id: check-ast
342
+ exclude: ^(tests|docs|examples)/
343
+ - id: check-builtin-literals
344
+ exclude: ^(tests|docs|examples)/
345
+ - id: check-case-conflict
346
+ exclude: ^(tests|docs|examples)/
347
+ - id: check-docstring-first
348
+ exclude: ^(tests|docs|examples)/
349
+ - id: check-merge-conflict
350
+ exclude: ^(tests|docs|examples)/
351
+ - id: debug-statements
352
+ exclude: ^(tests|docs|examples)/
353
+ - id: detect-private-key
354
+ exclude: ^(tests|docs|examples)/
355
+
356
+ - repo: https://github.com/asottile/pyupgrade
357
+ rev: v3.17.0
358
+ hooks:
359
+ - id: pyupgrade
360
+ args: [--py310-plus]
361
+ exclude: ^(tests|docs|examples)/
362
+
363
+ - repo: https://github.com/astral-sh/ruff-pre-commit
364
+ rev: v0.5.7
365
+ hooks:
366
+ - id: ruff-format
367
+ exclude: ^(tests|docs|examples)/
368
+
369
+ - repo: https://github.com/astral-sh/ruff-pre-commit
370
+ rev: v0.5.7
371
+ hooks:
372
+ - id: ruff
373
+ args: [--fix, --exit-non-zero-on-fix]
374
+ exclude: ^(tests|docs|examples)/
375
+
376
+ - repo: https://github.com/PyCQA/bandit
377
+ rev: 1.7.9
378
+ hooks:
379
+ - id: bandit
380
+ args: [-c, pyproject.toml]
381
+ additional_dependencies: ["bandit[toml]"]
382
+ exclude: ^(tests|docs|examples)/
383
+ """
384
+
385
+ DEFAULT_PYPROJECT: Final[str] = """[build-system]
386
+ requires = ["setuptools>=61.0", "wheel"]
387
+ build-backend = "setuptools.build_meta"
388
+
389
+ [project]
390
+ name = "10xgraph-api-app"
391
+ version = "0.1.0"
392
+ description = "10xGraph API application"
393
+ readme = "README.md"
394
+ license = {text = "MIT"}
395
+ requires-python = ">=3.10"
396
+ authors = [
397
+ {name = "Your Name", email = "you@example.com"},
398
+ ]
399
+ maintainers = [
400
+ {name = "Your Name", email = "you@example.com"},
401
+ ]
402
+ keywords = ["10xgraph", "api", "fastapi", "cli"]
403
+ classifiers = [
404
+ "Development Status :: 4 - Beta",
405
+ "Intended Audience :: Developers",
406
+ "License :: OSI Approved :: MIT License",
407
+ "Operating System :: OS Independent",
408
+ "Programming Language :: Python :: 3",
409
+ "Programming Language :: Python :: 3.10",
410
+ "Programming Language :: Python :: 3.11",
411
+ "Programming Language :: Python :: 3.12",
412
+ "Programming Language :: Python :: 3.13",
413
+ ]
414
+ dependencies = [
415
+ "10xgraph-api",
416
+ ]
417
+
418
+ [project.scripts]
419
+ 10xgraph = "tenxgraph_api.cli.main:main"
420
+
421
+ [tool.ruff]
422
+ line-length = 100
423
+ target-version = "py312"
424
+ lint.fixable = ["ALL"]
425
+ lint.select = [
426
+ "E", "W", "F", "PL", "I", "B", "A", "S", "ISC", "ICN", "PIE", "Q",
427
+ "RET", "SIM", "TID", "RUF", "YTT", "UP", "C4", "PTH", "G", "INP", "T20",
428
+ ]
429
+ lint.ignore = [
430
+ "UP006", "UP007", "RUF012", "G004", "B904", "B008", "ISC001",
431
+ ]
432
+ lint.dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
433
+ exclude = [
434
+ "venv/*",
435
+ ]
436
+
437
+ [tool.ruff.lint.mccabe]
438
+ max-complexity = 10
439
+
440
+ [tool.ruff.lint.per-file-ignores]
441
+ "bin/*.py" = ["E402", "S603", "T201", "S101"]
442
+ "*/tests/*.py" = ["E402", "S603", "T201", "S101"]
443
+ "*/test/*.py" = ["E402", "S603", "T201", "S101"]
444
+ "scripts/*.py" = ["E402", "S603", "T201", "S101", "INP001"]
445
+ "*/__init__.py" = ["E402", "S603", "T201", "S101"]
446
+ "*/migrations/*.py" = ["E402", "S603", "T201", "S101"]
447
+
448
+ [tool.ruff.lint.isort]
449
+ lines-after-imports = 2
450
+
451
+ [tool.ruff.lint.pylint]
452
+ max-args = 10
453
+
454
+ [tool.ruff.format]
455
+ quote-style = "double"
456
+ indent-style = "space"
457
+ skip-magic-trailing-comma = false
458
+ line-ending = "auto"
459
+ docstring-code-format = true
460
+
461
+ [tool.ruff.lint.pydocstyle]
462
+ convention = "google"
463
+
464
+ [tool.bandit]
465
+ exclude_dirs = ["*/tests/*", "*/tenxgraph_api/tests/*"]
466
+ skips = ["B101", "B611", "B601", "B608"]
467
+
468
+ [tool.pytest.ini_options]
469
+ env = ["ENVIRONMENT=pytest"]
470
+ testpaths = ["tests"]
471
+ pythonpath = ["."]
472
+ filterwarnings = ["ignore::DeprecationWarning"]
473
+ addopts = [
474
+ "--cov=tenxgraph_api", "--cov-report=html", "--cov-report=term-missing",
475
+ "--cov-report=xml", "--cov-fail-under=0", "--strict-markers", "-v"
476
+ ]
477
+
478
+ [tool.coverage.run]
479
+ source = ["tenxgraph_api"]
480
+ branch = true
481
+ omit = [
482
+ "*/__init__.py", "*/tests/*", "*/migrations/*", "*/scripts/*", "*/venv/*", "*/.venv/*",
483
+ ]
484
+
485
+ [tool.coverage.report]
486
+ exclude_lines = [
487
+ "if __name__ == '__main__':", "pragma: no cover", "@abc.abstractmethod", "@abstractmethod",
488
+ "raise NotImplementedError",
489
+ ]
490
+ show_missing = true
491
+
492
+ [tool.coverage.paths]
493
+ source = ["tenxgraph_api", "*/site-packages/tenxgraph_api"]
494
+
495
+ [tool.pytest-env]
496
+ ENVIRONMENT = "pytest"
497
+ """
498
+
499
+
500
+ # Docker templates
501
+ # The published name on PyPI. The bare name "10xgraph-api" belongs to someone else.
502
+ CLI_PACKAGE = "10xgraph-api"
503
+
504
+
505
+ # Directories under ``tenxgraph_api/cli`` that only the CLI uses on a developer machine.
506
+ # The server imports nothing from them (it needs ``cli/constants.py`` only).
507
+ CLI_ONLY_ASSET_DIRS: Final[tuple[str, ...]] = ("templates", "config_editor")
508
+
509
+ # One-liner run in the image after install. ``find_spec`` locates the package without
510
+ # importing it, and a missing package is skipped rather than failing the build. Run with
511
+ # ``python -I`` so only the installed copy in site-packages can match, never a checkout on
512
+ # the working directory or PYTHONPATH.
513
+ _STRIP_CLI_ASSETS = (
514
+ "import importlib.util, pathlib, shutil; "
515
+ "spec = importlib.util.find_spec('tenxgraph_api'); "
516
+ "cli = pathlib.Path(spec.origin).parent / 'cli' if spec and spec.origin else None; "
517
+ f"[shutil.rmtree(cli / d, ignore_errors=True) for d in {CLI_ONLY_ASSET_DIRS!r} if cli]"
518
+ )
519
+
520
+
521
+ def generate_dockerfile_content(
522
+ python_version: str,
523
+ port: int,
524
+ requirements_file: str,
525
+ has_requirements: bool,
526
+ omit_cmd: bool = False,
527
+ has_pyproject: bool = False,
528
+ ) -> str:
529
+ """Generate the content for the Dockerfile.
530
+
531
+ Dependencies come from ``requirements_file`` when there is one, otherwise from the
532
+ ``[project].dependencies`` of ``pyproject.toml`` (what ``10xgraph init`` scaffolds), and
533
+ only as a last resort from the published CLI package alone.
534
+ """
535
+ dockerfile_lines = [
536
+ "# Dockerfile for 10xGraph API",
537
+ "# Generated by 10xgraph-api CLI",
538
+ "",
539
+ f"FROM python:{python_version}-slim",
540
+ "",
541
+ "# Set environment variables",
542
+ "ENV PYTHONDONTWRITEBYTECODE=1",
543
+ "ENV PYTHONUNBUFFERED=1",
544
+ "ENV PYTHONPATH=/app",
545
+ "# Default to production; overridable via .env or runtime env.",
546
+ "# In production the local in-memory telemetry store stays disabled",
547
+ "# (use OTEL / a publisher for observability instead).",
548
+ "ENV MODE=production",
549
+ "ENV IS_DEBUG=false",
550
+ "# Gunicorn worker count. Gunicorn reads WEB_CONCURRENCY natively; a sane default",
551
+ "# here beats gunicorn's built-in default of 1 worker. Tune to your CPU/memory",
552
+ "# at deploy time, e.g. `docker run -e WEB_CONCURRENCY=8 ...`.",
553
+ "ENV WEB_CONCURRENCY=2",
554
+ "",
555
+ "# Set work directory",
556
+ "WORKDIR /app",
557
+ "",
558
+ "# Install system dependencies",
559
+ "RUN apt-get update \\",
560
+ " && apt-get install -y --no-install-recommends \\",
561
+ " build-essential \\",
562
+ " curl \\",
563
+ " && rm -rf /var/lib/apt/lists/*",
564
+ "",
565
+ ]
566
+
567
+ if has_requirements:
568
+ dockerfile_lines.extend(
569
+ [
570
+ "# Install Python dependencies",
571
+ f"COPY {requirements_file} .",
572
+ "RUN pip install --no-cache-dir --upgrade pip \\",
573
+ f" && pip install --no-cache-dir -r {requirements_file} \\",
574
+ " && pip install --no-cache-dir gunicorn uvicorn",
575
+ "",
576
+ ]
577
+ )
578
+ elif has_pyproject:
579
+ dockerfile_lines.extend(
580
+ [
581
+ "# Install the project's dependencies from pyproject.toml",
582
+ "COPY pyproject.toml .",
583
+ "RUN python -c \"import tomllib; deps = tomllib.load(open('pyproject.toml', 'rb'))"
584
+ "['project'].get('dependencies', []); print('\\\\n'.join(deps))\" "
585
+ "> /tmp/requirements.txt \\",
586
+ " && pip install --no-cache-dir --upgrade pip \\",
587
+ " && pip install --no-cache-dir -r /tmp/requirements.txt \\",
588
+ f" && pip install --no-cache-dir {CLI_PACKAGE} gunicorn uvicorn",
589
+ "",
590
+ ]
591
+ )
592
+ else:
593
+ dockerfile_lines.extend(
594
+ [
595
+ "# No requirements.txt or pyproject.toml: install the 10xGraph CLI only",
596
+ "RUN pip install --no-cache-dir --upgrade pip \\",
597
+ f" && pip install --no-cache-dir {CLI_PACKAGE} \\",
598
+ " && pip install --no-cache-dir gunicorn uvicorn",
599
+ "",
600
+ ]
601
+ )
602
+
603
+ dockerfile_lines.extend(
604
+ [
605
+ "# Strip CLI-only assets the server never loads: project scaffolds, the bundled",
606
+ "# agent skill, and the local config editor (HTML/JS). Only the server code and",
607
+ "# its dependencies stay in the image.",
608
+ f'RUN python -I -c "{_STRIP_CLI_ASSETS}"',
609
+ "",
610
+ "# Copy application code",
611
+ "COPY . .",
612
+ "",
613
+ "# Create a non-root user",
614
+ "RUN groupadd -r appuser && useradd -r -g appuser appuser \\",
615
+ " && chown -R appuser:appuser /app",
616
+ "USER appuser",
617
+ "",
618
+ "# Expose port",
619
+ f"EXPOSE {port}",
620
+ "",
621
+ "# Health check",
622
+ "HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \\",
623
+ f" CMD curl -f http://localhost:{port}/ping || exit 1",
624
+ "",
625
+ ]
626
+ )
627
+
628
+ if not omit_cmd:
629
+ dockerfile_lines.extend(
630
+ [
631
+ "# Run the application (production)",
632
+ "# Use Gunicorn with Uvicorn workers for better performance and multi-core",
633
+ "# utilization",
634
+ "#",
635
+ "# --graceful-timeout is critical: an agent run can legitimately take",
636
+ "# minutes (the LLM client alone allows up to 600s). Gunicorn's default",
637
+ "# graceful timeout is 30s, so on every rolling deploy SIGTERM would",
638
+ "# hard-kill runs that were still mid-flight, truncating them. Give",
639
+ "# in-flight runs time to finish draining instead.",
640
+ (
641
+ 'CMD ["gunicorn", "-k", "uvicorn.workers.UvicornWorker", '
642
+ f'"-b", "0.0.0.0:{port}", '
643
+ f'"--graceful-timeout", "{GRACEFUL_TIMEOUT_SECONDS}", '
644
+ f'"--timeout", "{WORKER_TIMEOUT_SECONDS}", '
645
+ '"tenxgraph_api.src.app.main:app"]'
646
+ ),
647
+ "",
648
+ ]
649
+ )
650
+
651
+ return "\n".join(dockerfile_lines)
652
+
653
+
654
+ def generate_k8s_manifest_content(service_name: str, port: int) -> str:
655
+ """Generate a Kubernetes Deployment + Service manifest.
656
+
657
+ Exists because rolling deploys were the main way in-flight agent runs got
658
+ truncated, and no manifest was generated at all -- so every user hand-rolled
659
+ one, typically with the default 30s grace period that kills a run mid-LLM-call.
660
+
661
+ The three settings that matter here:
662
+
663
+ - ``terminationGracePeriodSeconds`` must exceed the app's own graceful timeout,
664
+ or the kubelet SIGKILLs the pod while it is still draining.
665
+ - The ``preStop`` sleep gives the load balancer time to notice the pod is
666
+ terminating and stop routing NEW requests to it. Without it, Kubernetes sends
667
+ SIGTERM and removes the endpoint concurrently, so requests can still arrive
668
+ at a pod that has already begun shutting down.
669
+ - The readiness probe is what takes the pod out of rotation; the liveness probe
670
+ must be slack enough that a busy worker is not restarted mid-run.
671
+ """
672
+ return "\n".join(
673
+ [
674
+ "apiVersion: apps/v1",
675
+ "kind: Deployment",
676
+ "metadata:",
677
+ f" name: {service_name}",
678
+ "spec:",
679
+ " replicas: 2",
680
+ " selector:",
681
+ " matchLabels:",
682
+ f" app: {service_name}",
683
+ " template:",
684
+ " metadata:",
685
+ " labels:",
686
+ f" app: {service_name}",
687
+ " spec:",
688
+ # Must be > the app's graceful timeout, else the kubelet SIGKILLs a
689
+ # pod that is still finishing a run.
690
+ f" terminationGracePeriodSeconds: {K8S_TERMINATION_GRACE_SECONDS}",
691
+ " containers:",
692
+ f" - name: {service_name}",
693
+ " image: 10xgraph-api:latest",
694
+ " ports:",
695
+ f" - containerPort: {port}",
696
+ " env:",
697
+ " - name: MODE",
698
+ ' value: "production"',
699
+ " - name: IS_DEBUG",
700
+ ' value: "false"',
701
+ " # CORS: production refuses wildcard origins with credentials.",
702
+ " - name: ORIGINS",
703
+ ' value: "https://your-frontend.example.com"',
704
+ " lifecycle:",
705
+ " preStop:",
706
+ " exec:",
707
+ " # Let the load balancer stop sending new traffic before",
708
+ " # the app starts shutting down.",
709
+ " command:",
710
+ ' - "/bin/sh"',
711
+ ' - "-c"',
712
+ f' - "sleep {K8S_PRESTOP_SLEEP_SECONDS}"',
713
+ " readinessProbe:",
714
+ " httpGet:",
715
+ " path: /ping",
716
+ f" port: {port}",
717
+ " initialDelaySeconds: 5",
718
+ " periodSeconds: 10",
719
+ " livenessProbe:",
720
+ " httpGet:",
721
+ " path: /ping",
722
+ f" port: {port}",
723
+ " # Deliberately slack: a worker busy with a long agent run",
724
+ " # must not be mistaken for a hung one and restarted.",
725
+ " initialDelaySeconds: 30",
726
+ " periodSeconds: 30",
727
+ " failureThreshold: 5",
728
+ " resources:",
729
+ " requests:",
730
+ ' cpu: "500m"',
731
+ ' memory: "512Mi"',
732
+ " limits:",
733
+ ' cpu: "2"',
734
+ ' memory: "2Gi"',
735
+ "---",
736
+ "apiVersion: v1",
737
+ "kind: Service",
738
+ "metadata:",
739
+ f" name: {service_name}",
740
+ "spec:",
741
+ " selector:",
742
+ f" app: {service_name}",
743
+ " ports:",
744
+ " - protocol: TCP",
745
+ f" port: 80",
746
+ f" targetPort: {port}",
747
+ "",
748
+ ]
749
+ )
750
+
751
+
752
+ def generate_docker_compose_content(service_name: str, port: int) -> str:
753
+ """Generate a simple docker-compose.yml content for the API service."""
754
+ return "\n".join(
755
+ [
756
+ "services:",
757
+ f" {service_name}:",
758
+ " build: .",
759
+ " image: 10xgraph-api:latest",
760
+ " environment:",
761
+ " - PYTHONUNBUFFERED=1",
762
+ " - PYTHONDONTWRITEBYTECODE=1",
763
+ # Default to production; local in-memory telemetry stays disabled.
764
+ " - MODE=production",
765
+ " - IS_DEBUG=false",
766
+ " ports:",
767
+ f" - '{port}:{port}'",
768
+ (
769
+ f" command: [ 'gunicorn', '-k', 'uvicorn.workers.UvicornWorker', "
770
+ f"'-b', '0.0.0.0:{port}', "
771
+ f"'--graceful-timeout', '{GRACEFUL_TIMEOUT_SECONDS}', "
772
+ f"'--timeout', '{WORKER_TIMEOUT_SECONDS}', "
773
+ "'tenxgraph_api.src.app.main:app' ]"
774
+ ),
775
+ # Give in-flight agent runs time to drain on `docker compose down`
776
+ # instead of being killed after the default 10s.
777
+ f" stop_grace_period: {GRACEFUL_TIMEOUT_SECONDS}s",
778
+ " restart: unless-stopped",
779
+ " # Consider adding resource limits and deploy configurations in a swarm/stack",
780
+ " # deploy:",
781
+ " # replicas: 2",
782
+ " # resources:",
783
+ " # limits:",
784
+ " # cpus: '1.0'",
785
+ " # memory: 512M",
786
+ ]
787
+ )
788
+
789
+
790
+ def generate_dockerignore_content() -> str:
791
+ """Generate a standard .dockerignore file content."""
792
+ return "\n".join(
793
+ [
794
+ "# Exclude environment secrets and local configs",
795
+ ".env",
796
+ ".env.*",
797
+ "secrets/",
798
+ "configs/",
799
+ "*.env",
800
+ "",
801
+ "# Exclude Python artifacts and caches",
802
+ "__pycache__/",
803
+ "*.py[cod]",
804
+ "*$py.class",
805
+ ".pytest_cache/",
806
+ ".coverage",
807
+ "htmlcov/",
808
+ ".mypy_cache/",
809
+ ".ruff_cache/",
810
+ "",
811
+ "# Exclude virtual environments",
812
+ ".venv/",
813
+ "venv/",
814
+ "env/",
815
+ "",
816
+ "# Exclude version control and IDE files",
817
+ ".git/",
818
+ ".gitignore",
819
+ ".idea/",
820
+ ".vscode/",
821
+ "*.swp",
822
+ "*.swo",
823
+ "",
824
+ "# Exclude built assets and distribution files",
825
+ "build/",
826
+ "dist/",
827
+ "*.egg-info/",
828
+ "",
829
+ "# Exclude local data directories",
830
+ "data/",
831
+ "db.sqlite3",
832
+ "store/",
833
+ "uploads/",
834
+ "eval_reports/",
835
+ "",
836
+ "# Exclude dev-only project folders: tests, evals, and installed agent skills",
837
+ "tests/",
838
+ "evals/",
839
+ ".claude/",
840
+ ".agents/",
841
+ ".github/",
842
+ "",
843
+ ]
844
+ )