Skip to content

Commit a2d0660

Browse files
committed
add locally mermaid validation
1 parent 105a6bf commit a2d0660

22 files changed

Lines changed: 408 additions & 365 deletions

README.md

Lines changed: 9 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,15 @@
1+
install Node
2+
```bash
3+
# macos
4+
brew install node
5+
6+
# linux
7+
sudo apt update && apt install -y nodejs npm
8+
```
9+
110
```bash
211
python3.12 -m venv .venv
312
source .venv/bin/activate
413
pip install -r requirements.txt
514
python run_web_app.py
615
```
7-
8-
Check networking for Mermaid validator
9-
```python
10-
import mermaid as md
11-
12-
error_mermaid = """
13-
graph TB
14-
subgraph "Core Agent System"
15-
A[DefaultAgent] --> B[Agent Execution]
16-
B --> C[Step Processing]
17-
C --> D[Action Execution]
18-
end
19-
20-
subgraph "Hook System"
21-
E[CombinedAgentHook] --> F[Hook 1]
22-
E --> G[Hook 2]
23-
E --> H[Hook N]
24-
end
25-
26-
subgraph "Event Flow"
27-
I[on_init] --> J[on_run_start]
28-
J --> K[on_step_start]
29-
K --> L[on_actions_generated]
30-
L --> M[on_action_started]
31-
M --> N[on_action_executed]
32-
N --> O[on_step_done]
33-
O --> P{More Steps?}
34-
P -->|Yes| K
35-
P -->|No| Q[on_run_done]
36-
end
37-
38-
A -.-> E : uses hooks
39-
B -.-> I
40-
C -.-> K
41-
D -.-> M
42-
43-
style E fill:#e1f5fe
44-
style A fill:#f3e5f5
45-
"""
46-
47-
true_mermaid = """
48-
graph TB
49-
subgraph "CLI Configuration Module"
50-
A[Main CLI Entry Point<br/>sweagent.run.run.main] --> B[Command Router]
51-
C[BasicCLI Configuration System<br/>sweagent.run.common.BasicCLI] --> D[Config Validation]
52-
53-
B --> E[Execution Commands]
54-
B --> F[Analysis Commands]
55-
B --> G[Inspector Commands]
56-
57-
E --> H[Single Execution<br/>run]
58-
E --> I[Batch Execution<br/>run-batch]
59-
E --> J[Replay Execution<br/>run-replay]
60-
E --> K[Shell Execution<br/>shell]
61-
62-
F --> L[Merge Predictions<br/>merge-preds]
63-
F --> M[Extract Predictions<br/>extract-pred]
64-
F --> N[Compare Runs<br/>compare-runs]
65-
F --> O[Quick Stats<br/>quick-stats]
66-
F --> P[Remove Unfinished<br/>remove-unfinished]
67-
68-
G --> Q[Terminal Inspector<br/>inspect]
69-
G --> R[Web Inspector<br/>inspector]
70-
end
71-
72-
subgraph "External Dependencies"
73-
S[execution_engines.md]
74-
T[inspector_tools.md]
75-
U[analysis_utilities.md]
76-
end
77-
78-
H --> S
79-
I --> S
80-
J --> S
81-
K --> S
82-
Q --> T
83-
R --> T
84-
L --> U
85-
M --> U
86-
N --> U
87-
O --> U
88-
P --> U
89-
90-
style A fill:#e1f5fe
91-
style C fill:#e8f5e8
92-
style B fill:#fff3e0
93-
"""
94-
95-
render = md.Mermaid(error_mermaid)
96-
print(render.svg_response.text[:50])
97-
# => Parse error on line 26:\n... end A -.-> E : use
98-
99-
render = md.Mermaid(true_mermaid)
100-
print(render.svg_response.text[:50])
101-
# => <svg id="mermaid-svg" width="100%" xmlns="http://w
102-
```

