Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,4 @@ test_scripts/
examples/data/
openviking/_version.py
specs/
.trae/
38 changes: 38 additions & 0 deletions openviking/prompts/templates/compression/field_compress.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
metadata:
id: "compression.field_compress"
name: "Field Content Compression"
description: "Compress field content while preserving key information"
version: "1.0.0"
language: "en"
category: "compression"

variables:
- name: "field_name"
type: "string"
description: "Field name (e.g., best_for, optimal_params)"
required: true
- name: "content"
type: "string"
description: "Original content to compress"
required: true
- name: "max_length"
type: "integer"
description: "Maximum length in characters"
required: true

template: |
Compress the following "{{ field_name }}" field content to fit within {{ max_length }} characters.

Original content:
{{ content }}

Requirements:
- Preserve the most important and unique information
- Remove redundancy and duplicates
- Use concise language
- Maintain semantic structure (keep items separated by semicolons if applicable)
- Output must be <= {{ max_length }} characters
- Return compressed content only, no explanation

llm_config:
temperature: 0.0
34 changes: 26 additions & 8 deletions openviking/prompts/templates/compression/memory_extraction.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -338,8 +338,12 @@ template: |
"category": "tools",
"tool_name": "web_search",
"abstract": "web_search: Technical docs search optimal, needs specific query terms",
"overview": "## Tool Info\\n- **Name**: web_search\\n- **Type**: external_api\\n\\n## Statistics\\n- **Success Rate**: 92%\\n- **Avg Time**: 2.3s\\n\\n## Best Scenarios\\n- Technical documentation (95%)\\n- Tutorials (90%)\\n\\n## Optimal Parameters\\n- max_results: 5-20\\n- language: 'en'",
"content": "## Recommendations\\n\\n### Query Optimization\\n- Use specific multi-word queries\\n- Add qualifiers like 'tutorial', 'guide'\\n\\n## Good Cases\\n- 'Python asyncio tutorial' → 3 high-quality results\\n\\n## Bad Cases\\n- 'programming' → timeout (too broad)"
"best_for": "Technical documentation, tutorials, API references",
"optimal_params": "max_results: 5-20 (larger values may timeout); language: 'en' for better results; query: specific multi-word phrases with qualifiers",
"common_failures": "Single-word queries return irrelevant results; max_results>50 causes timeout; non-English queries have lower quality",
"recommendation": "Use specific multi-word queries like 'Python asyncio tutorial'; add qualifiers like 'guide', 'docs', 'example'",
"overview": "## Tool Info\\n- **Name**: web_search\\n- **Type**: external_api\\n\\n## Statistics\\n- **Success Rate**: 92%\\n- **Avg Time**: 2.3s\\n\\n## Tool Memory Context\\n- Best for: Technical documentation, tutorials, API references\\n- Optimal params: max_results: 5-20; language: 'en'; query: specific multi-word phrases\\n- Common failures: Single-word queries; max_results>50 timeout\\n- Recommendation: Use specific multi-word queries with qualifiers",
"content": "## Guidelines\\n\\n### Query Optimization\\n- Use specific multi-word queries (e.g., 'FastAPI dependency injection guide')\\n- Add qualifiers: 'tutorial', 'guide', 'docs', 'example'\\n- Prefer English for technical content\\n\\n### Good Cases\\n- Query: 'Python asyncio tutorial for beginners' → Found 3 high-quality beginner tutorials\\n- Query: 'FastAPI dependency injection docs' → Located official documentation accurately\\n\\n### Bad Cases\\n- Query: 'programming' → Timeout, results too broad\\n- Query: 'how to code' → Irrelevant results, no specific context"
}
```

Expand All @@ -350,8 +354,13 @@ template: |
"category": "skills",
"skill_name": "create_presentation",
"abstract": "create_presentation: Collect materials first for better efficiency",
"overview": "## Skill Info\\n- **Name**: create_presentation\\n- **Type**: workflow\\n\\n## Statistics\\n- **Success Rate**: 85%\\n- **Avg Time**: 120s\\n\\n## Recommended Flow\\n1. Collect materials\\n2. Generate outline\\n3. Create slides\\n4. Refine content\\n\\n## Key Dependencies\\n- Clear topic\\n- Target audience",
"content": "## Execution Strategy\\n\\n### Preparation\\n1. Confirm topic and audience\\n2. Collect reference materials\\n\\n## Good Cases\\n- Tech sharing: collected asyncio docs first → 90s, high quality\\n\\n## Bad Cases\\n- 'Make a PPT' without topic → multiple reworks needed"
"best_for": "Slide creation tasks with clear topic and target audience",
"recommended_flow": "1. Confirm topic and audience → 2. Collect reference materials → 3. Generate outline → 4. Create slides → 5. Refine content",
"key_dependencies": "Clear topic (e.g., 'Q3 project update', 'Python tutorial'); Target audience (e.g., 'executives', 'beginners'); Reference materials (optional but recommended)",
"common_failures": "Vague topic like 'make a PPT' leads to multiple rework cycles; Missing audience info causes style mismatch; No reference materials results in generic content",
"recommendation": "Always confirm topic and audience before starting; Collect 2-3 reference materials for better quality",
"overview": "## Skill Info\\n- **Name**: create_presentation\\n- **Type**: workflow\\n\\n## Statistics\\n- **Success Rate**: 85%\\n\\n## Skill Memory Context\\n- Best for: Slide creation with clear topic and audience\\n- Recommended flow: Confirm → Collect → Outline → Create → Refine\\n- Key dependencies: Clear topic; target audience; reference materials\\n- Common failures: Vague topic; missing audience; no references\\n- Recommendation: Confirm topic/audience first; collect reference materials",
"content": "## Guidelines\\n\\n### Preparation\\n1. Confirm topic (e.g., 'Q3 Sales Report', 'Python Basics Tutorial')\\n2. Identify audience (e.g., executives, beginners, engineers)\\n3. Collect 2-3 reference materials or examples\\n\\n### Good Cases\\n- Topic: 'Python asyncio tutorial for beginners' + Audience: 'developers new to async' → Collected official docs first, generated 10 slides with code examples, completed in 90s\\n- Topic: 'Q3 project update' + Audience: 'stakeholders' → Gathered metrics first, created data-driven slides, positive feedback\\n\\n### Bad Cases\\n- Request: 'Make a PPT' (no topic specified) → Generated generic content, required 3 rounds of rework\\n- Request: 'Create presentation' (no audience) → Style mismatch, had to regenerate for different audience"
}
```

