emdash-core 0.1.7__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.
- emdash_core/__init__.py +3 -0
- emdash_core/agent/__init__.py +37 -0
- emdash_core/agent/agents.py +225 -0
- emdash_core/agent/code_reviewer.py +476 -0
- emdash_core/agent/compaction.py +143 -0
- emdash_core/agent/context_manager.py +140 -0
- emdash_core/agent/events.py +338 -0
- emdash_core/agent/handlers.py +224 -0
- emdash_core/agent/inprocess_subagent.py +377 -0
- emdash_core/agent/mcp/__init__.py +50 -0
- emdash_core/agent/mcp/client.py +346 -0
- emdash_core/agent/mcp/config.py +302 -0
- emdash_core/agent/mcp/manager.py +496 -0
- emdash_core/agent/mcp/tool_factory.py +213 -0
- emdash_core/agent/prompts/__init__.py +38 -0
- emdash_core/agent/prompts/main_agent.py +104 -0
- emdash_core/agent/prompts/subagents.py +131 -0
- emdash_core/agent/prompts/workflow.py +136 -0
- emdash_core/agent/providers/__init__.py +34 -0
- emdash_core/agent/providers/base.py +143 -0
- emdash_core/agent/providers/factory.py +80 -0
- emdash_core/agent/providers/models.py +220 -0
- emdash_core/agent/providers/openai_provider.py +463 -0
- emdash_core/agent/providers/transformers_provider.py +217 -0
- emdash_core/agent/research/__init__.py +81 -0
- emdash_core/agent/research/agent.py +143 -0
- emdash_core/agent/research/controller.py +254 -0
- emdash_core/agent/research/critic.py +428 -0
- emdash_core/agent/research/macros.py +469 -0
- emdash_core/agent/research/planner.py +449 -0
- emdash_core/agent/research/researcher.py +436 -0
- emdash_core/agent/research/state.py +523 -0
- emdash_core/agent/research/synthesizer.py +594 -0
- emdash_core/agent/reviewer_profile.py +475 -0
- emdash_core/agent/rules.py +123 -0
- emdash_core/agent/runner.py +601 -0
- emdash_core/agent/session.py +262 -0
- emdash_core/agent/spec_schema.py +66 -0
- emdash_core/agent/specification.py +479 -0
- emdash_core/agent/subagent.py +397 -0
- emdash_core/agent/subagent_prompts.py +13 -0
- emdash_core/agent/toolkit.py +482 -0
- emdash_core/agent/toolkits/__init__.py +64 -0
- emdash_core/agent/toolkits/base.py +96 -0
- emdash_core/agent/toolkits/explore.py +47 -0
- emdash_core/agent/toolkits/plan.py +55 -0
- emdash_core/agent/tools/__init__.py +141 -0
- emdash_core/agent/tools/analytics.py +436 -0
- emdash_core/agent/tools/base.py +131 -0
- emdash_core/agent/tools/coding.py +484 -0
- emdash_core/agent/tools/github_mcp.py +592 -0
- emdash_core/agent/tools/history.py +13 -0
- emdash_core/agent/tools/modes.py +153 -0
- emdash_core/agent/tools/plan.py +206 -0
- emdash_core/agent/tools/plan_write.py +135 -0
- emdash_core/agent/tools/search.py +412 -0
- emdash_core/agent/tools/spec.py +341 -0
- emdash_core/agent/tools/task.py +262 -0
- emdash_core/agent/tools/task_output.py +204 -0
- emdash_core/agent/tools/tasks.py +454 -0
- emdash_core/agent/tools/traversal.py +588 -0
- emdash_core/agent/tools/web.py +179 -0
- emdash_core/analytics/__init__.py +5 -0
- emdash_core/analytics/engine.py +1286 -0
- emdash_core/api/__init__.py +5 -0
- emdash_core/api/agent.py +308 -0
- emdash_core/api/agents.py +154 -0
- emdash_core/api/analyze.py +264 -0
- emdash_core/api/auth.py +173 -0
- emdash_core/api/context.py +77 -0
- emdash_core/api/db.py +121 -0
- emdash_core/api/embed.py +131 -0
- emdash_core/api/feature.py +143 -0
- emdash_core/api/health.py +93 -0
- emdash_core/api/index.py +162 -0
- emdash_core/api/plan.py +110 -0
- emdash_core/api/projectmd.py +210 -0
- emdash_core/api/query.py +320 -0
- emdash_core/api/research.py +122 -0
- emdash_core/api/review.py +161 -0
- emdash_core/api/router.py +76 -0
- emdash_core/api/rules.py +116 -0
- emdash_core/api/search.py +119 -0
- emdash_core/api/spec.py +99 -0
- emdash_core/api/swarm.py +223 -0
- emdash_core/api/tasks.py +109 -0
- emdash_core/api/team.py +120 -0
- emdash_core/auth/__init__.py +17 -0
- emdash_core/auth/github.py +389 -0
- emdash_core/config.py +74 -0
- emdash_core/context/__init__.py +52 -0
- emdash_core/context/models.py +50 -0
- emdash_core/context/providers/__init__.py +11 -0
- emdash_core/context/providers/base.py +74 -0
- emdash_core/context/providers/explored_areas.py +183 -0
- emdash_core/context/providers/touched_areas.py +360 -0
- emdash_core/context/registry.py +73 -0
- emdash_core/context/reranker.py +199 -0
- emdash_core/context/service.py +260 -0
- emdash_core/context/session.py +352 -0
- emdash_core/core/__init__.py +104 -0
- emdash_core/core/config.py +454 -0
- emdash_core/core/exceptions.py +55 -0
- emdash_core/core/models.py +265 -0
- emdash_core/core/review_config.py +57 -0
- emdash_core/db/__init__.py +67 -0
- emdash_core/db/auth.py +134 -0
- emdash_core/db/models.py +91 -0
- emdash_core/db/provider.py +222 -0
- emdash_core/db/providers/__init__.py +5 -0
- emdash_core/db/providers/supabase.py +452 -0
- emdash_core/embeddings/__init__.py +24 -0
- emdash_core/embeddings/indexer.py +534 -0
- emdash_core/embeddings/models.py +192 -0
- emdash_core/embeddings/providers/__init__.py +7 -0
- emdash_core/embeddings/providers/base.py +112 -0
- emdash_core/embeddings/providers/fireworks.py +141 -0
- emdash_core/embeddings/providers/openai.py +104 -0
- emdash_core/embeddings/registry.py +146 -0
- emdash_core/embeddings/service.py +215 -0
- emdash_core/graph/__init__.py +26 -0
- emdash_core/graph/builder.py +134 -0
- emdash_core/graph/connection.py +692 -0
- emdash_core/graph/schema.py +416 -0
- emdash_core/graph/writer.py +667 -0
- emdash_core/ingestion/__init__.py +7 -0
- emdash_core/ingestion/change_detector.py +150 -0
- emdash_core/ingestion/git/__init__.py +5 -0
- emdash_core/ingestion/git/commit_analyzer.py +196 -0
- emdash_core/ingestion/github/__init__.py +6 -0
- emdash_core/ingestion/github/pr_fetcher.py +296 -0
- emdash_core/ingestion/github/task_extractor.py +100 -0
- emdash_core/ingestion/orchestrator.py +540 -0
- emdash_core/ingestion/parsers/__init__.py +10 -0
- emdash_core/ingestion/parsers/base_parser.py +66 -0
- emdash_core/ingestion/parsers/call_graph_builder.py +121 -0
- emdash_core/ingestion/parsers/class_extractor.py +154 -0
- emdash_core/ingestion/parsers/function_extractor.py +202 -0
- emdash_core/ingestion/parsers/import_analyzer.py +119 -0
- emdash_core/ingestion/parsers/python_parser.py +123 -0
- emdash_core/ingestion/parsers/registry.py +72 -0
- emdash_core/ingestion/parsers/ts_ast_parser.js +313 -0
- emdash_core/ingestion/parsers/typescript_parser.py +278 -0
- emdash_core/ingestion/repository.py +346 -0
- emdash_core/models/__init__.py +38 -0
- emdash_core/models/agent.py +68 -0
- emdash_core/models/index.py +77 -0
- emdash_core/models/query.py +113 -0
- emdash_core/planning/__init__.py +7 -0
- emdash_core/planning/agent_api.py +413 -0
- emdash_core/planning/context_builder.py +265 -0
- emdash_core/planning/feature_context.py +232 -0
- emdash_core/planning/feature_expander.py +646 -0
- emdash_core/planning/llm_explainer.py +198 -0
- emdash_core/planning/similarity.py +509 -0
- emdash_core/planning/team_focus.py +821 -0
- emdash_core/server.py +153 -0
- emdash_core/sse/__init__.py +5 -0
- emdash_core/sse/stream.py +196 -0
- emdash_core/swarm/__init__.py +17 -0
- emdash_core/swarm/merge_agent.py +383 -0
- emdash_core/swarm/session_manager.py +274 -0
- emdash_core/swarm/swarm_runner.py +226 -0
- emdash_core/swarm/task_definition.py +137 -0
- emdash_core/swarm/worker_spawner.py +319 -0
- emdash_core/swarm/worktree_manager.py +278 -0
- emdash_core/templates/__init__.py +10 -0
- emdash_core/templates/defaults/agent-builder.md.template +82 -0
- emdash_core/templates/defaults/focus.md.template +115 -0
- emdash_core/templates/defaults/pr-review-enhanced.md.template +309 -0
- emdash_core/templates/defaults/pr-review.md.template +80 -0
- emdash_core/templates/defaults/project.md.template +85 -0
- emdash_core/templates/defaults/research_critic.md.template +112 -0
- emdash_core/templates/defaults/research_planner.md.template +85 -0
- emdash_core/templates/defaults/research_synthesizer.md.template +128 -0
- emdash_core/templates/defaults/reviewer.md.template +81 -0
- emdash_core/templates/defaults/spec.md.template +41 -0
- emdash_core/templates/defaults/tasks.md.template +78 -0
- emdash_core/templates/loader.py +296 -0
- emdash_core/utils/__init__.py +45 -0
- emdash_core/utils/git.py +84 -0
- emdash_core/utils/image.py +502 -0
- emdash_core/utils/logger.py +51 -0
- emdash_core-0.1.7.dist-info/METADATA +35 -0
- emdash_core-0.1.7.dist-info/RECORD +187 -0
- emdash_core-0.1.7.dist-info/WHEEL +4 -0
- emdash_core-0.1.7.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Agent prompts module.
|
|
2
|
+
|
|
3
|
+
Centralized location for all agent system prompts and workflow patterns.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .workflow import (
|
|
7
|
+
WORKFLOW_PATTERNS,
|
|
8
|
+
EXPLORATION_STRATEGY,
|
|
9
|
+
OUTPUT_GUIDELINES,
|
|
10
|
+
EFFICIENCY_RULES,
|
|
11
|
+
EXPLORATION_OUTPUT_FORMAT,
|
|
12
|
+
PLAN_TEMPLATE,
|
|
13
|
+
SIZING_GUIDELINES,
|
|
14
|
+
)
|
|
15
|
+
from .main_agent import (
|
|
16
|
+
BASE_SYSTEM_PROMPT,
|
|
17
|
+
build_system_prompt,
|
|
18
|
+
build_tools_section,
|
|
19
|
+
)
|
|
20
|
+
from .subagents import SUBAGENT_PROMPTS, get_subagent_prompt
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
# Workflow patterns
|
|
24
|
+
"WORKFLOW_PATTERNS",
|
|
25
|
+
"EXPLORATION_STRATEGY",
|
|
26
|
+
"OUTPUT_GUIDELINES",
|
|
27
|
+
"EFFICIENCY_RULES",
|
|
28
|
+
"EXPLORATION_OUTPUT_FORMAT",
|
|
29
|
+
"PLAN_TEMPLATE",
|
|
30
|
+
"SIZING_GUIDELINES",
|
|
31
|
+
# Main agent
|
|
32
|
+
"BASE_SYSTEM_PROMPT",
|
|
33
|
+
"build_system_prompt",
|
|
34
|
+
"build_tools_section",
|
|
35
|
+
# Sub-agents
|
|
36
|
+
"SUBAGENT_PROMPTS",
|
|
37
|
+
"get_subagent_prompt",
|
|
38
|
+
]
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Main agent system prompt.
|
|
2
|
+
|
|
3
|
+
The primary prompt for the orchestrating agent that manages sub-agents
|
|
4
|
+
and handles complex multi-step tasks.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .workflow import (
|
|
8
|
+
WORKFLOW_PATTERNS,
|
|
9
|
+
EXPLORATION_STRATEGY,
|
|
10
|
+
OUTPUT_GUIDELINES,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
# Base system prompt template with placeholder for tools
|
|
14
|
+
BASE_SYSTEM_PROMPT = """You are a code exploration and implementation assistant. You orchestrate focused sub-agents for exploration while maintaining the high-level view.
|
|
15
|
+
|
|
16
|
+
{tools_section}
|
|
17
|
+
""" + WORKFLOW_PATTERNS + EXPLORATION_STRATEGY + OUTPUT_GUIDELINES
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def build_system_prompt(toolkit) -> str:
|
|
21
|
+
"""Build the complete system prompt with dynamic tool descriptions.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
toolkit: The agent toolkit with registered tools
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Complete system prompt string
|
|
28
|
+
"""
|
|
29
|
+
tools_section = build_tools_section(toolkit)
|
|
30
|
+
return BASE_SYSTEM_PROMPT.format(tools_section=tools_section)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def build_tools_section(toolkit) -> str:
|
|
34
|
+
"""Build the tools section of the system prompt from registered tools.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
toolkit: The agent toolkit with registered tools
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Formatted string with tool descriptions grouped by category
|
|
41
|
+
"""
|
|
42
|
+
from ..tools.base import ToolCategory
|
|
43
|
+
|
|
44
|
+
# Group tools by category
|
|
45
|
+
tools_by_category: dict[str, list[tuple[str, str]]] = {}
|
|
46
|
+
|
|
47
|
+
for tool in toolkit._tools.values():
|
|
48
|
+
# Get category name
|
|
49
|
+
if hasattr(tool, 'category'):
|
|
50
|
+
category = tool.category.value if isinstance(tool.category, ToolCategory) else str(tool.category)
|
|
51
|
+
else:
|
|
52
|
+
category = "other"
|
|
53
|
+
|
|
54
|
+
# Get tool name and description
|
|
55
|
+
name = tool.name
|
|
56
|
+
description = tool.description
|
|
57
|
+
|
|
58
|
+
# Clean up description - take first sentence or first 150 chars
|
|
59
|
+
if description:
|
|
60
|
+
# Remove [server_name] prefix if present (from MCP tools)
|
|
61
|
+
if description.startswith("["):
|
|
62
|
+
description = description.split("]", 1)[-1].strip()
|
|
63
|
+
# Take first sentence
|
|
64
|
+
first_sentence = description.split(".")[0] + "."
|
|
65
|
+
if len(first_sentence) > 150:
|
|
66
|
+
first_sentence = description[:147] + "..."
|
|
67
|
+
description = first_sentence
|
|
68
|
+
else:
|
|
69
|
+
description = "No description available."
|
|
70
|
+
|
|
71
|
+
if category not in tools_by_category:
|
|
72
|
+
tools_by_category[category] = []
|
|
73
|
+
tools_by_category[category].append((name, description))
|
|
74
|
+
|
|
75
|
+
# Build formatted section
|
|
76
|
+
lines = ["## Available Tools\n"]
|
|
77
|
+
|
|
78
|
+
# Define category display order and titles
|
|
79
|
+
category_titles = {
|
|
80
|
+
"search": "Search & Discovery",
|
|
81
|
+
"traversal": "Graph Traversal",
|
|
82
|
+
"analytics": "Analytics",
|
|
83
|
+
"planning": "Planning",
|
|
84
|
+
"history": "History",
|
|
85
|
+
"other": "Other Tools",
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# Sort categories by predefined order
|
|
89
|
+
category_order = ["search", "traversal", "analytics", "planning", "history", "other"]
|
|
90
|
+
sorted_categories = sorted(
|
|
91
|
+
tools_by_category.keys(),
|
|
92
|
+
key=lambda c: category_order.index(c) if c in category_order else 999
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
for category in sorted_categories:
|
|
96
|
+
tools = tools_by_category[category]
|
|
97
|
+
title = category_titles.get(category, category.title())
|
|
98
|
+
|
|
99
|
+
lines.append(f"### {title}")
|
|
100
|
+
for name, desc in sorted(tools):
|
|
101
|
+
lines.append(f"- **{name}**: {desc}")
|
|
102
|
+
lines.append("")
|
|
103
|
+
|
|
104
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""Sub-agent system prompts.
|
|
2
|
+
|
|
3
|
+
Prompts for specialized sub-agents that handle focused tasks like
|
|
4
|
+
exploration, planning, command execution, and research.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .workflow import (
|
|
8
|
+
EFFICIENCY_RULES,
|
|
9
|
+
EXPLORATION_OUTPUT_FORMAT,
|
|
10
|
+
PLAN_TEMPLATE,
|
|
11
|
+
SIZING_GUIDELINES,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
# Explore agent prompt
|
|
15
|
+
EXPLORE_PROMPT = f"""You are a fast, focused codebase explorer. Your job is to find specific information and return structured results.
|
|
16
|
+
|
|
17
|
+
## Your Mission
|
|
18
|
+
Find and report: files, functions, classes, patterns, or code snippets relevant to the task. You have limited turns, so be efficient.
|
|
19
|
+
|
|
20
|
+
## Strategy
|
|
21
|
+
|
|
22
|
+
### Breadth-First for Discovery
|
|
23
|
+
When looking for something you're not sure exists:
|
|
24
|
+
1. glob to find candidate files by name/extension
|
|
25
|
+
2. grep with multiple keywords to find occurrences
|
|
26
|
+
3. Read the most promising files
|
|
27
|
+
|
|
28
|
+
### Depth-First for Understanding
|
|
29
|
+
When you have a specific target:
|
|
30
|
+
1. Go directly to the file
|
|
31
|
+
2. Read the relevant sections
|
|
32
|
+
3. Follow imports/dependencies as needed
|
|
33
|
+
|
|
34
|
+
{EFFICIENCY_RULES}
|
|
35
|
+
|
|
36
|
+
{EXPLORATION_OUTPUT_FORMAT}
|
|
37
|
+
|
|
38
|
+
## Constraints
|
|
39
|
+
- You are read-only - cannot modify files
|
|
40
|
+
- Focus on the specific task, don't go on tangents
|
|
41
|
+
- Be concise - the main agent needs your results, not your process"""
|
|
42
|
+
|
|
43
|
+
# Plan agent prompt
|
|
44
|
+
PLAN_PROMPT = f"""You are a software architect. Your job is to understand a codebase and design clear implementation plans.
|
|
45
|
+
|
|
46
|
+
## Your Mission
|
|
47
|
+
Explore the codebase, understand patterns and conventions, then write a concrete implementation plan.
|
|
48
|
+
|
|
49
|
+
## Approach
|
|
50
|
+
|
|
51
|
+
### 1. Understand Context (use 30-40% of your turns)
|
|
52
|
+
- Find similar features/patterns in the codebase
|
|
53
|
+
- Understand the architecture and conventions
|
|
54
|
+
- Identify files that will need changes
|
|
55
|
+
- Note any constraints or dependencies
|
|
56
|
+
|
|
57
|
+
### 2. Design the Solution
|
|
58
|
+
- Follow existing patterns when possible
|
|
59
|
+
- Break into clear, ordered steps
|
|
60
|
+
- Identify risks and edge cases
|
|
61
|
+
- Consider error handling
|
|
62
|
+
|
|
63
|
+
### 3. Write the Plan
|
|
64
|
+
{PLAN_TEMPLATE}
|
|
65
|
+
|
|
66
|
+
## Constraints
|
|
67
|
+
- You can only write to .emdash/plans/*.md
|
|
68
|
+
- Focus on actionable steps, not theory
|
|
69
|
+
- Reference specific files and line numbers
|
|
70
|
+
- Keep plans focused and concrete
|
|
71
|
+
{SIZING_GUIDELINES}"""
|
|
72
|
+
|
|
73
|
+
# Bash agent prompt
|
|
74
|
+
BASH_PROMPT = """You are a command executor. Run shell commands and report results clearly.
|
|
75
|
+
|
|
76
|
+
## Guidelines
|
|
77
|
+
- Show the command you're running
|
|
78
|
+
- Report full output (or summarize if very long)
|
|
79
|
+
- Explain what happened
|
|
80
|
+
- Warn about destructive operations before running
|
|
81
|
+
|
|
82
|
+
## Safety
|
|
83
|
+
- Never run commands that could cause data loss without warning
|
|
84
|
+
- Be cautious with sudo, rm -rf, force pushes
|
|
85
|
+
- Prefer dry-run flags when available for destructive operations
|
|
86
|
+
|
|
87
|
+
## Output
|
|
88
|
+
Report: command run, output received, what it means."""
|
|
89
|
+
|
|
90
|
+
# Research agent prompt
|
|
91
|
+
RESEARCH_PROMPT = """You are a documentation researcher. Find authoritative information from the web and official docs.
|
|
92
|
+
|
|
93
|
+
## Guidelines
|
|
94
|
+
- Prefer official documentation over blog posts
|
|
95
|
+
- Cite sources with URLs
|
|
96
|
+
- Include relevant code examples
|
|
97
|
+
- Note version-specific information
|
|
98
|
+
- Cross-reference multiple sources for accuracy
|
|
99
|
+
|
|
100
|
+
## Output
|
|
101
|
+
- Answer the specific question asked
|
|
102
|
+
- Provide context for when/why to use the information
|
|
103
|
+
- Include links for further reading"""
|
|
104
|
+
|
|
105
|
+
# Registry of all sub-agent prompts
|
|
106
|
+
SUBAGENT_PROMPTS = {
|
|
107
|
+
"Explore": EXPLORE_PROMPT,
|
|
108
|
+
"Plan": PLAN_PROMPT,
|
|
109
|
+
"Bash": BASH_PROMPT,
|
|
110
|
+
"Research": RESEARCH_PROMPT,
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def get_subagent_prompt(subagent_type: str) -> str:
|
|
115
|
+
"""Get the system prompt for a sub-agent type.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
subagent_type: Type of agent (e.g., "Explore", "Plan")
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
System prompt string
|
|
122
|
+
|
|
123
|
+
Raises:
|
|
124
|
+
ValueError: If agent type is not known
|
|
125
|
+
"""
|
|
126
|
+
if subagent_type not in SUBAGENT_PROMPTS:
|
|
127
|
+
available = list(SUBAGENT_PROMPTS.keys())
|
|
128
|
+
raise ValueError(
|
|
129
|
+
f"Unknown agent type: {subagent_type}. Available: {available}"
|
|
130
|
+
)
|
|
131
|
+
return SUBAGENT_PROMPTS[subagent_type]
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""Shared workflow patterns for agents.
|
|
2
|
+
|
|
3
|
+
These patterns can be embedded in different agent prompts to ensure
|
|
4
|
+
consistent behavior across agent types.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# Core workflow for tackling complex tasks
|
|
8
|
+
WORKFLOW_PATTERNS = """
|
|
9
|
+
## Workflow for Complex Tasks
|
|
10
|
+
|
|
11
|
+
### 1. Understand Before Acting
|
|
12
|
+
- Read code before modifying it
|
|
13
|
+
- Ask clarifying questions when requirements are ambiguous
|
|
14
|
+
- Search for similar patterns already in the codebase
|
|
15
|
+
|
|
16
|
+
### 2. Break Down Hard Problems
|
|
17
|
+
When facing a task you don't immediately know how to solve:
|
|
18
|
+
|
|
19
|
+
a) **Decompose**: Split into smaller, concrete sub-tasks
|
|
20
|
+
b) **Explore**: Use sub-agents to gather context (can run in parallel)
|
|
21
|
+
c) **Plan**: Write out your approach before implementing
|
|
22
|
+
d) **Execute**: Work through tasks one at a time
|
|
23
|
+
e) **Validate**: Check your work against requirements
|
|
24
|
+
|
|
25
|
+
### 3. Use Sub-Agents Strategically
|
|
26
|
+
Spawn sub-agents via the `task` tool when you need:
|
|
27
|
+
- **Explore**: Find files, patterns, or understand code structure
|
|
28
|
+
- **Plan**: Design implementation approach for complex features
|
|
29
|
+
|
|
30
|
+
Guidelines:
|
|
31
|
+
- Launch multiple Explore agents in parallel for independent searches
|
|
32
|
+
- Use sub-agents for focused work that would clutter your context
|
|
33
|
+
- Prefer sub-agents over doing 5+ search operations yourself
|
|
34
|
+
|
|
35
|
+
### 4. Track Progress
|
|
36
|
+
For multi-step tasks, mentally track what's done and what's next.
|
|
37
|
+
Update the user on progress for long-running work.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
# Exploration strategy for code navigation
|
|
41
|
+
EXPLORATION_STRATEGY = """
|
|
42
|
+
## Exploration Strategy
|
|
43
|
+
|
|
44
|
+
### Start Broad, Then Focus
|
|
45
|
+
1. Understand project structure (list_files on key directories)
|
|
46
|
+
2. Find relevant files (glob for patterns, grep for keywords)
|
|
47
|
+
3. Read key files to understand patterns
|
|
48
|
+
4. Deep dive into specific areas
|
|
49
|
+
|
|
50
|
+
### Tool Selection
|
|
51
|
+
- **glob** searches file NAMES/PATHS → glob("**/*.py")
|
|
52
|
+
- **grep** searches file CONTENTS → grep("authenticate")
|
|
53
|
+
- **semantic_search** finds conceptually related code
|
|
54
|
+
- Use local tools for the LOCAL codebase
|
|
55
|
+
- Use GitHub/MCP tools for REMOTE repositories, PRs, issues
|
|
56
|
+
|
|
57
|
+
### When Stuck
|
|
58
|
+
1. Step back - what are you actually trying to find?
|
|
59
|
+
2. Try alternative search terms
|
|
60
|
+
3. Look at imports/dependencies for clues
|
|
61
|
+
4. Ask the user for clarification
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
# Output formatting guidelines
|
|
65
|
+
OUTPUT_GUIDELINES = """
|
|
66
|
+
## Output Guidelines
|
|
67
|
+
- Cite specific files and line numbers
|
|
68
|
+
- Show relevant code snippets
|
|
69
|
+
- Be concise but thorough
|
|
70
|
+
- Explain your reasoning for complex decisions
|
|
71
|
+
- NEVER provide time estimates (hours, days, weeks). Use complexity sizing: S/M/L/XL
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
# Efficiency rules for sub-agents with limited turns
|
|
75
|
+
EFFICIENCY_RULES = """
|
|
76
|
+
## Efficiency Rules
|
|
77
|
+
- If you find what you need, STOP - don't keep searching
|
|
78
|
+
- If 3 searches return nothing, try different terms or report "not found"
|
|
79
|
+
- Read only the parts of files you need (use offset/limit for large files)
|
|
80
|
+
- Don't read entire files when you only need a specific function
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
# Structured output format for exploration results
|
|
84
|
+
EXPLORATION_OUTPUT_FORMAT = """
|
|
85
|
+
## Output Format
|
|
86
|
+
Structure your final response as:
|
|
87
|
+
|
|
88
|
+
**Summary**: 1-2 sentence answer to the task
|
|
89
|
+
|
|
90
|
+
**Key Findings**:
|
|
91
|
+
- `file:line` - Description of what you found
|
|
92
|
+
- `file:line` - Another finding
|
|
93
|
+
|
|
94
|
+
**Files Explored**: [list of files you read]
|
|
95
|
+
|
|
96
|
+
**Confidence**: high/medium/low
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
# Plan template for Plan agents
|
|
100
|
+
PLAN_TEMPLATE = """
|
|
101
|
+
## Plan Template
|
|
102
|
+
Use `write_plan` to save your plan. Structure it as:
|
|
103
|
+
|
|
104
|
+
```markdown
|
|
105
|
+
# [Feature Name] Implementation Plan
|
|
106
|
+
|
|
107
|
+
## Summary
|
|
108
|
+
Brief description of what this plan accomplishes.
|
|
109
|
+
|
|
110
|
+
## Files to Modify
|
|
111
|
+
- `path/to/file.py` - What changes
|
|
112
|
+
|
|
113
|
+
## Files to Create (if any)
|
|
114
|
+
- `path/to/new.py` - Purpose
|
|
115
|
+
|
|
116
|
+
## Implementation Steps
|
|
117
|
+
1. **Step name**: Detailed description
|
|
118
|
+
- Specific changes to make
|
|
119
|
+
- Code patterns to follow (reference existing code)
|
|
120
|
+
|
|
121
|
+
2. **Step name**: ...
|
|
122
|
+
|
|
123
|
+
## Edge Cases & Error Handling
|
|
124
|
+
- Case: How to handle
|
|
125
|
+
|
|
126
|
+
## Open Questions
|
|
127
|
+
Things that need clarification.
|
|
128
|
+
```
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
# Sizing guidelines (no time estimates)
|
|
132
|
+
SIZING_GUIDELINES = """
|
|
133
|
+
## Sizing (No Time Estimates)
|
|
134
|
+
- NEVER include time estimates (no hours, days, weeks, sprints, timelines)
|
|
135
|
+
- Use complexity sizing instead: S (small), M (medium), L (large), XL (extra large)
|
|
136
|
+
"""
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""LLM provider abstraction layer using OpenAI SDK."""
|
|
2
|
+
|
|
3
|
+
from .base import LLMProvider, LLMResponse, ToolCall
|
|
4
|
+
from .models import ChatModel, ChatModelSpec
|
|
5
|
+
from .openai_provider import OpenAIProvider
|
|
6
|
+
from .transformers_provider import TransformersProvider
|
|
7
|
+
from .factory import (
|
|
8
|
+
get_provider,
|
|
9
|
+
get_default_model,
|
|
10
|
+
get_default_api_key,
|
|
11
|
+
list_available_models,
|
|
12
|
+
DEFAULT_MODEL,
|
|
13
|
+
DEFAULT_API_KEY_ENV,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
# Base
|
|
18
|
+
"LLMProvider",
|
|
19
|
+
"LLMResponse",
|
|
20
|
+
"ToolCall",
|
|
21
|
+
# Models
|
|
22
|
+
"ChatModel",
|
|
23
|
+
"ChatModelSpec",
|
|
24
|
+
# Providers
|
|
25
|
+
"OpenAIProvider",
|
|
26
|
+
"TransformersProvider",
|
|
27
|
+
# Factory
|
|
28
|
+
"get_provider",
|
|
29
|
+
"get_default_model",
|
|
30
|
+
"get_default_api_key",
|
|
31
|
+
"list_available_models",
|
|
32
|
+
"DEFAULT_MODEL",
|
|
33
|
+
"DEFAULT_API_KEY_ENV",
|
|
34
|
+
]
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""Base classes for LLM providers."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import Any, Optional, Union
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class ToolCall:
|
|
10
|
+
"""Represents a tool call from the LLM."""
|
|
11
|
+
|
|
12
|
+
id: str
|
|
13
|
+
name: str
|
|
14
|
+
arguments: str # JSON string of arguments
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class ImageContent:
|
|
19
|
+
"""Represents an image for vision-capable models."""
|
|
20
|
+
|
|
21
|
+
image_data: bytes # Raw image bytes
|
|
22
|
+
format: str = "png" # Image format (png, jpeg, gif)
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def base64_url(self) -> str:
|
|
26
|
+
"""Get base64 data URL for the image."""
|
|
27
|
+
import base64
|
|
28
|
+
encoded = base64.b64encode(self.image_data).decode("utf-8")
|
|
29
|
+
return f"data:image/{self.format};base64,{encoded}"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class LLMResponse:
|
|
34
|
+
"""Unified response from any LLM provider."""
|
|
35
|
+
|
|
36
|
+
content: Optional[str] = None
|
|
37
|
+
tool_calls: list[ToolCall] = field(default_factory=list)
|
|
38
|
+
raw: Any = None # Original provider response
|
|
39
|
+
stop_reason: Optional[str] = None
|
|
40
|
+
input_tokens: int = 0 # Tokens in the request
|
|
41
|
+
output_tokens: int = 0 # Tokens in the response
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class LLMProvider(ABC):
|
|
45
|
+
"""Abstract base class for LLM providers."""
|
|
46
|
+
|
|
47
|
+
def __init__(self, model: str):
|
|
48
|
+
self.model = model
|
|
49
|
+
|
|
50
|
+
@abstractmethod
|
|
51
|
+
def chat(
|
|
52
|
+
self,
|
|
53
|
+
messages: list[dict],
|
|
54
|
+
tools: Optional[list[dict]] = None,
|
|
55
|
+
system: Optional[str] = None,
|
|
56
|
+
reasoning: bool = False,
|
|
57
|
+
images: Optional[list[ImageContent]] = None,
|
|
58
|
+
) -> LLMResponse:
|
|
59
|
+
"""Send a chat completion request.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
messages: List of message dicts with 'role' and 'content'
|
|
63
|
+
tools: Optional list of tool schemas
|
|
64
|
+
system: Optional system prompt (will be prepended or handled per provider)
|
|
65
|
+
reasoning: Enable reasoning mode (for models that support it)
|
|
66
|
+
images: Optional list of images for vision-capable models
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
LLMResponse with content and/or tool calls
|
|
70
|
+
"""
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
@abstractmethod
|
|
74
|
+
def get_context_limit(self) -> int:
|
|
75
|
+
"""Get the context window size for this model."""
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
@abstractmethod
|
|
79
|
+
def get_max_image_size(self) -> int:
|
|
80
|
+
"""Get maximum image size in bytes for this model."""
|
|
81
|
+
return 5 * 1024 * 1024 # Default 5MB
|
|
82
|
+
|
|
83
|
+
@abstractmethod
|
|
84
|
+
def supports_vision(self) -> bool:
|
|
85
|
+
"""Check if this model supports image input."""
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
@abstractmethod
|
|
89
|
+
def format_tool_result(self, tool_call_id: str, result: str) -> dict:
|
|
90
|
+
"""Format a tool result message for this provider.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
tool_call_id: ID of the tool call being responded to
|
|
94
|
+
result: JSON string result from the tool
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Message dict in provider's expected format
|
|
98
|
+
"""
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
@abstractmethod
|
|
102
|
+
def format_assistant_message(self, response: LLMResponse) -> dict:
|
|
103
|
+
"""Format an assistant response to add back to messages.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
response: The LLMResponse from a chat call
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
Message dict in provider's expected format
|
|
110
|
+
"""
|
|
111
|
+
pass
|
|
112
|
+
|
|
113
|
+
def format_content_with_images(
|
|
114
|
+
self,
|
|
115
|
+
text: str,
|
|
116
|
+
images: Optional[list[ImageContent]] = None
|
|
117
|
+
) -> Union[str, list[dict]]:
|
|
118
|
+
"""Format message content with optional images.
|
|
119
|
+
|
|
120
|
+
For vision models, returns a list of content blocks.
|
|
121
|
+
For non-vision models, returns text only (images stripped).
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
text: Text content
|
|
125
|
+
images: Optional list of images
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Content formatted for this provider
|
|
129
|
+
"""
|
|
130
|
+
if not images:
|
|
131
|
+
return text
|
|
132
|
+
|
|
133
|
+
if self.supports_vision():
|
|
134
|
+
content = [{"type": "text", "text": text}]
|
|
135
|
+
for img in images:
|
|
136
|
+
content.append({
|
|
137
|
+
"type": "image_url",
|
|
138
|
+
"image_url": {"url": img.base64_url}
|
|
139
|
+
})
|
|
140
|
+
return content
|
|
141
|
+
|
|
142
|
+
# Non-vision model: strip images, warn
|
|
143
|
+
return text
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Factory for creating LLM providers."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import Union
|
|
5
|
+
|
|
6
|
+
from .base import LLMProvider
|
|
7
|
+
from .models import ChatModel
|
|
8
|
+
from .openai_provider import OpenAIProvider
|
|
9
|
+
from .transformers_provider import TransformersProvider
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
13
|
+
# Configuration - Single source of truth
|
|
14
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
15
|
+
|
|
16
|
+
# Default model alias
|
|
17
|
+
DEFAULT_MODEL = "fireworks:accounts/fireworks/models/minimax-m2p1"
|
|
18
|
+
|
|
19
|
+
# Default API key environment variable (used by default model)
|
|
20
|
+
DEFAULT_API_KEY_ENV = "FIREWORKS_API_KEY"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
24
|
+
# Factory functions
|
|
25
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_provider(model: Union[str, ChatModel] = DEFAULT_MODEL) -> LLMProvider:
|
|
29
|
+
"""
|
|
30
|
+
Get an LLM provider for the specified model.
|
|
31
|
+
|
|
32
|
+
Uses OpenAI SDK with provider-specific base URLs for OpenAI, Anthropic, and Fireworks.
|
|
33
|
+
For local models, uses HuggingFace Transformers.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
model: Model specification - ChatModel enum or alias
|
|
37
|
+
Examples:
|
|
38
|
+
- ChatModel.ANTHROPIC_CLAUDE_HAIKU_4
|
|
39
|
+
- "haiku", "sonnet", "opus"
|
|
40
|
+
- "gpt-4o-mini"
|
|
41
|
+
- "minimax"
|
|
42
|
+
- "local:Qwen/Qwen2.5-1.5B-Instruct"
|
|
43
|
+
- "transformers:microsoft/Phi-3-mini-4k-instruct"
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
LLMProvider instance
|
|
47
|
+
|
|
48
|
+
Raises:
|
|
49
|
+
ValueError: If model string not recognized
|
|
50
|
+
"""
|
|
51
|
+
# Handle local/transformers prefix for local models
|
|
52
|
+
if isinstance(model, str) and (model.startswith("local:") or model.startswith("transformers:")):
|
|
53
|
+
return TransformersProvider(model)
|
|
54
|
+
|
|
55
|
+
# Handle ChatModel enum directly
|
|
56
|
+
if isinstance(model, ChatModel):
|
|
57
|
+
return OpenAIProvider(model)
|
|
58
|
+
|
|
59
|
+
# Try to parse as ChatModel
|
|
60
|
+
parsed = ChatModel.from_string(model)
|
|
61
|
+
if parsed:
|
|
62
|
+
return OpenAIProvider(parsed)
|
|
63
|
+
|
|
64
|
+
# Assume it's a raw model string
|
|
65
|
+
return OpenAIProvider(model)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def get_default_model() -> ChatModel:
|
|
69
|
+
"""Get the default model."""
|
|
70
|
+
return ChatModel.from_string(DEFAULT_MODEL) or ChatModel.get_default()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get_default_api_key() -> str | None:
|
|
74
|
+
"""Get the default API key from environment."""
|
|
75
|
+
return os.environ.get(DEFAULT_API_KEY_ENV)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def list_available_models() -> list[dict]:
|
|
79
|
+
"""List all available models."""
|
|
80
|
+
return ChatModel.list_all()
|