Skip to content

Commit 9d9f434

Browse files
committed
feat(ai-markmap-agent): simplify writer prompt and fix resume functionality
Writer prompt simplification: - Remove URL format instructions from writer_behavior.md - Compress problem data to ID list only (reduces tokens) - Add _convert_plain_leetcode_to_links() to post_processing.py - Post-processing handles plain text "LeetCode XXX" -> full link Resume functionality fixes: - Fix load_expert_responses_from_run to scan correct file categories - Add parse_suggestions_from_response() standalone function - Add parse_adoption_list_from_response() standalone function - Fix run_expert_review resume to load expert_suggestions into state - Fix run_full_discussion resume to load adoption_lists into state
1 parent 3f32c87 commit 9d9f434

13 files changed

+1035
-3433
lines changed

docs/pages/mindmaps/neetcode_ontology_agent_evolved_en.html

Lines changed: 99 additions & 592 deletions
Large diffs are not rendered by default.

docs/pages/mindmaps/neetcode_ontology_agent_evolved_zh-TW.html

Lines changed: 139 additions & 632 deletions
Large diffs are not rendered by default.

tools/ai-markmap-agent/config/config.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ urls:
2929
github:
3030
base: "https://github.com/lufftw/neetcode"
3131
solution_template: "https://github.com/lufftw/neetcode/blob/main/{solution_file}"
32+
33+
leetcode:
34+
base: "https://leetcode.com"
35+
problem_template: "https://leetcode.com/problems/{slug}/"
3236

3337
# -----------------------------------------------------------------------------
3438
# Data Sources Configuration

tools/ai-markmap-agent/outputs/versions/v1/neetcode_ontology_agent_evolved_en.html

Lines changed: 0 additions & 788 deletions
This file was deleted.

tools/ai-markmap-agent/outputs/versions/v1/neetcode_ontology_agent_evolved_en.md

Lines changed: 0 additions & 694 deletions
This file was deleted.

tools/ai-markmap-agent/outputs/versions/v1/neetcode_ontology_agent_evolved_zh-TW.html

Lines changed: 159 additions & 311 deletions
Large diffs are not rendered by default.

tools/ai-markmap-agent/outputs/versions/v1/neetcode_ontology_agent_evolved_zh-TW.md

Lines changed: 159 additions & 312 deletions
Large diffs are not rendered by default.

tools/ai-markmap-agent/prompts/writer/writer_behavior.md

Lines changed: 24 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ You are the **Markmap Writer** operating in **refinement mode**. Your task is to
77
1. **Apply ONLY the adopted improvements** - Do not add your own ideas or "fix" things not mentioned
88
2. **Preserve baseline quality** - The baseline was carefully crafted; maintain its strengths
99
3. **Match existing style** - New content should be indistinguishable from original content
10-
4. **Verify all links** - Use the reference data to ensure URLs are correct
10+
4. **Normalize problem references** - Follow the link format rules below
1111

1212
---
1313

@@ -39,28 +39,24 @@ For context, here are the full descriptions and rationales:
3939

4040
## Reference Data
4141

42-
Use this data to verify problem information and generate correct links:
43-
44-
### Problem Metadata
42+
### Problems With Solutions (auto-linked)
4543
{problem_data}
4644

47-
### URL Templates
45+
### Link Format Rules
4846

49-
**CRITICAL: Link Generation Rules**
47+
**For problems in the list above:**
48+
- Write ONLY: `LeetCode {id} - {title}` (NO URL needed)
49+
- Links will be generated automatically in post-processing
50+
- If baseline has URLs for these problems, REMOVE them
51+
- Example: `LeetCode 11 - Container With Most Water`
5052

51-
1. **If problem has `solution_file` (Has Solution = "Yes"):**
52-
- Use GitHub project link: `{github_template}`
53-
- Replace `{solution_file}` with the actual solution file path from problem metadata
54-
- Example: `https://github.com/lufftw/neetcode/blob/main/solutions/0905_sort_array_by_parity.py`
55-
- This takes priority over LeetCode links
53+
**For problems NOT in the list:**
54+
- Include the LeetCode link: `[LeetCode {id} - {title}](https://leetcode.com/problems/{slug}/description/)`
55+
- Example: `[LeetCode 999 - Some Problem](https://leetcode.com/problems/some-problem/description/)`
5656

57-
2. **If problem has NO `solution_file` (Has Solution = "No"):**
58-
- Use LeetCode problem link with CORRECT format
59-
- Format: `https://leetcode.com/problems/{slug}/description/`
60-
- The `slug` is provided in the problem metadata table
61-
- **IMPORTANT**: Use the slug as-is from metadata (e.g., `container-with-most-water`), NOT the problem ID format (e.g., NOT `0011_container_with_most_water`)
62-
- You know the correct LeetCode URL format - use it correctly
63-
- Example: `https://leetcode.com/problems/container-with-most-water/description/`
57+
**Important:**
58+
- Do NOT add GitHub solution links (added automatically where available)
59+
- Use full "LeetCode" not "LC"
6460

