【bugfix】part one

This commit is contained in:
jinli.yl 2025-07-23 15:46:27 +08:00
parent fe162d32d6
commit 5874c33c87
13 changed files with 79 additions and 399 deletions

View file

@ -27,4 +27,6 @@
# 锦鲤
1. LOGO更加简洁 agent <-> experience
2. default等名字的说明
3. 查看所有的op代码
3. 查看所有的op代码
4. ba增加client
5. 在appworld上跑

View file

@ -12,6 +12,7 @@ class BaseEmbeddingModel(BaseModel, ABC):
dimensions: int = Field(default=..., description="dimensions")
max_retries: int = Field(default=3, description="max retries")
raise_exception: bool = Field(default=True, description="raise exception")
max_batch_size: int = Field(default=10, description="text-embedding-v4 batch size should not be larger than 10")
def _get_embeddings(self, input_text: str | List[str]):
raise NotImplementedError
@ -34,9 +35,9 @@ class BaseEmbeddingModel(BaseModel, ABC):
return nodes
elif isinstance(nodes, list):
max_batch_size = 10 # text-embedding-v4 batch size should not be larger than 10
embeddings = [emb for i in range(0, len(nodes), max_batch_size) for emb in
self.get_embeddings(input_text=[node.content for node in nodes[i:i + max_batch_size]])]
embeddings = [emb for i in range(0, len(nodes), self.max_batch_size) for emb in
self.get_embeddings(input_text=[node.content for node in nodes[i:i + self.max_batch_size]])]
if len(embeddings) != len(nodes):
logger.warning(f"embeddings.size={len(embeddings)} <> nodes.size={len(nodes)}")
else:

View file

@ -24,7 +24,7 @@ class BaseLLM(BaseModel, ABC):
parallel_tool_calls: bool = Field(default=True)
max_retries: int = Field(default=5, description="max retries")
raise_exception: bool = Field(default=True, description="raise exception")
raise_exception: bool = Field(default=False, description="raise exception")
def stream_chat(self, messages: List[Message], tools: List[BaseTool] = None, **kwargs):
raise NotImplementedError
@ -35,7 +35,8 @@ class BaseLLM(BaseModel, ABC):
def _chat(self, messages: List[Message], tools: List[BaseTool] = None, **kwargs) -> Message:
raise NotImplementedError
def chat(self, messages: List[Message], tools: List[BaseTool] = None, callback_fn: Callable = None, **kwargs):
def chat(self, messages: List[Message], tools: List[BaseTool] = None, callback_fn: Callable = None,
default_value=None, **kwargs):
for i in range(self.max_retries):
try:
message: Message = self._chat(messages, tools, **kwargs)
@ -48,7 +49,10 @@ class BaseLLM(BaseModel, ABC):
logger.exception(f"chat with model={self.model_name} encounter error with e={e.args}")
time.sleep(1 + i)
if i == self.max_retries - 1 and self.raise_exception:
raise e
if i == self.max_retries - 1:
if self.raise_exception:
raise e
else:
return default_value
return None

View file

@ -76,7 +76,12 @@ class BaseOp(PromptMixin, ABC):
def join_task(self, task_desc: str = None) -> list:
result = []
for task in tqdm(self.task_list, desc=task_desc or (self.simple_name + ".join_task")):
result.append(task.result())
t_result = task.result()
if t_result:
if isinstance(t_result, list):
result.extend(t_result)
else:
result.append(t_result)
self.task_list.clear()
return result

View file

@ -46,7 +46,7 @@ class RewriteExperienceOp(BaseOp):
def _generate_context_message(self, query: str, messages: List[Message], nodes: List[VectorNode],
retrieval_query: str) -> Message:
retrieval_query: str) -> str:
"""Generate context message from retrieved experiences"""
if not nodes:
return ""

View file