Expand All @@ -365,8 +374,14 @@ template: |
"abstract": "Merge types use `[Merge key]: [Description]`, independent types use specific description",
"overview": "Structured Markdown, use different heading templates by category",
"content": "Free Markdown, complete narrative",
"tool_name": "Only for tools category: the tool name from input [ToolCall] record (must copy exactly)",
"skill_name": "Only for skills category: inferred from full conversation context (descriptive name for the skill/workflow)"
"tool_name": "[REQUIRED for tools] The tool name from [ToolCall] record - MUST copy exactly, no modification",
"skill_name": "[REQUIRED for skills] The skill name - copy from [ToolCall] skill_name if present, otherwise infer from context",
"best_for": "Optional: tools/skills only, what this tool/skill is best used for",
"optimal_params": "Optional: tools only, GENERAL parameter ranges/best practices (NOT specific case values)",
"recommended_flow": "Optional: skills only, recommended execution flow",
"key_dependencies": "Optional: skills only, prerequisites/inputs needed",
"common_failures": "Optional: tools/skills only, common failure patterns",
"recommendation": "Optional: tools/skills only, short actionable recommendations"
}
]
}
Expand All @@ -376,8 +391,11 @@ template: |
- Only extract truly valuable personalized information
- If nothing worth recording, return {"memories": []}
- For preferences, keep each memory as one independently updatable facet; do not combine unrelated facets in one memory
- For tools category: tool_name MUST be copied from [ToolCall] tool_name field exactly, do NOT modify it
- For skills category: skill_name should be inferred from the full conversation context (skill may involve internal tools not shown in [ToolCall]). Use a descriptive name that represents the skill/workflow.
- **CRITICAL for tools category**: "tool_name" is REQUIRED. You MUST copy the exact tool_name value from the [ToolCall] record. Do NOT omit this field. Do NOT modify the name.
- **CRITICAL for skills category**: "skill_name" is REQUIRED. If [ToolCall] contains skill_name, copy it exactly. If not present, infer a descriptive name from context. Do NOT omit this field.
- **CRITICAL for tools/skills content field**: The "content" field MUST include structured sections with EXACT English headings: `## Guidelines` (with best practices), `### Good Cases` (successful usage examples), and `### Bad Cases` (failed usage examples). The section content can be in {{ output_language }}, but the headings MUST remain in English.
- For tools/skills category: Fill "best_for/recommended_flow/key_dependencies/common_failures/recommendation" based on observed usage patterns. Infer reasonable values from the tool/skill nature when direct evidence is limited.
- For tools category: "optimal_params" should describe GENERAL best practices (e.g., "max_results=5-20", "timeout>30s for large files"), NOT specific case values (e.g., "command: 'echo hello'", "file: '/path/to/specific/file'").