6561
### Ontology Reference
6662
{ontology_summary}
@@ -72,7 +68,7 @@ Use this data to verify problem information and generate correct links:
7268
1. **Read the baseline carefully** - Understand its structure and style
7369
2. **Apply each improvement one by one** - Be surgical and precise
7470
3. **Maintain consistency** - New content should match existing style
75-
4. **Verify links** - All URLs must be correct per the templates
71+
4. **Normalize problem references** - Follow the link format rules above
7672
5. **Output the complete refined Markmap**
7773

7874
---
@@ -97,24 +93,11 @@ Use this data to verify problem information and generate correct links:
9793
- Match code formatting with baseline
9894
- Match emoji usage with baseline (if any)
9995

100-
### Link Format
101-
102-
**Priority Order:**
103-
1. **If solution exists (Has Solution = "Yes"):** Use GitHub project link
104-
- Format: `[Problem Title](https://github.com/lufftw/neetcode/blob/main/solutions/XXXX_problem_name.py)`
105-
- Example: `[LeetCode 905 - Sort Array By Parity](https://github.com/lufftw/neetcode/blob/main/solutions/0905_sort_array_by_parity.py)`
106-
107-
2. **If no solution (Has Solution = "No"):** Use LeetCode problem link
108-
- Format: `[Problem Title](https://leetcode.com/problems/{slug}/description/)`
109-
- Use the `slug` from problem metadata (e.g., `container-with-most-water`)
110-
- **DO NOT** use problem ID format like `0011_container_with_most_water`
111-
- **DO** use the correct slug format: `container-with-most-water`
112-
- Example: `[LeetCode 11 - Container With Most Water](https://leetcode.com/problems/container-with-most-water/description/)`
113-
114-
**General Rules:**
115-
- Always use full "LeetCode" not "LC" in link text
116-
- Format: `[Problem Title](url)`
117-
- Verify all links are correct before outputting
96+
### Problem References
97+
- Use full "LeetCode" not "LC"
98+
- For problems in our list: `LeetCode {id} - {title}` (no URL)
99+
- For other problems: `[LeetCode {id} - {title}](leetcode_url)`
100+
- Do NOT add GitHub solution links
118101

119102
### Markmap Features
120103
- Use `<!-- markmap: fold -->` for collapsible sections
@@ -133,15 +116,15 @@ You would find those problems in the baseline and add the marker:
133116

134117
Before:
135118
```markdown
136-
- [x] [LeetCode 3: Longest Substring Without Repeating Characters](url)
119+
- [x] [LeetCode 3 - Longest Substring Without Repeating Characters](https://leetcode.com/...)
137120
```
138121

139-
After:
122+
After (assuming LeetCode 3 is in our solutions list):
140123
```markdown
141-
- [x] 🔥 [LeetCode 3: Longest Substring Without Repeating Characters](url)
124+
- [x] 🔥 LeetCode 3 - Longest Substring Without Repeating Characters
142125
```
143126

144-
The rest of the line remains unchanged.
127+
Note: The URL is removed because LeetCode 3 is in our solutions list. Post-processing will add the correct links automatically.
145128

146129
---
147130

tools/ai-markmap-agent/src/agents/expert.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,88 @@ def to_dict(self) -> dict[str, Any]:
5959
}
6060

6161

