codeoptix 0.1.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.
- codeoptix/__init__.py +8 -0
- codeoptix/acp/__init__.py +33 -0
- codeoptix/acp/agent.py +209 -0
- codeoptix/acp/bridge.py +402 -0
- codeoptix/acp/client_adapter.py +312 -0
- codeoptix/acp/code_extractor.py +125 -0
- codeoptix/acp/orchestrator.py +349 -0
- codeoptix/acp/registry.py +294 -0
- codeoptix/adapters/__init__.py +18 -0
- codeoptix/adapters/base.py +50 -0
- codeoptix/adapters/basic.py +195 -0
- codeoptix/adapters/claude_code.py +218 -0
- codeoptix/adapters/codex.py +327 -0
- codeoptix/adapters/factory.py +56 -0
- codeoptix/adapters/gemini_cli.py +370 -0
- codeoptix/artifacts/__init__.py +5 -0
- codeoptix/artifacts/manager.py +193 -0
- codeoptix/behaviors/__init__.py +45 -0
- codeoptix/behaviors/base.py +81 -0
- codeoptix/behaviors/insecure_code.py +129 -0
- codeoptix/behaviors/plan_drift.py +192 -0
- codeoptix/behaviors/vacuous_tests.py +198 -0
- codeoptix/cli.py +1472 -0
- codeoptix/evaluation/__init__.py +23 -0
- codeoptix/evaluation/bloom_integration.py +271 -0
- codeoptix/evaluation/engine.py +274 -0
- codeoptix/evaluation/evaluators.py +308 -0
- codeoptix/evaluation/scenario_generator.py +222 -0
- codeoptix/evolution/__init__.py +7 -0
- codeoptix/evolution/engine.py +206 -0
- codeoptix/evolution/gepa_integration.py +149 -0
- codeoptix/evolution/proposer.py +185 -0
- codeoptix/linters/__init__.py +13 -0
- codeoptix/linters/bandit_linter.py +172 -0
- codeoptix/linters/base.py +105 -0
- codeoptix/linters/coverage_linter.py +156 -0
- codeoptix/linters/flake8_linter.py +156 -0
- codeoptix/linters/html_accessibility_linter.py +374 -0
- codeoptix/linters/language_detector.py +150 -0
- codeoptix/linters/mypy_linter.py +184 -0
- codeoptix/linters/pip_audit_linter.py +152 -0
- codeoptix/linters/pylint_linter.py +198 -0
- codeoptix/linters/ruff_linter.py +206 -0
- codeoptix/linters/runner.py +186 -0
- codeoptix/linters/safety_linter.py +184 -0
- codeoptix/reflection/__init__.py +6 -0
- codeoptix/reflection/engine.py +70 -0
- codeoptix/reflection/generator.py +209 -0
- codeoptix/utils/__init__.py +1 -0
- codeoptix/utils/config.py +91 -0
- codeoptix/utils/llm.py +332 -0
- codeoptix/utils/retry.py +133 -0
- codeoptix/vendor/__init__.py +2 -0
- codeoptix/vendor/bloom/README.md +26 -0
- codeoptix/vendor/bloom/__init__.py +11 -0
- codeoptix/vendor/bloom/globals.py +39 -0
- codeoptix/vendor/bloom/orchestrators/ConversationOrchestrator.py +450 -0
- codeoptix/vendor/bloom/orchestrators/SimEnvOrchestrator.py +839 -0
- codeoptix/vendor/bloom/prompts/configurable_prompts/README.md +85 -0
- codeoptix/vendor/bloom/prompts/configurable_prompts/default.json +18 -0
- codeoptix/vendor/bloom/prompts/configurable_prompts/ideation-default.json +18 -0
- codeoptix/vendor/bloom/prompts/configurable_prompts/mo_animal-welfare.json +18 -0
- codeoptix/vendor/bloom/prompts/configurable_prompts/mo_contextual-optimism.json +18 -0
- codeoptix/vendor/bloom/prompts/configurable_prompts/mo_defend-objects.json +18 -0
- codeoptix/vendor/bloom/prompts/configurable_prompts/mo_defer-to-users.json +18 -0
- codeoptix/vendor/bloom/prompts/configurable_prompts/mo_emotional-bond.json +18 -0
- codeoptix/vendor/bloom/prompts/configurable_prompts/mo_flattery.json +18 -0
- codeoptix/vendor/bloom/prompts/configurable_prompts/mo_hardcode-test-cases.json +18 -0
- codeoptix/vendor/bloom/prompts/configurable_prompts/mo_increasing-pep.json +18 -0
- codeoptix/vendor/bloom/prompts/configurable_prompts/mo_research-sandbagging.json +18 -0
- codeoptix/vendor/bloom/prompts/configurable_prompts/mo_self-promotion.json +18 -0
- codeoptix/vendor/bloom/prompts/configurable_prompts/sandbag.json +18 -0
- codeoptix/vendor/bloom/prompts/configurable_prompts/self-preferential-bias.json +18 -0
- codeoptix/vendor/bloom/prompts/configurable_prompts/static-prompts.yaml +72 -0
- codeoptix/vendor/bloom/prompts/configurable_prompts/web-search.json +18 -0
- codeoptix/vendor/bloom/prompts/step1_understanding.py +63 -0
- codeoptix/vendor/bloom/prompts/step2_ideation.py +254 -0
- codeoptix/vendor/bloom/prompts/step3_rollout.py +120 -0
- codeoptix/vendor/bloom/prompts/step4_judgment.py +183 -0
- codeoptix/vendor/bloom/schemas/behavior.schema.json +160 -0
- codeoptix/vendor/bloom/schemas/conversation.schema.json +51 -0
- codeoptix/vendor/bloom/schemas/transcript_schema.json +2225 -0
- codeoptix/vendor/bloom/scripts/step2_ideation.py +667 -0
- codeoptix/vendor/bloom/scripts/step4_judgment.py +811 -0
- codeoptix/vendor/bloom/transcript_utils.py +440 -0
- codeoptix/vendor/bloom/utils.py +700 -0
- codeoptix-0.1.0.dist-info/METADATA +304 -0
- codeoptix-0.1.0.dist-info/RECORD +91 -0
- codeoptix-0.1.0.dist-info/WHEEL +4 -0
- codeoptix-0.1.0.dist-info/entry_points.txt +2 -0
- codeoptix-0.1.0.dist-info/licenses/LICENSE +203 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""Evolution engine for CodeOptix using GEPA-style patterns."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from codeoptix.adapters.base import AgentAdapter
|
|
6
|
+
from codeoptix.artifacts.manager import ArtifactManager
|
|
7
|
+
from codeoptix.evaluation import EvaluationEngine
|
|
8
|
+
from codeoptix.evolution.proposer import PromptProposer
|
|
9
|
+
from codeoptix.utils.llm import LLMClient
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class EvolutionEngine:
|
|
13
|
+
"""
|
|
14
|
+
Evolution engine that optimizes agent prompts using GEPA-style reflective mutation.
|
|
15
|
+
|
|
16
|
+
Uses evaluation results and reflection to evolve prompts iteratively.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
adapter: AgentAdapter,
|
|
22
|
+
evaluation_engine: EvaluationEngine,
|
|
23
|
+
llm_client: LLMClient,
|
|
24
|
+
artifact_manager: ArtifactManager | None = None,
|
|
25
|
+
config: dict[str, Any] | None = None,
|
|
26
|
+
):
|
|
27
|
+
"""
|
|
28
|
+
Initialize evolution engine.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
adapter: Agent adapter to evolve
|
|
32
|
+
evaluation_engine: Evaluation engine for testing candidates
|
|
33
|
+
llm_client: LLM client for prompt proposal
|
|
34
|
+
artifact_manager: Artifact manager for saving evolved prompts
|
|
35
|
+
config: Configuration dictionary
|
|
36
|
+
"""
|
|
37
|
+
self.adapter = adapter
|
|
38
|
+
self.evaluation_engine = evaluation_engine
|
|
39
|
+
self.llm_client = llm_client
|
|
40
|
+
self.artifact_manager = artifact_manager
|
|
41
|
+
self.config = config or {}
|
|
42
|
+
|
|
43
|
+
# Evolution parameters
|
|
44
|
+
self.max_iterations = self.config.get("max_iterations", 3)
|
|
45
|
+
self.population_size = self.config.get("population_size", 3)
|
|
46
|
+
self.minibatch_size = self.config.get("minibatch_size", 2)
|
|
47
|
+
self.improvement_threshold = self.config.get("improvement_threshold", 0.05)
|
|
48
|
+
|
|
49
|
+
# Initialize proposer
|
|
50
|
+
proposer_config = self.config.get("proposer", {})
|
|
51
|
+
self.proposer = PromptProposer(llm_client, proposer_config)
|
|
52
|
+
|
|
53
|
+
def evolve(
|
|
54
|
+
self,
|
|
55
|
+
evaluation_results: dict[str, Any],
|
|
56
|
+
reflection: str,
|
|
57
|
+
behavior_names: list[str] | None = None,
|
|
58
|
+
context: dict[str, Any] | None = None,
|
|
59
|
+
) -> dict[str, Any]:
|
|
60
|
+
"""
|
|
61
|
+
Evolve agent prompts based on evaluation results.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
evaluation_results: Previous evaluation results
|
|
65
|
+
reflection: Reflection markdown content
|
|
66
|
+
behavior_names: Behaviors to focus on (default: all)
|
|
67
|
+
context: Optional context for evaluation
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
Dictionary with evolved prompts and evolution metadata
|
|
71
|
+
"""
|
|
72
|
+
context = context or {}
|
|
73
|
+
behavior_names = behavior_names or list(evaluation_results.get("behaviors", {}).keys())
|
|
74
|
+
|
|
75
|
+
# Get current prompt
|
|
76
|
+
current_prompt = self.adapter.get_prompt()
|
|
77
|
+
current_candidate = {"system_prompt": current_prompt}
|
|
78
|
+
|
|
79
|
+
# Evaluate current candidate on minibatch
|
|
80
|
+
current_score = evaluation_results.get("overall_score", 0.0)
|
|
81
|
+
|
|
82
|
+
evolution_history = []
|
|
83
|
+
best_candidate = current_candidate.copy()
|
|
84
|
+
best_score = current_score
|
|
85
|
+
|
|
86
|
+
for iteration in range(self.max_iterations):
|
|
87
|
+
# Generate population of candidates
|
|
88
|
+
candidates = self._generate_candidates(
|
|
89
|
+
current_candidate=current_candidate,
|
|
90
|
+
evaluation_results=evaluation_results,
|
|
91
|
+
reflection=reflection,
|
|
92
|
+
population_size=self.population_size,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Evaluate candidates on minibatch
|
|
96
|
+
candidate_scores = []
|
|
97
|
+
for candidate in candidates:
|
|
98
|
+
# Update adapter with candidate prompt
|
|
99
|
+
self.adapter.update_prompt(candidate["system_prompt"])
|
|
100
|
+
|
|
101
|
+
# Quick evaluation on minibatch
|
|
102
|
+
minibatch_results = self._evaluate_minibatch(
|
|
103
|
+
behavior_names=behavior_names,
|
|
104
|
+
num_scenarios=self.minibatch_size,
|
|
105
|
+
context=context,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
score = minibatch_results.get("overall_score", 0.0)
|
|
109
|
+
candidate_scores.append((candidate, score))
|
|
110
|
+
|
|
111
|
+
# Select best candidate
|
|
112
|
+
candidate_scores.sort(key=lambda x: x[1], reverse=True)
|
|
113
|
+
best_iteration_candidate, best_iteration_score = candidate_scores[0]
|
|
114
|
+
|
|
115
|
+
# Check for improvement
|
|
116
|
+
improvement = best_iteration_score - best_score
|
|
117
|
+
|
|
118
|
+
evolution_history.append(
|
|
119
|
+
{
|
|
120
|
+
"iteration": iteration + 1,
|
|
121
|
+
"candidates_tested": len(candidates),
|
|
122
|
+
"best_score": best_iteration_score,
|
|
123
|
+
"improvement": improvement,
|
|
124
|
+
"candidate": best_iteration_candidate,
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Accept if improved
|
|
129
|
+
if improvement >= self.improvement_threshold:
|
|
130
|
+
best_candidate = best_iteration_candidate
|
|
131
|
+
best_score = best_iteration_score
|
|
132
|
+
current_candidate = best_iteration_candidate
|
|
133
|
+
|
|
134
|
+
# Update adapter with best prompt
|
|
135
|
+
self.adapter.update_prompt(best_candidate["system_prompt"])
|
|
136
|
+
else:
|
|
137
|
+
# No improvement, stop early
|
|
138
|
+
break
|
|
139
|
+
|
|
140
|
+
# Restore best prompt to adapter
|
|
141
|
+
self.adapter.update_prompt(best_candidate["system_prompt"])
|
|
142
|
+
|
|
143
|
+
# Prepare evolved prompts output
|
|
144
|
+
evolved_prompts = {
|
|
145
|
+
"agent": self.adapter.get_adapter_type(),
|
|
146
|
+
"prompts": best_candidate,
|
|
147
|
+
"metadata": {
|
|
148
|
+
"iterations": len(evolution_history),
|
|
149
|
+
"initial_score": current_score,
|
|
150
|
+
"final_score": best_score,
|
|
151
|
+
"improvement": best_score - current_score,
|
|
152
|
+
"evolution_history": evolution_history,
|
|
153
|
+
},
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
# Save evolved prompts
|
|
157
|
+
if self.artifact_manager:
|
|
158
|
+
run_id = evaluation_results.get("run_id")
|
|
159
|
+
self.artifact_manager.save_evolved_prompts(evolved_prompts, run_id=run_id)
|
|
160
|
+
|
|
161
|
+
return evolved_prompts
|
|
162
|
+
|
|
163
|
+
def _generate_candidates(
|
|
164
|
+
self,
|
|
165
|
+
current_candidate: dict[str, str],
|
|
166
|
+
evaluation_results: dict[str, Any],
|
|
167
|
+
reflection: str,
|
|
168
|
+
population_size: int,
|
|
169
|
+
) -> list[dict[str, str]]:
|
|
170
|
+
"""Generate population of candidate prompts."""
|
|
171
|
+
candidates = []
|
|
172
|
+
|
|
173
|
+
# Always include current candidate
|
|
174
|
+
candidates.append(current_candidate.copy())
|
|
175
|
+
|
|
176
|
+
# Generate new candidates
|
|
177
|
+
for _ in range(population_size - 1):
|
|
178
|
+
proposed_prompt = self.proposer.propose_improved_prompt(
|
|
179
|
+
current_prompt=current_candidate["system_prompt"],
|
|
180
|
+
evaluation_results=evaluation_results,
|
|
181
|
+
reflection=reflection,
|
|
182
|
+
component_name="system_prompt",
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
candidates.append({"system_prompt": proposed_prompt})
|
|
186
|
+
|
|
187
|
+
return candidates
|
|
188
|
+
|
|
189
|
+
def _evaluate_minibatch(
|
|
190
|
+
self, behavior_names: list[str], num_scenarios: int, context: dict[str, Any]
|
|
191
|
+
) -> dict[str, Any]:
|
|
192
|
+
"""Quick evaluation on a minibatch of scenarios."""
|
|
193
|
+
# Use evaluation engine with reduced scenarios
|
|
194
|
+
config = self.evaluation_engine.config.copy()
|
|
195
|
+
config["scenario_generator"] = config.get("scenario_generator", {}).copy()
|
|
196
|
+
config["scenario_generator"]["num_scenarios"] = num_scenarios
|
|
197
|
+
|
|
198
|
+
# Create temporary evaluation engine with reduced config
|
|
199
|
+
from codeoptix.evaluation import EvaluationEngine
|
|
200
|
+
|
|
201
|
+
temp_engine = EvaluationEngine(self.adapter, self.llm_client, config=config)
|
|
202
|
+
|
|
203
|
+
# Run evaluation
|
|
204
|
+
results = temp_engine.evaluate_behaviors(behavior_names=behavior_names, context=context)
|
|
205
|
+
|
|
206
|
+
return results
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Minimal GEPA integration for CodeOptiX evolution.
|
|
2
|
+
|
|
3
|
+
This module provides a minimal integration with GEPA (Genetic-Pareto) by using
|
|
4
|
+
GEPA's InstructionProposalSignature component for prompt evolution.
|
|
5
|
+
|
|
6
|
+
Note: This is NOT a full GEPA framework integration. We're using GEPA's proven
|
|
7
|
+
instruction proposal mechanism rather than the complete GEPA optimization engine.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from gepa.strategies.instruction_proposal import InstructionProposalSignature
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MinimalGEPAProposer:
|
|
16
|
+
"""
|
|
17
|
+
Minimal GEPA integration for prompt proposal.
|
|
18
|
+
|
|
19
|
+
Uses GEPA's InstructionProposalSignature for prompt evolution.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, llm_client, config: dict[str, Any] | None = None):
|
|
23
|
+
"""
|
|
24
|
+
Initialize minimal GEPA proposer.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
llm_client: LLM client compatible with GEPA's LanguageModel interface
|
|
28
|
+
config: Configuration dictionary
|
|
29
|
+
"""
|
|
30
|
+
self.llm_client = llm_client
|
|
31
|
+
self.config = config or {}
|
|
32
|
+
self.prompt_template = self.config.get(
|
|
33
|
+
"prompt_template", InstructionProposalSignature.default_prompt_template
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def propose_improved_prompt(
|
|
37
|
+
self,
|
|
38
|
+
current_prompt: str,
|
|
39
|
+
reflective_dataset: list[dict[str, Any]],
|
|
40
|
+
component_name: str = "system_prompt",
|
|
41
|
+
) -> str:
|
|
42
|
+
"""
|
|
43
|
+
Propose improved prompt using GEPA's InstructionProposalSignature.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
current_prompt: Current prompt text
|
|
47
|
+
reflective_dataset: List of failure examples with feedback
|
|
48
|
+
component_name: Name of the component
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Proposed improved prompt text
|
|
52
|
+
"""
|
|
53
|
+
# Convert reflective dataset to GEPA format
|
|
54
|
+
gepa_dataset = self._convert_to_gepa_format(reflective_dataset)
|
|
55
|
+
|
|
56
|
+
# Use GEPA's instruction proposal
|
|
57
|
+
result = InstructionProposalSignature.run(
|
|
58
|
+
lm=self._wrap_llm_client(),
|
|
59
|
+
input_dict={
|
|
60
|
+
"current_instruction_doc": current_prompt,
|
|
61
|
+
"dataset_with_feedback": gepa_dataset,
|
|
62
|
+
"prompt_template": self.prompt_template,
|
|
63
|
+
},
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
return result["new_instruction"]
|
|
67
|
+
|
|
68
|
+
def _convert_to_gepa_format(
|
|
69
|
+
self, reflective_dataset: list[dict[str, Any]]
|
|
70
|
+
) -> list[dict[str, Any]]:
|
|
71
|
+
"""
|
|
72
|
+
Convert CodeOptiX reflective dataset to GEPA format.
|
|
73
|
+
|
|
74
|
+
GEPA's InstructionProposalSignature expects a dataset with this structure:
|
|
75
|
+
{
|
|
76
|
+
"Inputs": Dict[str, str], # Task inputs
|
|
77
|
+
"Generated Outputs": Dict[str, str] | str, # Model outputs
|
|
78
|
+
"Feedback": str # Feedback on performance
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
This format is used by GEPA's default prompt template to generate
|
|
82
|
+
improved instructions through reflective mutation.
|
|
83
|
+
"""
|
|
84
|
+
gepa_format = []
|
|
85
|
+
|
|
86
|
+
for example in reflective_dataset:
|
|
87
|
+
gepa_example = {
|
|
88
|
+
"Inputs": {
|
|
89
|
+
"task": example.get("scenario", ""),
|
|
90
|
+
"behavior": example.get("behavior", ""),
|
|
91
|
+
},
|
|
92
|
+
"Generated Outputs": {
|
|
93
|
+
"score": example.get("score", 0.0),
|
|
94
|
+
"evidence": example.get("evidence", []),
|
|
95
|
+
},
|
|
96
|
+
"Feedback": self._generate_feedback(example),
|
|
97
|
+
}
|
|
98
|
+
gepa_format.append(gepa_example)
|
|
99
|
+
|
|
100
|
+
return gepa_format
|
|
101
|
+
|
|
102
|
+
def _generate_feedback(self, example: dict[str, Any]) -> str:
|
|
103
|
+
"""Generate feedback string from example."""
|
|
104
|
+
behavior = example.get("behavior", "unknown")
|
|
105
|
+
score = example.get("score", 0.0)
|
|
106
|
+
evidence = example.get("evidence", [])
|
|
107
|
+
|
|
108
|
+
feedback = f"Behavior '{behavior}' scored {score:.2f}/1.0. "
|
|
109
|
+
|
|
110
|
+
if evidence:
|
|
111
|
+
feedback += "Issues found: " + "; ".join(evidence[:3])
|
|
112
|
+
else:
|
|
113
|
+
feedback += "No specific issues identified."
|
|
114
|
+
|
|
115
|
+
return feedback
|
|
116
|
+
|
|
117
|
+
def _wrap_llm_client(self):
|
|
118
|
+
"""
|
|
119
|
+
Wrap CodeOptiX LLM client to GEPA's LanguageModel interface.
|
|
120
|
+
|
|
121
|
+
GEPA expects a LanguageModel Protocol with a __call__ method that takes
|
|
122
|
+
a prompt string and returns a string response.
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
class GEPALLMWrapper:
|
|
126
|
+
def __init__(self, llm_client):
|
|
127
|
+
self.llm_client = llm_client
|
|
128
|
+
# Get default model from config if available
|
|
129
|
+
self.default_model = "gpt-4o" # Default fallback
|
|
130
|
+
if hasattr(llm_client, "config") and isinstance(llm_client.config, dict):
|
|
131
|
+
self.default_model = llm_client.config.get("model", self.default_model)
|
|
132
|
+
|
|
133
|
+
def __call__(self, prompt: str) -> str:
|
|
134
|
+
"""
|
|
135
|
+
Generate response using CodeOptiX LLM client.
|
|
136
|
+
|
|
137
|
+
This implements GEPA's LanguageModel Protocol:
|
|
138
|
+
- Takes a prompt string
|
|
139
|
+
- Returns a string response
|
|
140
|
+
"""
|
|
141
|
+
response = self.llm_client.chat_completion(
|
|
142
|
+
messages=[{"role": "user", "content": prompt}],
|
|
143
|
+
model=self.default_model,
|
|
144
|
+
temperature=0.7,
|
|
145
|
+
)
|
|
146
|
+
# Ensure we return a string
|
|
147
|
+
return str(response) if response else ""
|
|
148
|
+
|
|
149
|
+
return GEPALLMWrapper(self.llm_client)
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""Prompt proposer using GEPA-style reflective mutation."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from codeoptix.evolution.gepa_integration import MinimalGEPAProposer
|
|
6
|
+
from codeoptix.utils.llm import LLMClient
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PromptProposer:
|
|
10
|
+
"""
|
|
11
|
+
Proposes improved prompts using GEPA-style reflective mutation.
|
|
12
|
+
|
|
13
|
+
Uses evaluation results and reflection to generate improved prompt variants.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, llm_client: LLMClient, config: dict[str, Any] | None = None):
|
|
17
|
+
"""
|
|
18
|
+
Initialize prompt proposer.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
llm_client: LLM client for generating proposals
|
|
22
|
+
config: Configuration dictionary
|
|
23
|
+
"""
|
|
24
|
+
self.llm_client = llm_client
|
|
25
|
+
self.config = config or {}
|
|
26
|
+
self.use_gepa = self.config.get("use_gepa", True) # Use GEPA by default
|
|
27
|
+
|
|
28
|
+
# Initialize GEPA proposer if enabled
|
|
29
|
+
if self.use_gepa:
|
|
30
|
+
self.gepa_proposer = MinimalGEPAProposer(llm_client, config)
|
|
31
|
+
else:
|
|
32
|
+
self.gepa_proposer = None
|
|
33
|
+
self.model = self.config.get("model", "gpt-4o")
|
|
34
|
+
self.temperature = self.config.get("temperature", 0.7)
|
|
35
|
+
|
|
36
|
+
def propose_improved_prompt(
|
|
37
|
+
self,
|
|
38
|
+
current_prompt: str,
|
|
39
|
+
evaluation_results: dict[str, Any],
|
|
40
|
+
reflection: str,
|
|
41
|
+
component_name: str = "system_prompt",
|
|
42
|
+
) -> str:
|
|
43
|
+
"""
|
|
44
|
+
Propose an improved prompt based on evaluation results and reflection.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
current_prompt: Current prompt text
|
|
48
|
+
evaluation_results: Evaluation results dictionary
|
|
49
|
+
reflection: Reflection markdown content
|
|
50
|
+
component_name: Name of the prompt component
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Proposed improved prompt text
|
|
54
|
+
"""
|
|
55
|
+
# Build reflective dataset from evaluation results
|
|
56
|
+
reflective_data = self._build_reflective_dataset(evaluation_results)
|
|
57
|
+
|
|
58
|
+
# Use GEPA if enabled
|
|
59
|
+
if self.use_gepa and self.gepa_proposer:
|
|
60
|
+
return self.gepa_proposer.propose_improved_prompt(
|
|
61
|
+
current_prompt=current_prompt,
|
|
62
|
+
reflective_dataset=reflective_data,
|
|
63
|
+
component_name=component_name,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Fallback to custom implementation
|
|
67
|
+
# Create proposal prompt
|
|
68
|
+
proposal_prompt = self._create_proposal_prompt(
|
|
69
|
+
current_prompt=current_prompt,
|
|
70
|
+
reflective_data=reflective_data,
|
|
71
|
+
reflection=reflection,
|
|
72
|
+
component_name=component_name,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Get proposal from LLM
|
|
76
|
+
response = self.llm_client.chat_completion(
|
|
77
|
+
messages=[{"role": "user", "content": proposal_prompt}],
|
|
78
|
+
model=self.model,
|
|
79
|
+
temperature=self.temperature,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# Extract proposed prompt
|
|
83
|
+
proposed_prompt = self._extract_proposed_prompt(response, current_prompt)
|
|
84
|
+
|
|
85
|
+
return proposed_prompt
|
|
86
|
+
|
|
87
|
+
def _build_reflective_dataset(self, evaluation_results: dict[str, Any]) -> list[dict[str, Any]]:
|
|
88
|
+
"""Build reflective dataset from evaluation results."""
|
|
89
|
+
dataset = []
|
|
90
|
+
|
|
91
|
+
behaviors = evaluation_results.get("behaviors", {})
|
|
92
|
+
|
|
93
|
+
for behavior_name, behavior_data in behaviors.items():
|
|
94
|
+
score = behavior_data.get("score", 0.0)
|
|
95
|
+
evidence = behavior_data.get("evidence", [])
|
|
96
|
+
scenario_results = behavior_data.get("scenario_results", [])
|
|
97
|
+
|
|
98
|
+
# Collect failure examples
|
|
99
|
+
if score < 0.7: # Focus on failures
|
|
100
|
+
for scenario_result in scenario_results:
|
|
101
|
+
behavior_result = scenario_result.get("behavior_result", {})
|
|
102
|
+
if not behavior_result.get("passed", True):
|
|
103
|
+
dataset.append(
|
|
104
|
+
{
|
|
105
|
+
"behavior": behavior_name,
|
|
106
|
+
"score": score,
|
|
107
|
+
"evidence": evidence[:3], # Limit evidence
|
|
108
|
+
"scenario": scenario_result.get("scenario", {}).get("prompt", ""),
|
|
109
|
+
}
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
return dataset
|
|
113
|
+
|
|
114
|
+
def _create_proposal_prompt(
|
|
115
|
+
self,
|
|
116
|
+
current_prompt: str,
|
|
117
|
+
reflective_data: list[dict[str, Any]],
|
|
118
|
+
reflection: str,
|
|
119
|
+
component_name: str,
|
|
120
|
+
) -> str:
|
|
121
|
+
"""Create prompt for LLM to propose improved prompt."""
|
|
122
|
+
|
|
123
|
+
# Format reflective data
|
|
124
|
+
failure_examples = []
|
|
125
|
+
for i, data in enumerate(reflective_data[:5], 1): # Limit to 5 examples
|
|
126
|
+
failure_examples.append(
|
|
127
|
+
f"Example {i}:\n"
|
|
128
|
+
f" Behavior: {data['behavior']}\n"
|
|
129
|
+
f" Score: {data['score']:.2f}\n"
|
|
130
|
+
f" Issues: {', '.join(data['evidence'][:2])}\n"
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
prompt = f"""You are optimizing a coding agent's prompt to improve its behavior.
|
|
134
|
+
|
|
135
|
+
Current {component_name}:
|
|
136
|
+
```
|
|
137
|
+
{current_prompt}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Evaluation Results Summary:
|
|
141
|
+
{reflection[:1000]}...
|
|
142
|
+
|
|
143
|
+
Specific Failure Examples:
|
|
144
|
+
{"".join(failure_examples)}
|
|
145
|
+
|
|
146
|
+
Based on the evaluation results and reflection above, propose an improved version of the {component_name} that addresses the identified issues.
|
|
147
|
+
|
|
148
|
+
The improved prompt should:
|
|
149
|
+
1. Address the root causes identified in the reflection
|
|
150
|
+
2. Include specific guidance to prevent the failures observed
|
|
151
|
+
3. Maintain the original intent while adding necessary constraints
|
|
152
|
+
4. Be clear, actionable, and specific
|
|
153
|
+
|
|
154
|
+
Provide ONLY the improved prompt text, without additional explanation or markdown formatting.
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
return prompt
|
|
158
|
+
|
|
159
|
+
def _extract_proposed_prompt(self, response: str, fallback: str) -> str:
|
|
160
|
+
"""Extract proposed prompt from LLM response."""
|
|
161
|
+
# Try to extract code block
|
|
162
|
+
import re
|
|
163
|
+
|
|
164
|
+
# Look for code blocks
|
|
165
|
+
code_blocks = re.findall(r"```(?:.*)?\n(.*?)```", response, re.DOTALL)
|
|
166
|
+
if code_blocks:
|
|
167
|
+
return code_blocks[0].strip()
|
|
168
|
+
|
|
169
|
+
# Look for text between markers
|
|
170
|
+
if "Current" in response and "Improved" in response:
|
|
171
|
+
# Try to extract after "Improved" marker
|
|
172
|
+
improved_section = response.split("Improved")[-1]
|
|
173
|
+
# Take first substantial paragraph
|
|
174
|
+
lines = [l.strip() for l in improved_section.split("\n") if l.strip()]
|
|
175
|
+
if lines:
|
|
176
|
+
return "\n".join(lines[:10]) # Take first 10 non-empty lines
|
|
177
|
+
|
|
178
|
+
# Fallback: use response as-is, but clean it up
|
|
179
|
+
cleaned = response.strip()
|
|
180
|
+
# Remove common prefixes
|
|
181
|
+
for prefix in ["Improved prompt:", "Here's the improved prompt:", "Proposed prompt:"]:
|
|
182
|
+
if cleaned.startswith(prefix):
|
|
183
|
+
cleaned = cleaned[len(prefix) :].strip()
|
|
184
|
+
|
|
185
|
+
return cleaned if cleaned else fallback
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Linter integration for CodeOptiX - works without API keys."""
|
|
2
|
+
|
|
3
|
+
from codeoptix.linters.base import LinterIssue, LinterResult, Severity
|
|
4
|
+
from codeoptix.linters.language_detector import LanguageDetector
|
|
5
|
+
from codeoptix.linters.runner import LinterRunner
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"LanguageDetector",
|
|
9
|
+
"LinterIssue",
|
|
10
|
+
"LinterResult",
|
|
11
|
+
"LinterRunner",
|
|
12
|
+
"Severity",
|
|
13
|
+
]
|