llm_config:
temperature: 0.0
87 changes: 44 additions & 43 deletions openviking/session/compressor.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,10 @@ async def extract_long_term_memories(
viking_fs = get_viking_fs()

tool_parts = self._extract_tool_parts(messages)
from .tool_skill_utils import collect_skill_stats, collect_tool_stats

tool_stats_map = collect_tool_stats(tool_parts)
skill_stats_map = collect_skill_stats(tool_parts)

for candidate in candidates:
# Profile: skip dedup, always merge
Expand All @@ -176,6 +180,31 @@ async def extract_long_term_memories(
candidate, tool_parts
)
candidate.tool_status = tool_status
if tool_name:
candidate.tool_name = tool_name
if skill_name:
candidate.skill_name = skill_name

if tool_name and candidate.call_time == 0:
tool_stats = tool_stats_map.get(tool_name, {})
candidate.call_time = tool_stats.get("call_count", candidate.call_time)
candidate.success_time = tool_stats.get(
"success_time", candidate.success_time
)
candidate.duration_ms = tool_stats.get("duration_ms", candidate.duration_ms)
candidate.prompt_tokens = tool_stats.get(
"prompt_tokens", candidate.prompt_tokens
)
candidate.completion_tokens = tool_stats.get(
"completion_tokens", candidate.completion_tokens
)

if skill_name and candidate.call_time == 0:
skill_stats = skill_stats_map.get(skill_name, {})
candidate.call_time = skill_stats.get("call_count", candidate.call_time)
candidate.success_time = skill_stats.get(
"success_time", candidate.success_time
)
if skill_name:
memory = await self.extractor._merge_skill_memory(
skill_name, candidate, ctx=ctx
Expand Down Expand Up @@ -292,43 +321,22 @@ def _get_tool_skill_info(
Returns:
(tool_name, skill_name, tool_status) tuple
"""
candidate_tool = candidate.tool_name
candidate_skill = candidate.skill_name

calibrated_tool = ""
calibrated_skill = ""
tool_status = "completed"

for part in tool_parts:
if part.skill_uri:
part_skill_name = self._extract_skill_name_from_uri(part.skill_uri)
if candidate_skill:
if self._is_similar_name(candidate_skill, part_skill_name):
calibrated_skill = part_skill_name
tool_status = part.tool_status or "completed"
else:
calibrated_skill = candidate_skill
else:
calibrated_skill = part_skill_name
from .tool_skill_utils import calibrate_skill_name, calibrate_tool_name

if candidate.category == MemoryCategory.TOOLS:
candidate_tool = (candidate.tool_name or "").strip()
if not candidate_tool:
return ("", "", "completed")
calibrated_name, status = calibrate_tool_name(candidate_tool, tool_parts)
return (calibrated_name, "", status)

if candidate.category == MemoryCategory.SKILLS:
candidate_skill = (candidate.skill_name or "").strip()
if not candidate_skill:
return ("", "", "completed")
calibrated_name, status = calibrate_skill_name(candidate_skill, tool_parts)
return ("", calibrated_name, status)

elif part.tool_name:
if candidate_tool:
if self._is_similar_name(candidate_tool, part.tool_name):
calibrated_tool = part.tool_name
tool_status = part.tool_status or "completed"
else:
calibrated_tool = candidate_tool
else:
calibrated_tool = part.tool_name

if calibrated_skill:
return ("", calibrated_skill, tool_status)
if calibrated_tool:
return (calibrated_tool, "", tool_status)
if candidate_skill:
return ("", candidate_skill, "completed")
if candidate_tool:
return (candidate_tool, "", "completed")
return ("", "", "completed")

def _is_similar_name(self, name1: str, name2: str) -> bool:
Expand All @@ -353,13 +361,6 @@ def _is_similar_name(self, name1: str, name2: str) -> bool:
ratio = SequenceMatcher(None, n1, n2).ratio()
return ratio >= 0.7

def _extract_skill_name_from_uri(self, skill_uri: str) -> str:
"""从 skill_uri 提取 skill_name

例如: viking://agent/skills/create_presentation -> create_presentation
"""
return skill_uri.rstrip("/").split("/")[-1]

def _extract_used_uris(self, messages: List[Message]) -> Dict[str, List[str]]:
"""Extract URIs used in messages."""
uris = {"memories": set(), "resources": set(), "skills": set()}
Expand Down
Loading
Loading