requirements.txt

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
ag-ui-protocol==0.1.8
2+
aiodns==3.5.0
23
aiohappyeyeballs==2.6.1
34
aiohttp==3.12.15
45
aiosignal==1.4.0
56
annotated-types==0.7.0
6-
anthropic==0.60.0
7+
anthropic==0.67.0
78
anyio==4.10.0
89
appnope==0.1.4
910
argcomplete==3.6.2
1011
asttokens==3.0.0
1112
attrs==25.3.0
1213
boto3==1.40.2
1314
botocore==1.40.2
15+
Brotli==1.1.0
1416
cachetools==5.5.2
1517
certifi==2025.8.3
18+
cffi==2.0.0
1619
charset-normalizer==3.4.2
1720
click==8.2.1
1821
cohere==5.16.2
@@ -25,11 +28,15 @@ eval_type_backport==0.2.2
2528
executing==2.2.0
2629
fastapi==0.116.1
2730
fastavro==1.12.0
31+
fastuuid==0.12.0
2832
filelock==3.18.0
2933
frozenlist==1.7.0
3034
fsspec==2025.7.0
35+
genai-prices==0.0.27
36+
gitdb==4.0.12
37+
GitPython==3.1.40
3138
google-auth==2.40.3
32-
google-genai==1.28.0
39+
google-genai==1.36.0
3340
googleapis-common-protos==1.70.0
3441
griffe==1.9.0
3542
groq==0.30.0
@@ -41,6 +48,7 @@ httpx-sse==0.4.0
4148
huggingface-hub==0.34.3
4249
idna==3.10
4350
importlib_metadata==8.7.0
51+
invoke==2.2.0
4452
ipykernel==6.30.1
4553
ipython==9.4.0
4654
ipython_pygments_lexers==1.1.1
@@ -52,29 +60,39 @@ jsonschema==4.25.0
5260
jsonschema-specifications==2025.4.1
5361
jupyter_client==8.6.3
5462
jupyter_core==5.8.1
63+
litellm==1.77.0
5564
logfire==4.1.0
5665
logfire-api==4.1.0
66+
loguru==0.7.3
5767
markdown-it-py==3.0.0
5868
MarkupSafe==3.0.2
5969
matplotlib-inline==0.1.7
6070
mcp==1.12.3
6171
mdurl==0.1.2
72+
mermaid-parser-py==0.0.2
6273
mermaid-py==0.8.0
63-
mistralai==1.9.3
74+
mistralai==1.9.10
6475
multidict==6.6.3
6576
nest-asyncio==1.6.0
66-
openai==1.98.0
77+
networkx==3.5
78+
nexus-rpc==1.1.0
79+
openai==1.107.1
80+
openai-agents==0.3.0
6781
opentelemetry-api==1.36.0
6882
opentelemetry-exporter-otlp-proto-common==1.36.0
6983
opentelemetry-exporter-otlp-proto-http==1.36.0
7084
opentelemetry-instrumentation==0.57b0
85+
opentelemetry-instrumentation-httpx==0.57b0
7186
opentelemetry-proto==1.36.0
7287
opentelemetry-sdk==1.36.0
7388
opentelemetry-semantic-conventions==0.57b0
89+
opentelemetry-util-http==0.57b0
7490
packaging==25.0
7591
parso==0.8.4
92+
pathspec==0.12.1
7693
pexpect==4.9.0
7794
platformdirs==4.3.8
95+
pminit==1.2.0
7896
prompt_toolkit==3.0.51
7997
propcache==0.3.2
8098
protobuf==6.31.1
@@ -83,17 +101,22 @@ ptyprocess==0.7.0
83101
pure_eval==0.2.3
84102
pyasn1==0.6.1
85103
pyasn1_modules==0.4.2
104+
pycares==4.11.0
105+
pycparser==2.23
86106
pydantic==2.11.7
87-
pydantic-ai==0.5.0
107+
pydantic-ai==1.0.5
88108
pydantic-ai-slim==0.5.0
89-
pydantic-evals==0.5.0
90-
pydantic-graph==0.5.0
109+
pydantic-evals==1.0.5
110+
pydantic-graph==1.0.5
91111
pydantic-settings==2.10.1
92112
pydantic_core==2.33.2
93113
Pygments==2.19.2
114+
pyperclip==1.9.0
94115
python-dateutil==2.9.0.post0
95116
python-dotenv==1.1.1
96117
python-multipart==0.0.20
118+
pythonmonkey==1.2.0
119+
pytz==2025.2
97120
PyYAML==6.0.2
98121
pyzmq==27.0.1
99122
referencing==0.36.2
@@ -104,26 +127,18 @@ rpds-py==0.26.0
104127
rsa==4.9.1
105128
s3transfer==0.13.1
106129
six==1.17.0
130+
smmap==5.0.2
107131
sniffio==1.3.1
108132
sse-starlette==3.0.2
109133
stack-data==0.6.3
110134
starlette==0.47.2
135+
temporalio==1.17.0
111136
tenacity==8.5.0
112137
tiktoken==0.10.0
113138
tokenizers==0.21.4
114139
tornado==6.5.1
115140
tqdm==4.67.1
116141
traitlets==5.14.3
117-
types-requests==2.32.4.20250611
118-
typing-inspection==0.4.1
119-
typing_extensions==4.14.1
120-
urllib3==2.5.0
121-
uvicorn==0.35.0
122-
wcwidth==0.2.13
123-
websockets==15.0.1
124-
wrapt==1.17.2
125-
yarl==1.20.1
126-
zipp==3.23.0
127142
tree-sitter==0.23.2
128143
tree-sitter-c==0.21.4
129144
tree-sitter-c-sharp==0.23.1
@@ -135,6 +150,14 @@ tree-sitter-language-pack==0.8.0
135150
tree-sitter-python==0.23.6
136151
tree-sitter-typescript==0.21.2
137152
tree-sitter-yaml==0.7.1
138-
pathspec==0.12.1
139-
python-multipart
140-
gitpython==3.1.40
153+
types-protobuf==6.30.2.20250822
154+
types-requests==2.32.4.20250611
155+
typing-inspection==0.4.1
156+
typing_extensions==4.14.1
157+
urllib3==2.5.0
158+
uvicorn==0.35.0
159+
wcwidth==0.2.13
160+
websockets==15.0.1
161+
wrapt==1.17.2
162+
yarl==1.20.1
163+
zipp==3.23.0