62+
def parse_suggestions_from_response(response: str, expert_id: str) -> list[Suggestion]:
63+
"""
64+
Parse suggestions from LLM response (standalone function for resume).
65+
66+
Args:
67+
response: Raw LLM response text
68+
expert_id: Expert identifier (e.g., "architect", "professor")
69+
70+
Returns:
71+
List of parsed Suggestion objects
72+
"""
73+
suggestions = []
74+
75+
# Pattern to match suggestion blocks
76+
# Looking for patterns like: ### A1: Title or ## A1 - Title
77+
suggestion_pattern = r'#{2,3}\s*([A-Z]\d+)[:\s-]+(.+?)(?=#{2,3}\s*[A-Z]\d+|$)'
78+
matches = re.findall(suggestion_pattern, response, re.DOTALL)
79+
80+
for match in matches:
81+
suggestion_id = match[0]
82+
content = match[1].strip()
83+
84+
# Extract fields
85+
type_match = re.search(r'\*\*Type\*\*:\s*(\w+)', content, re.IGNORECASE)
86+
location_match = re.search(r'\*\*Location\*\*:\s*(.+?)(?=\n\*\*|\n-|\n#|$)', content, re.IGNORECASE | re.DOTALL)
87+
what_match = re.search(r'\*\*What\*\*:\s*(.+?)(?=\n\*\*|\n-|\n#|$)', content, re.IGNORECASE | re.DOTALL)
88+
why_match = re.search(r'\*\*Why\*\*:\s*(.+?)(?=\n\*\*|\n-|\n#|$)', content, re.IGNORECASE | re.DOTALL)
89+
90+
suggestions.append(Suggestion(
91+
id=suggestion_id,
92+
expert_id=expert_id,
93+
type=type_match.group(1).strip().lower() if type_match else "modify",
94+
location=location_match.group(1).strip() if location_match else "",
95+
what=what_match.group(1).strip() if what_match else content[:200],
96+
why=why_match.group(1).strip() if why_match else "",
97+
raw_text=content,
98+
))
99+
100+
return suggestions
101+
102+
103+
def parse_adoption_list_from_response(response: str) -> list[str]:
104+
"""
105+
Parse adoption list from discussion response (standalone function for resume).
106+
107+
Args:
108+
response: Raw LLM discussion response text
109+
110+
Returns:
111+
List of adopted suggestion IDs (e.g., ["A1", "P2", "E3"])
112+
"""
113+
adopted_ids = []
114+
115+
# Strategy 1: Look for explicit adoption section
116+
adoption_patterns = [
117+
r'(?:^|\n)#+\s*(?:My\s+)?Final\s+Adoption\s+List.*',
118+
r'I\s+recommend\s+adopting\s+(?:these\s+)?suggestions?:?\s*\n.*',
119+
r'(?:^|\n)#+\s*Part\s*2\s*:?\s*Final\s+Adoption.*',
120+
]
121+
122+
section_text = ""
123+
for pattern in adoption_patterns:
124+
match = re.search(pattern, response, re.IGNORECASE | re.DOTALL)
125+
if match:
126+
section_text = response[match.start():]
127+
break
128+
129+
# Strategy 2: If no explicit section found, look for all ✅ Agree votes
130+
if not section_text:
131+
agree_pattern = r'\*\*Vote\*\*:\s*✅\s*Agree.*?(?:^|\n)#+\s*([APE]\d+)'
132+
agrees = re.findall(agree_pattern, response, re.IGNORECASE | re.DOTALL | re.MULTILINE)
133+
if agrees:
134+
adopted_ids = list(dict.fromkeys(agrees))
135+
136+
# Extract IDs from section text
137+
if section_text:
138+
ids = re.findall(r'\b([APE]\d+)\b', section_text)
139+
adopted_ids = list(dict.fromkeys(ids))
140+
141+
return adopted_ids
142+
143+
62144
class ExpertAgent(BaseAgent):
63145
"""
64146
Base class for Expert agents.

tools/ai-markmap-agent/src/agents/writer.py

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,6 @@ def __init__(self, config: dict[str, Any] | None = None):
4848

4949
# Load format guide
5050
self.format_guide = self._load_format_guide(model_config)
51-
52-
# URL templates
53-
urls_config = config.get("urls", {})
54-
self.github_template = urls_config.get("github", {}).get(
55-
"solution_template",
56-
"https://github.com/lufftw/neetcode/blob/main/{solution_file}"
57-
)
5851

5952
def _load_format_guide(self, model_config: dict) -> str:
6053
"""Load the Markmap format guide."""
@@ -91,24 +84,33 @@ def _format_problems_for_prompt(
9184
self,
9285
problems_lookup: dict[str, dict],
9386
) -> str:
94-
"""Format problems for the writer prompt."""
87+
"""Format problems for the writer prompt - compressed ID list only."""
9588
if not problems_lookup:
96-
return "No problem data available."
97-
98-
lines = ["| ID | Title | Slug | Has Solution |", "|---|---|---|---|"]
89+
return "None"
9990

91+
# Collect problem IDs that have solution files
92+
solution_ids = []
10093
seen = set()
101-
for pid, problem in list(problems_lookup.items())[:100]:
94+
95+
for pid, problem in problems_lookup.items():
10296
if pid in seen:
10397
continue
104-
seen.add(pid)
105-
106-
title = problem.get("title", "Unknown")[:50]
107-
slug = problem.get("slug", "")
108-
has_solution = "Yes" if problem.get("solution_file") else "No"
109-
lines.append(f"| {pid} | {title} | {slug} | {has_solution} |")
98+
try:
99+
pid_int = int(pid)
100+
seen.add(pid)
101+
# Check for solution file in files.solution
102+
files = problem.get("files", {})
103+
if files.get("solution"):
104+
solution_ids.append(pid_int)
105+
except (ValueError, TypeError):
106+
continue
110107

111-
return "\n".join(lines)
108+
if not solution_ids:
109+
return "None"
110+
111+
# Sort and format as compact list: "1,2,3,11,15,..."
112+
solution_ids.sort()
113+
return ",".join(str(pid) for pid in solution_ids)
112114

113115
def _format_ontology(self, ontology: dict[str, Any]) -> str:
114116
"""Format ontology for prompt."""
@@ -159,13 +161,12 @@ def process(self, state: dict[str, Any]) -> dict[str, Any]:
159161
# Get ontology
160162
ontology = state.get("ontology", {})
161163

162-
# Build the prompt
164+
# Build the prompt (no longer pass github_template or leetcode_template)
163165
prompt = self.behavior_prompt.format(
164166
baseline_markmap=baseline_markmap,
165167
adopted_improvements=brief_list,
166168
improvement_details=detailed_descriptions,
167169
problem_data=self._format_problems_for_prompt(problems_lookup),
168-
github_template=self.github_template,
169170
ontology_summary=self._format_ontology(ontology),
170171
)
171172

0 commit comments

Comments
 (0)