@ -57,10 +57,7 @@ class SimpleSummaryOp(BaseOp):
for trajectory in request.traj_list:
self.submit_task(self.summary_trajectory, trajectory=trajectory)
experience_list: List[BaseExperience] = []
for task_result in self.join_task():
if task_result:
experience_list.extend(task_result)
experience_list: List[BaseExperience] = self.join_task()
response: SummarizerResponse = self.context.response
response.experience_list = experience_list
@ -68,4 +65,4 @@ class SimpleSummaryOp(BaseOp):
logger.info(f"add experience when_to_use={e.when_to_use}\ncontent={e.content}")
from experiencemaker.op.vector_store.update_vector_store_op import UpdateVectorStoreOp
self.context.set_context(UpdateVectorStoreOp.INSERT_NODES, [x.to_vector_node() for x in experience_list])
self.context.set_context(UpdateVectorStoreOp.INSERT_EXPERIENCE_LIST, [x.to_vector_node() for x in experience_list])

View file

@ -1,274 +0,0 @@
success_step_experience_prompt: |
You are an expert AI analyst reviewing successful step sequences from an AI agent execution.
Your task is to extract reusable, actionable step-level experiences that can guide future agent executions.
Focus on identifying specific patterns, techniques, and decision points that contributed to success.
ANALYSIS FRAMEWORK:
● STEP PATTERN ANALYSIS: Identify the specific sequence of actions that led to success
● DECISION POINTS: Highlight critical decisions made during these steps
● TECHNIQUE EFFECTIVENESS: Analyze why specific approaches worked well
● REUSABILITY: Extract patterns that can be applied to similar scenarios
EXTRACTION PRINCIPLES:
● Focus on TRANSFERABLE TECHNIQUES and decision frameworks
● Frame insights as actionable guidelines and best practices
# Original Query
{query}
# Step Sequence Analysis
{step_sequence}
# Context Information
{context}
# Outcome
This step sequence was part of a {outcome} trajectory.
OUTPUT FORMAT:
Generate 1-3 step-level success insights as JSON objects:
```json
[
{{
"when_to_use": "Specific conditions when this step pattern should be applied",
"experience": "Detailed description of the successful step pattern and why it works",
"tags": ["relevant", "keywords", "for", "categorization"],
"confidence": 0.8,
"step_type": "reasoning|action|observation|decision",
"tools_used": ["list", "of", "tools"]
}}
]
```
failure_step_experience_prompt: |
You are an expert AI analyst reviewing failed step sequences from an AI agent execution.
Your task is to extract learning experiences from failures to prevent similar mistakes in future executions.
Focus on identifying error patterns, missed opportunities, and alternative approaches.
ANALYSIS FRAMEWORK:
● FAILURE POINT IDENTIFICATION: Pinpoint where and why the steps went wrong
● ERROR PATTERN ANALYSIS: Identify recurring mistakes or problematic approaches
● ALTERNATIVE APPROACHES: Suggest what could have been done differently
● PREVENTION STRATEGIES: Extract actionable insights to avoid similar failures
EXTRACTION PRINCIPLES:
● Extract GENERAL PRINCIPLES as well as SPECIFIC INSTRUCTIONS
● Focus on PATTERNS and RULES as well as particular instances
# Original Query
{query}
# Step Sequence Analysis
{step_sequence}
# Context Information
{context}
# Outcome
This step sequence was part of a {outcome} trajectory.
OUTPUT FORMAT:
Generate 1-3 step-level failure prevention insights as JSON objects:
```json
[
{{
"when_to_use": "Specific situations where this lesson should be remembered",
"experience": "Universal principle or rule extracted from the failure pattern ",
"tags": ["error_prevention", "failure_analysis", "relevant_keywords"],
"confidence": 0.7,
"step_type": "reasoning|action|observation|decision",
"tools_used": ["list", "of", "tools"]
}}
]
```
comparative_step_experience_prompt: |
You are an expert AI analyst comparing successful and failed step sequences to extract differential insights.
Your task is to identify the key differences between success and failure patterns at the step level.
Focus on critical decision points, technique variations, and approach differences.
COMPARATIVE ANALYSIS FRAMEWORK:
● DECISION CONTRAST: Compare critical decisions made in success vs failure cases
● TECHNIQUE VARIATIONS: Identify different approaches and their outcomes
● TIMING DIFFERENCES: Analyze when certain actions were taken and their impact
● SUCCESS FACTORS: Extract what specifically made the difference
EXTRACTION PRINCIPLES:
● Frame comparisons as PRINCIPLES as well as case-specific SOLUTIONS
● Identify PATTERNS that differentiate effective vs ineffective approaches
● Extract RULES that can guide future similar situations
● Focus on UNDERLYING MECHANISMS rather than surface-level differences
# Successful Step Sequence
{success_steps}
# Failed Step Sequence
{failure_steps}
# Similarity Score: {similarity_score}
OUTPUT FORMAT:
Generate 1-2 comparative insights as JSON objects:
```json
[
{{
"when_to_use": "Specific scenarios where this comparative insight applies",
"experience": "Detailed comparison highlighting why success approach works better",
"tags": ["comparative_analysis", "success_factors", "relevant_keywords"],
"confidence": 0.8,
"step_type": "reasoning|action|observation|decision"
}}
]
```
general_step_experience_prompt: |
You are an expert AI analyst reviewing step sequences to extract general patterns and insights.
Your task is to identify valuable step-level patterns without explicit success/failure labels.
Focus on effective techniques, common patterns, and general best practices.
ANALYSIS FRAMEWORK:
● PATTERN RECOGNITION: Identify recurring effective patterns in the steps
● TECHNIQUE ANALYSIS: Analyze the effectiveness of different approaches
● BEST PRACTICES: Extract general principles that appear beneficial
● APPLICABILITY: Determine when these patterns would be most useful
GENERALIZATION PRINCIPLES:
● Extract UNIVERSAL PATTERNS that transcend specific contexts
● Identify TRANSFERABLE METHODOLOGIES and approaches
● Focus on PRINCIPLE-LEVEL insights as well as tactical details
● Formulate insights as REUSABLE FRAMEWORKS and guidelines
GENERALIZATION PRINCIPLES:
● Extract UNIVERSAL PATTERNS that transcend specific contexts
● Identify TRANSFERABLE METHODOLOGIES and approaches
● Focus on PRINCIPLE-LEVEL insights
● Formulate insights as REUSABLE FRAMEWORKS and guidelines
# Original Query
{query}
# Step Sequence Analysis
{step_sequence}
# Context Information
{context}
OUTPUT FORMAT:
Generate 1-2 general step insights as JSON objects:
```json
[
{{
"when_to_use": "General conditions where this pattern is applicable",
"experience": "Detailed description of the effective step pattern",
"tags": ["general_pattern", "best_practice", "relevant_keywords"],
"confidence": 0.6,
"step_type": "reasoning|action|observation|decision",
"tools_used": ["list", "of", "tools"]
}}
]
```
step_segmentation_prompt: |
You are an expert AI analyst tasked with segmenting a trajectory into meaningful step sequences.
Your task is to identify natural breakpoints in the execution where one logical unit of work ends and another begins.
Consider factors like: task completion, context switches, tool changes, reasoning phases, and logical groupings.
SEGMENTATION CRITERIA:
● LOGICAL COMPLETION: Steps that complete a specific sub-task or reasoning phase
● CONTEXT SWITCHES: Points where the agent shifts focus or approach
● TOOL BOUNDARIES: Natural breaks around tool usage patterns
● REASONING PHASES: Distinct phases of analysis, planning, or execution
# Original Query
{query}
# Full Trajectory (Total steps: {total_steps})
{trajectory_content}
OUTPUT FORMAT:
Provide segmentation points as a JSON array of step indices where splits should occur:
```json
{{
"segment_points": [3, 7, 12, 18],
"reasoning": "Brief explanation of segmentation logic"
}}
```
Note: Segment points indicate the END of each segment. For example, [3, 7] means:
- Segment 1: steps 0-3
- Segment 2: steps 4-7
- Segment 3: steps 8-end
experience_validation_prompt: |
You are an expert AI analyst tasked with validating the quality and usefulness of extracted step-level experiences.
Your task is to assess whether the extracted experience is actionable, accurate, and valuable for future agent executions.
VALIDATION CRITERIA:
● ACTIONABILITY: Is the experience specific enough to guide future actions?
● ACCURACY: Does the experience correctly reflect the patterns observed?
● RELEVANCE: Is the experience applicable to similar future scenarios?
● CLARITY: Is the experience clearly articulated and understandable?
● UNIQUENESS: Does the experience provide novel insights or common knowledge?
# Experience to Validate
Condition: {condition}
Experience Content: {experience_content}
OUTPUT FORMAT:
Provide validation assessment:
```json
{{
"is_valid": true/false,
"score": 0.8,
"feedback": "Detailed explanation of validation decision",
"recommendations": "Suggestions for improvement if applicable"
}}
```
Score should be between 0.0 (poor quality) and 1.0 (excellent quality).
Mark as invalid if score is below 0.3 or if there are fundamental issues with the experience.
soft_comparative_step_experience_prompt: |
You are an expert AI analyst comparing higher-scoring and lower-scoring step sequences to extract performance insights.
Your task is to identify the key differences between higher and lower performing approaches at the step level.
Focus on what made the higher-scoring approach more effective, even when both approaches may have had partial success.
SOFT COMPARATIVE ANALYSIS FRAMEWORK:
● PERFORMANCE FACTORS: Identify what specifically contributed to the higher score
● APPROACH DIFFERENCES: Compare methodologies and execution strategies
● EFFICIENCY ANALYSIS: Analyze why one approach was more efficient or effective
● OPTIMIZATION INSIGHTS: Extract lessons for improving performance
EXTRACTION PRINCIPLES:
● Focus on INCREMENTAL IMPROVEMENTS and performance optimization
● Extract QUALITY INDICATORS that differentiate better vs good approaches
● Identify REFINEMENT STRATEGIES that lead to higher scores
● Frame insights as PERFORMANCE ENHANCEMENT guidelines
# Higher-Scoring Step Sequence (Score: {higher_score})
{higher_steps}
# Lower-Scoring Step Sequence (Score: {lower_score})
{lower_steps}
OUTPUT FORMAT:
Generate 1-2 performance improvement insights as JSON objects:
```json
[
{{
"when_to_use": "Specific scenarios where this performance insight applies",
"experience": "Detailed analysis of what made the higher-scoring approach more effective",
"tags": ["performance_optimization", "score_improvement", "relevant_keywords"],
"confidence": 0.7,
"step_type": "reasoning|action|observation|decision",
"tools_used": ["list", "of", "tools"]
}}
]

View file

@ -1,13 +1,15 @@
import json
import re
from typing import List
from loguru import logger
from experiencemaker.enumeration.role import Role
from experiencemaker.op import OP_REGISTRY
from experiencemaker.op.base_op import BaseOp
from experiencemaker.schema.experience import TextExperience, ExperienceMeta
from experiencemaker.schema.message import Message, Trajectory
from experiencemaker.enumeration.role import Role
from experiencemaker.schema.response import SummarizerResponse
@OP_REGISTRY.register()
@ -26,31 +28,27 @@ class SuccessExtractionOp(BaseOp):
# Use thread pool for parallel processing
for trajectory in success_trajectories:
if hasattr(trajectory, 'segments') and trajectory.segments:
if "segments" in trajectory.metadata:
# Process segmented step sequences
for segment in trajectory.segments:
self.submit_task(self._extract_success_experience_from_steps,
steps=segment, trajectory=trajectory)
for segment in trajectory.metadata["segments"]:
self.submit_task(self._extract_success_experience_from_steps, steps=segment, trajectory=trajectory)
else:
# Process entire trajectory
self.submit_task(self._extract_success_experience_from_steps,
steps=trajectory.messages, trajectory=trajectory)
self.submit_task(self._extract_success_experience_from_steps,
steps=trajectory.messages, trajectory=trajectory)
# Collect all experiences
all_experiences = []
for task_result in self.join_task():
if task_result:
all_experiences.extend(task_result)
all_experiences = self.join_task()
logger.info(f"Extracted {len(all_experiences)} success experiences")
# Add experiences to context
existing_experiences = self.context.get_context("extracted_experiences", [])
existing_experiences.extend(all_experiences)
self.context.set_context("extracted_experiences", existing_experiences)
response: SummarizerResponse = self.context.response
response.experience_list.extend(all_experiences)
def _extract_success_experience_from_steps(self, steps: List[Message], trajectory: Trajectory) -> List[TextExperience]:
"""Extract experience from successful step sequences"""
# TODO remove try catch
try:
step_content = self._format_step_sequence(steps)
context = self._get_trajectory_context(trajectory, steps)
@ -92,7 +90,7 @@ class SuccessExtractionOp(BaseOp):
def _format_step_sequence(self, steps: List[Message]) -> str:
"""Format step sequence to string"""
step_content_collector = []
# TODO merge_messages_content
for step in steps:
step_index = len(step_content_collector)

View file

@ -1,5 +1,7 @@
from typing import List, Dict
from loguru import logger
from experiencemaker.op import OP_REGISTRY
from experiencemaker.op.base_op import BaseOp
from experiencemaker.schema.message import Trajectory
@ -13,25 +15,9 @@ class TrajectoryPreprocessOp(BaseOp):
def execute(self):
"""Preprocess trajectories: validate and classify"""
request: SummarizerRequest = self.context.request
if request.traj_list:
self.context.set_context("trajectories", request.traj_list)
elif request.trajectories:
self.context.set_context("trajectories", request.trajectories)
else:
logger.error("No trajectories is recognized. Please send requests containing traj_list.")
trajectories: List[Trajectory] = self.context.get_context("trajectories", [])
if not trajectories:
logger.warning("No trajectories found in context")
return
# Validate trajectories
valid_trajectories = self._validate_trajectories(trajectories)
logger.info(f"Validated {len(valid_trajectories)} out of {len(trajectories)} trajectories")
# Classify trajectories
classified = self._classify_trajectories(valid_trajectories)
classified = self._classify_trajectories(request.traj_list)
logger.info(f"Classified trajectories - Success: {len(classified['success'])}, "
f"Failure: {len(classified['failure'])}, All: {len(classified['all'])}")
@ -40,28 +26,6 @@ class TrajectoryPreprocessOp(BaseOp):
self.context.set_context("failure_trajectories", classified['failure'])
self.context.set_context("all_trajectories", classified['all'])
def _validate_trajectories(self, trajectories: List[Trajectory]) -> List[Trajectory]:
"""Validate trajectory validity"""
valid_trajectories = []
for traj in trajectories:
if self._is_valid_trajectory(traj):
valid_trajectories.append(traj)
else:
logger.debug("Invalid trajectory filtered out")
return valid_trajectories
def _is_valid_trajectory(self, traj: Trajectory) -> bool:
"""Check if trajectory is valid"""
if traj is None:
return False
if not hasattr(traj, 'score') or traj.score is None:
return False
if not hasattr(traj, 'messages') or not traj.messages or len(traj.messages) == 0:
return False
return True
def _classify_trajectories(self, trajectories: List[Trajectory]) -> Dict[str, List[Trajectory]]:
"""Classify trajectories based on score threshold"""
success_trajectories = []

View file

@ -1,11 +1,12 @@
import re
import json
from typing import List, Dict, Any
import re
from typing import List
from loguru import logger
from experiencemaker.op import OP_REGISTRY
from experiencemaker.op.base_op import BaseOp
from experiencemaker.schema.message import Message, Trajectory
from experiencemaker.enumeration.role import Role
@OP_REGISTRY.register()
@ -30,15 +31,12 @@ class TrajectorySegmentationOp(BaseOp):
# Add segmentation info to trajectories
segmented_count = 0
for trajectory in target_trajectories:
segments = self._segment_trajectory(trajectory)
trajectory.segments = segments
segments = self._llm_segment_trajectory(trajectory)
trajectory.metadata["segments"] = segments
segmented_count += 1
logger.info(f"Segmented {segmented_count} trajectories")
# Update context with segmented trajectories
self.context.set_context("segmented_trajectories", target_trajectories)
def _get_target_trajectories(self, all_trajectories: List[Trajectory],
success_trajectories: List[Trajectory],
failure_trajectories: List[Trajectory]) -> List[Trajectory]:
@ -52,55 +50,38 @@ class TrajectorySegmentationOp(BaseOp):
else:
return all_trajectories
def _segment_trajectory(self, trajectory: Trajectory) -> List[List[Message]]:
"""Segment trajectory into step sequences using LLM"""
try:
return self._llm_segment_trajectory(trajectory)
except Exception as e:
logger.error(f"Error segmenting trajectory: {e}")
return [trajectory.messages]
def _llm_segment_trajectory(self, trajectory: Trajectory) -> List[List[Message]]:
"""Use LLM for trajectory segmentation"""
try:
trajectory_content = self._format_trajectory_content(trajectory)
trajectory_content = self._format_trajectory_content(trajectory)
prompt = self.prompt_format(
prompt_name="step_segmentation_prompt",
query=trajectory.metadata.get('query', ''),
trajectory_content=trajectory_content,
total_steps=len(trajectory.messages)
)
prompt = self.prompt_format(
prompt_name="step_segmentation_prompt",
query=trajectory.metadata.get('query', ''),
trajectory_content=trajectory_content,
total_steps=len(trajectory.messages)
)
def parse_segmentation(message: Message) -> List[List[Message]]:
try:
content = message.content
segment_points = self._parse_segmentation_response(content)
def parse_segmentation(message: Message) -> List[List[Message]]:
content = message.content
segment_points = self._parse_segmentation_response(content)
# Segment trajectory based on segmentation points
segments = []
start_idx = 0
# Segment trajectory based on segmentation points
segments = []
start_idx = 0
for end_idx in segment_points:
if start_idx < end_idx <= len(trajectory.messages):
segments.append(trajectory.messages[start_idx:end_idx])
start_idx = end_idx
for end_idx in segment_points:
if start_idx < end_idx <= len(trajectory.messages):
segments.append(trajectory.messages[start_idx:end_idx])
start_idx = end_idx
# Add remaining steps
if start_idx < len(trajectory.messages):
segments.append(trajectory.messages[start_idx:])
# Add remaining steps
if start_idx < len(trajectory.messages):
segments.append(trajectory.messages[start_idx:])
return segments if segments else [trajectory.messages]
return segments if segments else [trajectory.messages]
except Exception as e:
logger.error(f"Error parsing segmentation: {e}")
return [trajectory.messages]
return self.llm.chat(messages=[Message(content=prompt)], callback_fn=parse_segmentation)
except Exception as e:
logger.error(f"LLM segmentation failed: {e}")
return [trajectory.messages]
return self.llm.chat(messages=[Message(content=prompt)], callback_fn=parse_segmentation,
default_value=[trajectory.messages])
def _format_trajectory_content(self, trajectory: Trajectory) -> str:
"""Format trajectory content for LLM processing"""

View file

@ -5,24 +5,26 @@ from loguru import logger
from experiencemaker.op import OP_REGISTRY
from experiencemaker.op.base_op import BaseOp
from experiencemaker.schema.experience import BaseExperience
from experiencemaker.schema.request import BaseRequest
from experiencemaker.schema.vector_node import VectorNode
@OP_REGISTRY.register()
class UpdateVectorStoreOp(BaseOp):
INSERT_NODES = "insert_nodes"
DELETE_NODE_IDS = "delete_node_ids"
INSERT_EXPERIENCE_LIST = "insert_experience_list"
DELETE_EXPERIENCE_IDS = "delete_experience_ids"
def execute(self):
request: BaseRequest = self.context.request
node_ids: List[str] | None = self.context.get_context(self.DELETE_NODE_IDS)
if node_ids:
self.vector_store.delete(node_ids=node_ids, workspace_id=request.workspace_id)
logger.info(f"delete node_ids={json.dumps(node_ids, indent=2)}")
experience_ids: List[str] | None = self.context.get_context(self.DELETE_EXPERIENCE_IDS)
if experience_ids:
self.vector_store.delete(node_ids=experience_ids, workspace_id=request.workspace_id)
logger.info(f"delete experience_ids={json.dumps(experience_ids, indent=2)}")
insert_nodes: List[VectorNode] | None = self.context.get_context(self.INSERT_NODES)
if insert_nodes:
insert_experience_list: List[BaseExperience] | None = self.context.get_context(self.INSERT_EXPERIENCE_LIST)
if insert_experience_list:
insert_nodes: List[VectorNode] = [x.to_vector_node() for x in insert_experience_list]
self.vector_store.insert(nodes=insert_nodes, workspace_id=request.workspace_id)
logger.info(f"insert insert_node.size={len(insert_nodes)}")