src/agent_tools/str_replace_editor.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,14 @@
1515
from typing import List, Optional, Tuple, Literal
1616
import io
1717

18+
import logging
19+
20+
# Configure logging and monitoring
21+
logging.basicConfig(level=logging.INFO)
22+
logger = logging.getLogger(__name__)
23+
1824
from pydantic_ai import RunContext, Tool
25+
1926
from .deps import CodeWikiDeps
2027
from utils import validate_mermaid_diagrams
2128

@@ -755,7 +762,8 @@ async def str_replace_editor(
755762
result = "\n".join(tool.logs)
756763

757764
if command != "view" and path.endswith(".md"):
758-
result = result + "\n---------- Mermaid validation ----------\n" + validate_mermaid_diagrams(absolute_path, path)
765+
mermaid_validation = await validate_mermaid_diagrams(absolute_path, path)
766+
result = result + "\n---------- Mermaid validation ----------\n" + mermaid_validation
759767

760768
return result
761769

src/cluster_modules.py

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,19 @@ def format_potential_core_components(leaf_nodes: List[str], components: Dict[str
1212
"""
1313
Format the potential core components into a string that can be used in the prompt.
1414
"""
15+
# Filter out any invalid leaf nodes that don't exist in components
16+
valid_leaf_nodes = []
17+
for leaf_node in leaf_nodes:
18+
if leaf_node in components:
19+
valid_leaf_nodes.append(leaf_node)
20+
else:
21+
import logging
22+
logger = logging.getLogger(__name__)
23+
logger.warning(f"Skipping invalid leaf node '{leaf_node}' - not found in components")
24+
1525
#group leaf nodes by file
1626
leaf_nodes_by_file = defaultdict(list)
17-
for leaf_node in leaf_nodes:
27+
for leaf_node in valid_leaf_nodes:
1828
leaf_nodes_by_file[components[leaf_node].relative_path].append(leaf_node)
1929

2030
potential_core_components = ""
@@ -49,7 +59,27 @@ def cluster_modules(
4959
response = call_llm(prompt, model=MAIN_MODEL)
5060

5161
#parse the response
52-
module_tree = eval(response.split("<GROUPED_COMPONENTS>")[1].split("</GROUPED_COMPONENTS>")[0])
62+
try:
63+
if "<GROUPED_COMPONENTS>" not in response or "</GROUPED_COMPONENTS>" not in response:
64+
import logging
65+
logger = logging.getLogger(__name__)
66+
logger.error(f"Invalid LLM response format - missing component tags: {response[:200]}...")
67+
return {}
68+
69+
response_content = response.split("<GROUPED_COMPONENTS>")[1].split("</GROUPED_COMPONENTS>")[0]
70+
module_tree = eval(response_content)
71+
72+
if not isinstance(module_tree, dict):
73+
import logging
74+
logger = logging.getLogger(__name__)
75+
logger.error(f"Invalid module tree format - expected dict, got {type(module_tree)}")
76+
return {}
77+
78+
except Exception as e:
79+
import logging
80+
logger = logging.getLogger(__name__)
81+
logger.error(f"Failed to parse LLM response: {e}. Response: {response[:200]}...")
82+
return {}
5383

5484
# check if the module tree is valid
5585
if len(module_tree) <= 1:
@@ -66,10 +96,21 @@ def cluster_modules(
6696
value[module_name] = module_info
6797

6898
for module_name, module_info in module_tree.items():
69-
sub_leaf_nodes = module_info["components"]
99+
sub_leaf_nodes = module_info.get("components", [])
100+
101+
# Filter sub_leaf_nodes to ensure they exist in components
102+
valid_sub_leaf_nodes = []
103+
for node in sub_leaf_nodes:
104+
if node in components:
105+
valid_sub_leaf_nodes.append(node)
106+
else:
107+
import logging
108+
logger = logging.getLogger(__name__)
109+
logger.warning(f"Skipping invalid sub leaf node '{node}' in module '{module_name}' - not found in components")
110+
70111
current_module_path.append(module_name)
71112
module_info["children"] = {}
72-
module_info["children"] = cluster_modules(sub_leaf_nodes, components, current_module_tree, module_name, current_module_path)
113+
module_info["children"] = cluster_modules(valid_sub_leaf_nodes, components, current_module_tree, module_name, current_module_path)
73114
current_module_path.pop()
74115

75116
return module_tree

0 commit comments

Comments
 (0)