hdsp-jupyter-extension 2.0.8__py3-none-any.whl → 2.0.11__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.
- agent_server/core/notebook_generator.py +4 -4
- agent_server/core/rag_manager.py +12 -3
- agent_server/core/retriever.py +2 -1
- agent_server/core/vllm_embedding_service.py +8 -5
- agent_server/langchain/ARCHITECTURE.md +7 -51
- agent_server/langchain/agent.py +31 -20
- agent_server/langchain/custom_middleware.py +234 -31
- agent_server/langchain/hitl_config.py +5 -8
- agent_server/langchain/logging_utils.py +7 -7
- agent_server/langchain/prompts.py +106 -120
- agent_server/langchain/tools/__init__.py +1 -10
- agent_server/langchain/tools/file_tools.py +9 -61
- agent_server/langchain/tools/jupyter_tools.py +0 -1
- agent_server/langchain/tools/lsp_tools.py +8 -8
- agent_server/langchain/tools/resource_tools.py +12 -12
- agent_server/langchain/tools/search_tools.py +3 -158
- agent_server/prompts/file_action_prompts.py +8 -8
- agent_server/routers/langchain_agent.py +200 -125
- hdsp_agent_core/__init__.py +46 -47
- hdsp_agent_core/factory.py +6 -10
- hdsp_agent_core/interfaces.py +4 -2
- hdsp_agent_core/knowledge/__init__.py +5 -5
- hdsp_agent_core/knowledge/chunking.py +87 -61
- hdsp_agent_core/knowledge/loader.py +103 -101
- hdsp_agent_core/llm/service.py +192 -107
- hdsp_agent_core/managers/config_manager.py +16 -22
- hdsp_agent_core/managers/session_manager.py +5 -4
- hdsp_agent_core/models/__init__.py +12 -12
- hdsp_agent_core/models/agent.py +15 -8
- hdsp_agent_core/models/common.py +1 -2
- hdsp_agent_core/models/rag.py +48 -111
- hdsp_agent_core/prompts/__init__.py +12 -12
- hdsp_agent_core/prompts/cell_action_prompts.py +9 -7
- hdsp_agent_core/services/agent_service.py +10 -8
- hdsp_agent_core/services/chat_service.py +10 -6
- hdsp_agent_core/services/rag_service.py +3 -6
- hdsp_agent_core/tests/conftest.py +4 -1
- hdsp_agent_core/tests/test_factory.py +2 -2
- hdsp_agent_core/tests/test_services.py +12 -19
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/build_log.json +1 -1
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/package.json +2 -2
- hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.8740a527757068814573.js → hdsp_jupyter_extension-2.0.11.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.2d9fb488c82498c45c2d.js +93 -4
- hdsp_jupyter_extension-2.0.11.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.2d9fb488c82498c45c2d.js.map +1 -0
- hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.e4ff4b5779b5e049f84c.js → hdsp_jupyter_extension-2.0.11.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.58c1e128ba0b76f41f04.js +153 -130
- hdsp_jupyter_extension-2.0.11.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.58c1e128ba0b76f41f04.js.map +1 -0
- hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.020cdb0b864cfaa4e41e.js → hdsp_jupyter_extension-2.0.11.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.9da31d1134a53b0c4af5.js +6 -6
- hdsp_jupyter_extension-2.0.11.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.9da31d1134a53b0c4af5.js.map +1 -0
- {hdsp_jupyter_extension-2.0.8.dist-info → hdsp_jupyter_extension-2.0.11.dist-info}/METADATA +1 -3
- hdsp_jupyter_extension-2.0.11.dist-info/RECORD +144 -0
- jupyter_ext/__init__.py +21 -11
- jupyter_ext/_version.py +1 -1
- jupyter_ext/handlers.py +69 -50
- jupyter_ext/labextension/build_log.json +1 -1
- jupyter_ext/labextension/package.json +2 -2
- jupyter_ext/labextension/static/{frontend_styles_index_js.8740a527757068814573.js → frontend_styles_index_js.2d9fb488c82498c45c2d.js} +93 -4
- jupyter_ext/labextension/static/frontend_styles_index_js.2d9fb488c82498c45c2d.js.map +1 -0
- jupyter_ext/labextension/static/{lib_index_js.e4ff4b5779b5e049f84c.js → lib_index_js.58c1e128ba0b76f41f04.js} +153 -130
- jupyter_ext/labextension/static/lib_index_js.58c1e128ba0b76f41f04.js.map +1 -0
- jupyter_ext/labextension/static/{remoteEntry.020cdb0b864cfaa4e41e.js → remoteEntry.9da31d1134a53b0c4af5.js} +6 -6
- jupyter_ext/labextension/static/remoteEntry.9da31d1134a53b0c4af5.js.map +1 -0
- hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.8740a527757068814573.js.map +0 -1
- hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.e4ff4b5779b5e049f84c.js.map +0 -1
- hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.020cdb0b864cfaa4e41e.js.map +0 -1
- hdsp_jupyter_extension-2.0.8.dist-info/RECORD +0 -144
- jupyter_ext/labextension/static/frontend_styles_index_js.8740a527757068814573.js.map +0 -1
- jupyter_ext/labextension/static/lib_index_js.e4ff4b5779b5e049f84c.js.map +0 -1
- jupyter_ext/labextension/static/remoteEntry.020cdb0b864cfaa4e41e.js.map +0 -1
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/etc/jupyter/jupyter_server_config.d/hdsp_jupyter_extension.json +0 -0
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/install.json +0 -0
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b80.c095373419d05e6f141a.js +0 -0
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b80.c095373419d05e6f141a.js.map +0 -0
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b81.61e75fb98ecff46cf836.js +0 -0
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b81.61e75fb98ecff46cf836.js.map +0 -0
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/style.js +0 -0
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_babel_runtime_helpers_esm_extends_js-node_modules_emotion_serialize_dist-051195.e2553aab0c3963b83dd7.js +0 -0
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_babel_runtime_helpers_esm_extends_js-node_modules_emotion_serialize_dist-051195.e2553aab0c3963b83dd7.js.map +0 -0
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js +0 -0
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js.map +0 -0
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js +0 -0
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js.map +0 -0
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js +0 -0
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js.map +0 -0
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js +0 -0
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js.map +0 -0
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js +0 -0
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js.map +0 -0
- {hdsp_jupyter_extension-2.0.8.dist-info → hdsp_jupyter_extension-2.0.11.dist-info}/WHEEL +0 -0
- {hdsp_jupyter_extension-2.0.8.dist-info → hdsp_jupyter_extension-2.0.11.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Search Tools for LangChain Agent
|
|
3
3
|
|
|
4
|
-
Provides tools for searching
|
|
5
|
-
|
|
6
|
-
using subprocess (find/grep/ripgrep).
|
|
4
|
+
Provides tools for searching notebook cells.
|
|
5
|
+
For file searching, use execute_command_tool with find/grep commands.
|
|
7
6
|
|
|
8
7
|
Key features:
|
|
9
8
|
- Returns command info for client-side execution via subprocess
|
|
10
|
-
- Supports ripgrep (rg) if available, falls back to grep
|
|
11
9
|
- Executes immediately without user approval
|
|
12
10
|
- Shows the command being executed in status messages
|
|
13
11
|
"""
|
|
14
12
|
|
|
15
13
|
import logging
|
|
16
|
-
import shutil
|
|
17
14
|
from typing import Any, Dict, List, Optional
|
|
18
15
|
|
|
19
16
|
from langchain_core.tools import tool
|
|
@@ -22,23 +19,6 @@ from pydantic import BaseModel, Field
|
|
|
22
19
|
logger = logging.getLogger(__name__)
|
|
23
20
|
|
|
24
21
|
|
|
25
|
-
class SearchWorkspaceInput(BaseModel):
|
|
26
|
-
"""Input schema for search_workspace tool"""
|
|
27
|
-
|
|
28
|
-
pattern: str = Field(description="Search pattern (regex or text)")
|
|
29
|
-
file_types: List[str] = Field(
|
|
30
|
-
default=["*.py", "*.ipynb"],
|
|
31
|
-
description="File patterns to search (e.g., ['*.py', '*.ipynb'])",
|
|
32
|
-
)
|
|
33
|
-
path: str = Field(default=".", description="Directory to search in")
|
|
34
|
-
max_results: int = Field(default=50, description="Maximum number of results")
|
|
35
|
-
case_sensitive: bool = Field(default=False, description="Case-sensitive search")
|
|
36
|
-
execution_result: Optional[Dict[str, Any]] = Field(
|
|
37
|
-
default=None,
|
|
38
|
-
description="Execution result payload from the client",
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
|
|
42
22
|
class SearchNotebookCellsInput(BaseModel):
|
|
43
23
|
"""Input schema for search_notebook_cells tool"""
|
|
44
24
|
|
|
@@ -58,72 +38,6 @@ class SearchNotebookCellsInput(BaseModel):
|
|
|
58
38
|
)
|
|
59
39
|
|
|
60
40
|
|
|
61
|
-
def _is_ripgrep_available() -> bool:
|
|
62
|
-
"""Check if ripgrep (rg) is installed and available."""
|
|
63
|
-
return shutil.which("rg") is not None
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def _build_grep_command(
|
|
67
|
-
pattern: str,
|
|
68
|
-
file_types: List[str],
|
|
69
|
-
path: str,
|
|
70
|
-
case_sensitive: bool,
|
|
71
|
-
max_results: int,
|
|
72
|
-
) -> tuple[str, str]:
|
|
73
|
-
"""
|
|
74
|
-
Build a grep/ripgrep command for searching files.
|
|
75
|
-
|
|
76
|
-
Returns:
|
|
77
|
-
Tuple of (command_string, tool_name) where tool_name is 'rg' or 'grep'
|
|
78
|
-
"""
|
|
79
|
-
# Check ripgrep availability (this check will also be done on client)
|
|
80
|
-
use_ripgrep = _is_ripgrep_available()
|
|
81
|
-
|
|
82
|
-
if use_ripgrep:
|
|
83
|
-
# Build ripgrep command
|
|
84
|
-
cmd_parts = ["rg", "--line-number", "--with-filename"]
|
|
85
|
-
|
|
86
|
-
if not case_sensitive:
|
|
87
|
-
cmd_parts.append("--ignore-case")
|
|
88
|
-
|
|
89
|
-
# Add file type filters using glob patterns
|
|
90
|
-
for ft in file_types:
|
|
91
|
-
cmd_parts.extend(["--glob", ft])
|
|
92
|
-
|
|
93
|
-
# Limit results
|
|
94
|
-
cmd_parts.extend(["--max-count", str(max_results)])
|
|
95
|
-
|
|
96
|
-
# Escape pattern for shell
|
|
97
|
-
escaped_pattern = pattern.replace("'", "'\\''")
|
|
98
|
-
cmd_parts.append(f"'{escaped_pattern}'")
|
|
99
|
-
cmd_parts.append(path)
|
|
100
|
-
|
|
101
|
-
return " ".join(cmd_parts), "rg"
|
|
102
|
-
else:
|
|
103
|
-
# Build find + grep command for cross-platform compatibility
|
|
104
|
-
find_parts = ["find", path, "-type", "f", "("]
|
|
105
|
-
|
|
106
|
-
for i, ft in enumerate(file_types):
|
|
107
|
-
if i > 0:
|
|
108
|
-
find_parts.append("-o")
|
|
109
|
-
find_parts.extend(["-name", f"'{ft}'"])
|
|
110
|
-
|
|
111
|
-
find_parts.append(")")
|
|
112
|
-
|
|
113
|
-
# Add grep with proper flags
|
|
114
|
-
grep_flags = "-n" # Line numbers
|
|
115
|
-
if not case_sensitive:
|
|
116
|
-
grep_flags += "i"
|
|
117
|
-
|
|
118
|
-
# Escape pattern for shell
|
|
119
|
-
escaped_pattern = pattern.replace("'", "'\\''")
|
|
120
|
-
|
|
121
|
-
# Combine with xargs for efficiency
|
|
122
|
-
cmd = f"{' '.join(find_parts)} 2>/dev/null | xargs grep -{grep_flags} '{escaped_pattern}' 2>/dev/null | head -n {max_results}"
|
|
123
|
-
|
|
124
|
-
return cmd, "grep"
|
|
125
|
-
|
|
126
|
-
|
|
127
41
|
def _build_notebook_search_command(
|
|
128
42
|
pattern: str,
|
|
129
43
|
notebook_path: Optional[str],
|
|
@@ -139,74 +53,6 @@ def _build_notebook_search_command(
|
|
|
139
53
|
)
|
|
140
54
|
|
|
141
55
|
|
|
142
|
-
@tool(args_schema=SearchWorkspaceInput)
|
|
143
|
-
def search_workspace_tool(
|
|
144
|
-
pattern: str,
|
|
145
|
-
file_types: List[str] = None,
|
|
146
|
-
path: str = ".",
|
|
147
|
-
max_results: int = 50,
|
|
148
|
-
case_sensitive: bool = False,
|
|
149
|
-
execution_result: Optional[Dict[str, Any]] = None,
|
|
150
|
-
workspace_root: str = ".",
|
|
151
|
-
) -> Dict[str, Any]:
|
|
152
|
-
"""
|
|
153
|
-
Search for a pattern across files in the workspace.
|
|
154
|
-
|
|
155
|
-
This tool is executed on the client side using subprocess (grep/ripgrep).
|
|
156
|
-
Searches both regular files and Jupyter notebooks.
|
|
157
|
-
|
|
158
|
-
Args:
|
|
159
|
-
pattern: Search pattern (regex or text)
|
|
160
|
-
file_types: File patterns to search (default: ['*.py', '*.ipynb'])
|
|
161
|
-
path: Directory to search in (relative to workspace)
|
|
162
|
-
max_results: Maximum number of results to return
|
|
163
|
-
case_sensitive: Whether search is case-sensitive
|
|
164
|
-
|
|
165
|
-
Returns:
|
|
166
|
-
Dict with search results or pending_execution status
|
|
167
|
-
"""
|
|
168
|
-
if file_types is None:
|
|
169
|
-
file_types = ["*.py", "*.ipynb"]
|
|
170
|
-
|
|
171
|
-
# Build the search command
|
|
172
|
-
command, tool_used = _build_grep_command(
|
|
173
|
-
pattern=pattern,
|
|
174
|
-
file_types=file_types,
|
|
175
|
-
path=path,
|
|
176
|
-
case_sensitive=case_sensitive,
|
|
177
|
-
max_results=max_results,
|
|
178
|
-
)
|
|
179
|
-
|
|
180
|
-
response: Dict[str, Any] = {
|
|
181
|
-
"tool": "search_workspace_tool",
|
|
182
|
-
"parameters": {
|
|
183
|
-
"pattern": pattern,
|
|
184
|
-
"file_types": file_types,
|
|
185
|
-
"path": path,
|
|
186
|
-
"max_results": max_results,
|
|
187
|
-
"case_sensitive": case_sensitive,
|
|
188
|
-
},
|
|
189
|
-
"command": command,
|
|
190
|
-
"tool_used": tool_used,
|
|
191
|
-
"status": "pending_execution",
|
|
192
|
-
"message": "Search queued for execution by client",
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
if execution_result is not None:
|
|
196
|
-
response["execution_result"] = execution_result
|
|
197
|
-
response["status"] = "complete"
|
|
198
|
-
response["message"] = "Search executed with client-reported results"
|
|
199
|
-
# Parse the execution result to extract search results
|
|
200
|
-
if isinstance(execution_result, dict):
|
|
201
|
-
response["success"] = execution_result.get("success", False)
|
|
202
|
-
response["results"] = execution_result.get("results", [])
|
|
203
|
-
response["total_results"] = execution_result.get("total_results", 0)
|
|
204
|
-
if "error" in execution_result:
|
|
205
|
-
response["error"] = execution_result["error"]
|
|
206
|
-
|
|
207
|
-
return response
|
|
208
|
-
|
|
209
|
-
|
|
210
56
|
@tool(args_schema=SearchNotebookCellsInput)
|
|
211
57
|
def search_notebook_cells_tool(
|
|
212
58
|
pattern: str,
|
|
@@ -281,11 +127,10 @@ def create_search_tools(workspace_root: str = ".") -> List:
|
|
|
281
127
|
Note: workspace_root is not used since tools return pending_execution
|
|
282
128
|
and actual execution happens on the client side.
|
|
283
129
|
"""
|
|
284
|
-
return [
|
|
130
|
+
return [search_notebook_cells_tool]
|
|
285
131
|
|
|
286
132
|
|
|
287
133
|
# Export all tools
|
|
288
134
|
SEARCH_TOOLS = [
|
|
289
|
-
search_workspace_tool,
|
|
290
135
|
search_notebook_cells_tool,
|
|
291
136
|
]
|
|
@@ -20,9 +20,9 @@ def format_file_fix_prompt(
|
|
|
20
20
|
for rf in related_files:
|
|
21
21
|
if rf.get("content"):
|
|
22
22
|
related_context += f"""
|
|
23
|
-
### {rf[
|
|
23
|
+
### {rf["path"]}
|
|
24
24
|
```python
|
|
25
|
-
{rf[
|
|
25
|
+
{rf["content"]}
|
|
26
26
|
```
|
|
27
27
|
"""
|
|
28
28
|
|
|
@@ -33,9 +33,9 @@ def format_file_fix_prompt(
|
|
|
33
33
|
{error_output}
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
## 메인 파일: {main_file[
|
|
36
|
+
## 메인 파일: {main_file["path"]}
|
|
37
37
|
```python
|
|
38
|
-
{main_file[
|
|
38
|
+
{main_file["content"]}
|
|
39
39
|
```
|
|
40
40
|
{related_context}
|
|
41
41
|
|
|
@@ -97,17 +97,17 @@ def format_file_custom_prompt(
|
|
|
97
97
|
for rf in related_files:
|
|
98
98
|
if rf.get("content"):
|
|
99
99
|
related_context += f"""
|
|
100
|
-
### {rf[
|
|
100
|
+
### {rf["path"]}
|
|
101
101
|
```python
|
|
102
|
-
{rf[
|
|
102
|
+
{rf["content"]}
|
|
103
103
|
```
|
|
104
104
|
"""
|
|
105
105
|
|
|
106
106
|
return f"""{custom_prompt}
|
|
107
107
|
|
|
108
|
-
## 메인 파일: {main_file[
|
|
108
|
+
## 메인 파일: {main_file["path"]}
|
|
109
109
|
```python
|
|
110
|
-
{main_file[
|
|
110
|
+
{main_file["content"]}
|
|
111
111
|
```
|
|
112
112
|
{related_context}
|
|
113
113
|
"""
|