mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
TOON Optimizations
This commit is contained in:
+14
-1
@@ -36,6 +36,7 @@ dependencies = [
|
|||||||
"anthropic",
|
"anthropic",
|
||||||
"openai", # For embeddings
|
"openai", # For embeddings
|
||||||
"tiktoken", # Token counting
|
"tiktoken", # Token counting
|
||||||
|
"python-toon", # Token-efficient LLM serialization
|
||||||
|
|
||||||
# MCP (Model Context Protocol)
|
# MCP (Model Context Protocol)
|
||||||
"mcp",
|
"mcp",
|
||||||
@@ -120,11 +121,23 @@ select = [
|
|||||||
"SIM", # flake8-simplify
|
"SIM", # flake8-simplify
|
||||||
"TCH", # flake8-type-checking
|
"TCH", # flake8-type-checking
|
||||||
"PTH", # flake8-use-pathlib
|
"PTH", # flake8-use-pathlib
|
||||||
"ERA", # eradicate (commented code)
|
|
||||||
"PL", # Pylint
|
"PL", # Pylint
|
||||||
"RUF", # Ruff-specific
|
"RUF", # Ruff-specific
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# MCP servers: Tool functions require explicit parameters for schema generation.
|
||||||
|
# PLR0913 - MCP tools need many params (no dataclass workaround)
|
||||||
|
# PLR0915 - Factory functions define all tools (unavoidable nesting)
|
||||||
|
# PLC0415 - Lazy imports for circular import avoidance
|
||||||
|
# E501 - Docstrings and guidance messages
|
||||||
|
#
|
||||||
|
# Services: Internal methods often need multiple related parameters for DB ops.
|
||||||
|
# PLR0913 - Service methods wrapping DB/external calls need multiple params
|
||||||
|
# PLC0415 - Lazy imports for circular import avoidance
|
||||||
|
[tool.ruff.lint.per-file-ignores]
|
||||||
|
"roboco/mcp/*.py" = ["PLR0913", "PLR0915", "PLC0415", "E501"]
|
||||||
|
"roboco/services/*.py" = ["PLC0415", "PLR0913"]
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# MyPy Configuration
|
# MyPy Configuration
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ from pydantic import BaseModel, Field
|
|||||||
|
|
||||||
from roboco.api.websocket import broadcast_agent_chunk
|
from roboco.api.websocket import broadcast_agent_chunk
|
||||||
from roboco.config import settings
|
from roboco.config import settings
|
||||||
|
from roboco.llm import ToonAdapter
|
||||||
from roboco.models import AgentRole, AgentStatus, Team
|
from roboco.models import AgentRole, AgentStatus, Team
|
||||||
|
|
||||||
logger = structlog.get_logger()
|
logger = structlog.get_logger()
|
||||||
@@ -113,6 +114,7 @@ class Agent(ABC):
|
|||||||
self._running = False
|
self._running = False
|
||||||
self._task: asyncio.Task | None = None
|
self._task: asyncio.Task | None = None
|
||||||
self._llm_client: AsyncAnthropic | None = None
|
self._llm_client: AsyncAnthropic | None = None
|
||||||
|
self._toon = ToonAdapter()
|
||||||
|
|
||||||
self.log = logger.bind(
|
self.log = logger.bind(
|
||||||
agent_id=str(config.id),
|
agent_id=str(config.id),
|
||||||
@@ -157,6 +159,52 @@ class Agent(ABC):
|
|||||||
self._llm_client = AsyncAnthropic(api_key=settings.anthropic_api_key)
|
self._llm_client = AsyncAnthropic(api_key=settings.anthropic_api_key)
|
||||||
return self._llm_client
|
return self._llm_client
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# TOON SERIALIZATION (for token-efficient LLM communication)
|
||||||
|
# =========================================================================
|
||||||
|
|
||||||
|
def format_context(self, data: dict[str, Any]) -> str:
|
||||||
|
"""
|
||||||
|
Format context data for LLM using TOON.
|
||||||
|
|
||||||
|
TOON (Token-Oriented Object Notation) reduces token consumption
|
||||||
|
by 30-60% compared to JSON while maintaining semantic clarity.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data: Dictionary to encode for LLM prompt.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
TOON-formatted string.
|
||||||
|
"""
|
||||||
|
return self._toon.encode(data)
|
||||||
|
|
||||||
|
def format_context_labeled(self, label: str, data: dict[str, Any]) -> str:
|
||||||
|
"""
|
||||||
|
Format labeled context data for embedding in prompts.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
label: Section label (e.g., "Task Context").
|
||||||
|
data: Dictionary to encode.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Labeled TOON-formatted string.
|
||||||
|
"""
|
||||||
|
return self._toon.format_for_prompt(label, data)
|
||||||
|
|
||||||
|
def parse_llm_response(self, response: str) -> dict[str, Any] | list[Any]:
|
||||||
|
"""
|
||||||
|
Parse structured data from LLM response.
|
||||||
|
|
||||||
|
Attempts TOON parsing first, falls back to JSON.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
response: Raw LLM response text.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Parsed Python dict or list.
|
||||||
|
"""
|
||||||
|
return self._toon.decode(response)
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
# LIFECYCLE METHODS
|
# LIFECYCLE METHODS
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
|
|||||||
+23
-21
@@ -153,17 +153,20 @@ class ProductOwnerAgent(Agent):
|
|||||||
result = await self._api_call("GET", f"/tasks/{task_id}")
|
result = await self._api_call("GET", f"/tasks/{task_id}")
|
||||||
acceptance_criteria = result.get("acceptance_criteria", [])
|
acceptance_criteria = result.get("acceptance_criteria", [])
|
||||||
|
|
||||||
# Use LLM to check if criteria are met
|
# Use TOON for token-efficient context encoding
|
||||||
prompt = f"""
|
task_context = self.format_context_labeled(
|
||||||
Review this completed feature against its acceptance criteria:
|
"Feature Review",
|
||||||
|
{
|
||||||
|
"title": result.get("title", "Unknown"),
|
||||||
|
"description": result.get("description", "No description"),
|
||||||
|
"acceptance_criteria": acceptance_criteria,
|
||||||
|
"dev_notes": result.get("dev_notes", "None"),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
Task: {result.get("title", "Unknown")}
|
prompt = f"""Review this completed feature against its acceptance criteria:
|
||||||
Description: {result.get("description", "No description")}
|
|
||||||
|
|
||||||
Acceptance Criteria:
|
{task_context}
|
||||||
{chr(10).join(f"- {c}" for c in acceptance_criteria)}
|
|
||||||
|
|
||||||
Dev Notes: {result.get("dev_notes", "None")}
|
|
||||||
|
|
||||||
Determine if all criteria are met. Respond with:
|
Determine if all criteria are met. Respond with:
|
||||||
ACCEPTED: [reason] or NEEDS_CHANGES: [what's missing]
|
ACCEPTED: [reason] or NEEDS_CHANGES: [what's missing]
|
||||||
@@ -465,11 +468,15 @@ class AuditorAgent(Agent):
|
|||||||
if not self._observations:
|
if not self._observations:
|
||||||
return
|
return
|
||||||
|
|
||||||
prompt = f"""
|
# Use TOON for token-efficient context encoding
|
||||||
Analyze these observations for quality and efficiency issues:
|
observations_context = self.format_context_labeled(
|
||||||
|
"Observations",
|
||||||
|
{"recent": self._observations[-50:]},
|
||||||
|
)
|
||||||
|
|
||||||
Observations:
|
prompt = f"""Analyze these observations for quality and efficiency issues:
|
||||||
{chr(10).join(str(o) for o in self._observations[-50:])}
|
|
||||||
|
{observations_context}
|
||||||
|
|
||||||
Look for:
|
Look for:
|
||||||
1. Efficiency issues - wasted effort, unclear processes
|
1. Efficiency issues - wasted effort, unclear processes
|
||||||
@@ -478,14 +485,9 @@ Look for:
|
|||||||
4. Process violations - skipping QA, missing documentation
|
4. Process violations - skipping QA, missing documentation
|
||||||
5. Team health - frustration, conflicts
|
5. Team health - frustration, conflicts
|
||||||
|
|
||||||
For each issue found, provide:
|
Format response as TOON tabular:
|
||||||
- Category
|
[N,]{{category,severity,description,evidence,recommendation}}:
|
||||||
- Severity (info/warning/concern/critical)
|
efficiency,warning,Unclear handoff process,3 tasks delayed,Document handoff steps
|
||||||
- Description
|
|
||||||
- Evidence
|
|
||||||
- Recommendation
|
|
||||||
|
|
||||||
Be thorough but fair.
|
|
||||||
"""
|
"""
|
||||||
analysis = await self.think(prompt)
|
analysis = await self.think(prompt)
|
||||||
self.log.info("Analysis complete", analysis_length=len(analysis))
|
self.log.info("Analysis complete", analysis_length=len(analysis))
|
||||||
|
|||||||
+57
-24
@@ -249,13 +249,16 @@ class DeveloperAgent(Agent):
|
|||||||
# Read task requirements
|
# Read task requirements
|
||||||
requirements = await self._read_task_requirements(ctx.task_id)
|
requirements = await self._read_task_requirements(ctx.task_id)
|
||||||
|
|
||||||
# Use LLM to understand and identify gaps
|
# Format context using TOON for token efficiency
|
||||||
prompt = f"""
|
task_context = self.format_context_labeled(
|
||||||
You are analyzing a task before beginning work.
|
"Task Context",
|
||||||
|
{"title": ctx.title, "requirements": requirements},
|
||||||
|
)
|
||||||
|
|
||||||
Task: {ctx.title}
|
# Use LLM to understand and identify gaps
|
||||||
Requirements:
|
prompt = f"""You are analyzing a task before beginning work.
|
||||||
{requirements}
|
|
||||||
|
{task_context}
|
||||||
|
|
||||||
Analyze:
|
Analyze:
|
||||||
1. What exactly needs to be done?
|
1. What exactly needs to be done?
|
||||||
@@ -293,28 +296,48 @@ If clarification needed, respond with: "QUESTION: [your question]"
|
|||||||
"""
|
"""
|
||||||
self.log.info("PLAN phase", task_id=str(ctx.task_id))
|
self.log.info("PLAN phase", task_id=str(ctx.task_id))
|
||||||
|
|
||||||
# Use LLM to create plan
|
# Format context using TOON
|
||||||
prompt = f"""
|
plan_context = self.format_context_labeled(
|
||||||
Create an implementation plan for this task:
|
"Task",
|
||||||
|
{
|
||||||
|
"title": ctx.title,
|
||||||
|
"understanding": ctx.journal_entries[-1]
|
||||||
|
if ctx.journal_entries
|
||||||
|
else "No context",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
Task: {ctx.title}
|
# Use LLM to create plan - request TOON tabular response
|
||||||
Understanding: {ctx.journal_entries[-1] if ctx.journal_entries else "No context"}
|
prompt = f"""Create an implementation plan for this task:
|
||||||
|
|
||||||
Break this into ordered subtasks. For each subtask:
|
{plan_context}
|
||||||
|
|
||||||
|
Break this into ordered subtasks. For each subtask provide:
|
||||||
- Clear description
|
- Clear description
|
||||||
- Files to modify
|
- Files to modify
|
||||||
- Estimated complexity (small/medium/large)
|
- Estimated complexity (small/medium/large)
|
||||||
|
|
||||||
Format as JSON array:
|
Format response as TOON tabular:
|
||||||
[
|
[N,]{{description,files,complexity}}:
|
||||||
{{"description": "...", "files": ["..."], "complexity": "small|medium|large"}},
|
Implement the main logic,src/main.py|src/utils.py,medium
|
||||||
...
|
Add unit tests,tests/test_main.py,small
|
||||||
]
|
|
||||||
"""
|
"""
|
||||||
response = await self.think(prompt)
|
response = await self.think(prompt)
|
||||||
|
|
||||||
# Parse subtasks (simplified - would use proper JSON parsing)
|
# Parse subtasks using TOON (falls back to JSON)
|
||||||
ctx.subtasks = [{"description": response, "files": [], "complexity": "medium"}]
|
try:
|
||||||
|
subtasks = self.parse_llm_response(response)
|
||||||
|
if isinstance(subtasks, list):
|
||||||
|
ctx.subtasks = subtasks
|
||||||
|
else:
|
||||||
|
ctx.subtasks = [
|
||||||
|
{"description": response, "files": [], "complexity": "medium"}
|
||||||
|
]
|
||||||
|
except ValueError:
|
||||||
|
# Fallback if parsing fails
|
||||||
|
ctx.subtasks = [
|
||||||
|
{"description": response, "files": [], "complexity": "medium"}
|
||||||
|
]
|
||||||
|
|
||||||
# Journal entry
|
# Journal entry
|
||||||
ts = datetime.now(UTC).isoformat()
|
ts = datetime.now(UTC).isoformat()
|
||||||
@@ -349,12 +372,22 @@ Format as JSON array:
|
|||||||
|
|
||||||
subtask = ctx.subtasks[ctx.current_subtask]
|
subtask = ctx.subtasks[ctx.current_subtask]
|
||||||
|
|
||||||
# Use LLM to work on subtask
|
# Format context using TOON
|
||||||
prompt = f"""
|
execute_context = self.format_context_labeled(
|
||||||
Execute this subtask:
|
"Execution Context",
|
||||||
|
{
|
||||||
|
"task": ctx.title,
|
||||||
|
"subtask_number": ctx.current_subtask + 1,
|
||||||
|
"total_subtasks": len(ctx.subtasks),
|
||||||
|
"description": subtask.get("description", ""),
|
||||||
|
"files": subtask.get("files", []),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
Task: {ctx.title}
|
# Use LLM to work on subtask
|
||||||
Subtask {ctx.current_subtask + 1}/{len(ctx.subtasks)}: {subtask.get("description", "")}
|
prompt = f"""Execute this subtask:
|
||||||
|
|
||||||
|
{execute_context}
|
||||||
|
|
||||||
Provide:
|
Provide:
|
||||||
1. Code changes needed
|
1. Code changes needed
|
||||||
|
|||||||
+27
-18
@@ -321,19 +321,21 @@ Respond with structured analysis.
|
|||||||
|
|
||||||
doc_spec = ctx.documents_needed[ctx.current_doc]
|
doc_spec = ctx.documents_needed[ctx.current_doc]
|
||||||
|
|
||||||
# Use LLM to write documentation
|
# Use TOON for token-efficient context encoding
|
||||||
prompt = f"""
|
doc_context = self.format_context_labeled(
|
||||||
Write documentation for this task.
|
"Documentation Task",
|
||||||
|
{
|
||||||
|
"title": ctx.title,
|
||||||
|
"doc_type": doc_spec.doc_type.value,
|
||||||
|
"target_path": doc_spec.path,
|
||||||
|
"summary": ctx.summary,
|
||||||
|
"dev_notes": ctx.dev_notes or "None",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
Task: {ctx.title}
|
prompt = f"""Write documentation for this task.
|
||||||
Document Type: {doc_spec.doc_type.value}
|
|
||||||
Target Path: {doc_spec.path}
|
|
||||||
|
|
||||||
Summary:
|
{doc_context}
|
||||||
{ctx.summary}
|
|
||||||
|
|
||||||
Developer Notes:
|
|
||||||
{ctx.dev_notes or "None"}
|
|
||||||
|
|
||||||
Write professional, clear documentation following best practices.
|
Write professional, clear documentation following best practices.
|
||||||
Include:
|
Include:
|
||||||
@@ -373,14 +375,19 @@ Format appropriately for the document type.
|
|||||||
if not doc_spec.content:
|
if not doc_spec.content:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
prompt = f"""
|
# Use TOON for token-efficient context encoding
|
||||||
Review this documentation for quality:
|
review_context = self.format_context_labeled(
|
||||||
|
"Document Review",
|
||||||
|
{
|
||||||
|
"title": doc_spec.title,
|
||||||
|
"doc_type": doc_spec.doc_type.value,
|
||||||
|
"content": doc_spec.content,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
Document: {doc_spec.title}
|
prompt = f"""Review this documentation for quality:
|
||||||
Type: {doc_spec.doc_type.value}
|
|
||||||
|
|
||||||
Content:
|
{review_context}
|
||||||
{doc_spec.content}
|
|
||||||
|
|
||||||
Check:
|
Check:
|
||||||
1. Accuracy - Does it correctly describe the feature?
|
1. Accuracy - Does it correctly describe the feature?
|
||||||
@@ -388,7 +395,9 @@ Check:
|
|||||||
3. Clarity - Is it easy to understand?
|
3. Clarity - Is it easy to understand?
|
||||||
4. Examples - Are examples helpful and correct?
|
4. Examples - Are examples helpful and correct?
|
||||||
|
|
||||||
If issues found, provide suggestions.
|
Format response as TOON:
|
||||||
|
{{accuracy,completeness,clarity,examples,suggestions}}:
|
||||||
|
good,complete,clear,helpful,None
|
||||||
"""
|
"""
|
||||||
review = await self.think(prompt)
|
review = await self.think(prompt)
|
||||||
ts = datetime.now(UTC).isoformat()
|
ts = datetime.now(UTC).isoformat()
|
||||||
|
|||||||
+51
-28
@@ -199,12 +199,15 @@ class CellPMAgent(Agent):
|
|||||||
new_tasks = await self._get_unassigned_tasks()
|
new_tasks = await self._get_unassigned_tasks()
|
||||||
|
|
||||||
for task_id in new_tasks:
|
for task_id in new_tasks:
|
||||||
# Assess complexity and priority
|
# Use TOON for token-efficient context encoding
|
||||||
prompt = f"""
|
triage_context = self.format_context_labeled(
|
||||||
Assess this task for prioritization:
|
"Task Triage",
|
||||||
|
{"task_id": str(task_id), "cell": self.cell_name},
|
||||||
|
)
|
||||||
|
|
||||||
Task ID: {task_id}
|
prompt = f"""Assess this task for prioritization:
|
||||||
Cell: {self.cell_name}
|
|
||||||
|
{triage_context}
|
||||||
|
|
||||||
Consider:
|
Consider:
|
||||||
1. Complexity (low/medium/high)
|
1. Complexity (low/medium/high)
|
||||||
@@ -212,7 +215,9 @@ Consider:
|
|||||||
3. Priority (P0-P3)
|
3. Priority (P0-P3)
|
||||||
4. Best dev fit based on skills
|
4. Best dev fit based on skills
|
||||||
|
|
||||||
Provide assessment.
|
Format response as TOON:
|
||||||
|
{{complexity,dependencies,priority,dev_fit}}:
|
||||||
|
medium,TASK-abc123,P1,backend-dev-1
|
||||||
"""
|
"""
|
||||||
assessment = await self.think(prompt)
|
assessment = await self.think(prompt)
|
||||||
self.log.info(
|
self.log.info(
|
||||||
@@ -249,11 +254,15 @@ Provide assessment.
|
|||||||
questions = await self._get_pending_questions()
|
questions = await self._get_pending_questions()
|
||||||
|
|
||||||
for question in questions:
|
for question in questions:
|
||||||
# Try to answer or route appropriately
|
# Use TOON for token-efficient context encoding
|
||||||
prompt = f"""
|
question_context = self.format_context_labeled(
|
||||||
A cell member has a question:
|
"Cell Question",
|
||||||
|
{"question": question, "cell": self.cell_name},
|
||||||
|
)
|
||||||
|
|
||||||
{question}
|
prompt = f"""A cell member needs help:
|
||||||
|
|
||||||
|
{question_context}
|
||||||
|
|
||||||
As the Cell PM, provide:
|
As the Cell PM, provide:
|
||||||
1. Answer if you can
|
1. Answer if you can
|
||||||
@@ -610,23 +619,29 @@ class MainPMAgent(Agent):
|
|||||||
self.log.debug("PRIORITIZE phase")
|
self.log.debug("PRIORITIZE phase")
|
||||||
|
|
||||||
if self._board_directives:
|
if self._board_directives:
|
||||||
directives = chr(10).join(f"- {d}" for d in self._board_directives)
|
# Build status data for TOON encoding
|
||||||
status_lines = []
|
cell_status_data = {
|
||||||
for k, v in self._cell_statuses.items():
|
name: {"active": s.active_tasks, "blocked": s.blocked_tasks}
|
||||||
active = v.active_tasks
|
for name, s in self._cell_statuses.items()
|
||||||
blocked = v.blocked_tasks
|
}
|
||||||
status_lines.append(f"- {k}: {active} active, {blocked} blocked")
|
|
||||||
cell_status = chr(10).join(status_lines)
|
|
||||||
prompt = f"""
|
|
||||||
Translate these Board directives into cell priorities:
|
|
||||||
|
|
||||||
Directives:
|
# Use TOON for token-efficient context encoding
|
||||||
{directives}
|
priority_context = self.format_context_labeled(
|
||||||
|
"Prioritization Context",
|
||||||
|
{
|
||||||
|
"directives": self._board_directives,
|
||||||
|
"cell_status": cell_status_data,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
Current Cell Status:
|
prompt = f"""Translate these Board directives into cell priorities:
|
||||||
{cell_status}
|
|
||||||
|
|
||||||
Provide prioritized task list for each cell.
|
{priority_context}
|
||||||
|
|
||||||
|
Format response as TOON tabular:
|
||||||
|
[N,]{{cell,priority,task_description}}:
|
||||||
|
backend-cell,P0,Implement critical auth fix
|
||||||
|
frontend-cell,P1,Update dashboard layout
|
||||||
"""
|
"""
|
||||||
priorities = await self.think(prompt)
|
priorities = await self.think(prompt)
|
||||||
self.log.info("Priorities set", priorities=priorities[:200])
|
self.log.info("Priorities set", priorities=priorities[:200])
|
||||||
@@ -636,11 +651,19 @@ Provide prioritized task list for each cell.
|
|||||||
self.log.debug("COORDINATE phase")
|
self.log.debug("COORDINATE phase")
|
||||||
|
|
||||||
for issue in self._cross_cell_issues:
|
for issue in self._cross_cell_issues:
|
||||||
prompt = f"""
|
# Use TOON for token-efficient context encoding
|
||||||
Resolve this cross-cell issue:
|
issue_context = self.format_context_labeled(
|
||||||
|
"Cross-Cell Issue",
|
||||||
|
{
|
||||||
|
"description": issue.get("description"),
|
||||||
|
"cells": issue.get("cells"),
|
||||||
|
"task_id": issue.get("task_id"),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
Issue: {issue.get("description")}
|
prompt = f"""Resolve this cross-cell issue:
|
||||||
Cells Involved: {issue.get("cells")}
|
|
||||||
|
{issue_context}
|
||||||
|
|
||||||
Propose a resolution that unblocks all parties.
|
Propose a resolution that unblocks all parties.
|
||||||
"""
|
"""
|
||||||
|
|||||||
+33
-34
@@ -211,27 +211,22 @@ class QAAgent(Agent):
|
|||||||
dev_notes = await self._read_dev_notes(ctx.task_id)
|
dev_notes = await self._read_dev_notes(ctx.task_id)
|
||||||
commits = await self._get_task_commits(ctx.task_id)
|
commits = await self._get_task_commits(ctx.task_id)
|
||||||
|
|
||||||
# Use LLM to understand and create test plan
|
# Use TOON for token-efficient context encoding
|
||||||
prompt = f"""
|
task_context = self.format_context_labeled(
|
||||||
You are a QA engineer reviewing a completed task.
|
"QA Review Context",
|
||||||
|
{
|
||||||
|
"title": ctx.title,
|
||||||
|
"requirements": requirements,
|
||||||
|
"dev_notes": dev_notes,
|
||||||
|
"commits": commits,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
Task: {ctx.title}
|
prompt = f"""You are a QA engineer reviewing a completed task.
|
||||||
|
|
||||||
Requirements:
|
{task_context}
|
||||||
{requirements}
|
|
||||||
|
|
||||||
Developer Notes:
|
|
||||||
{dev_notes}
|
|
||||||
|
|
||||||
Commits:
|
|
||||||
{commits}
|
|
||||||
|
|
||||||
Based on this, create test cases to verify the implementation.
|
Based on this, create test cases to verify the implementation.
|
||||||
For each test case provide:
|
|
||||||
1. Name
|
|
||||||
2. What to test
|
|
||||||
3. Steps to execute
|
|
||||||
4. Expected outcome
|
|
||||||
|
|
||||||
Focus on:
|
Focus on:
|
||||||
- Acceptance criteria verification
|
- Acceptance criteria verification
|
||||||
@@ -239,8 +234,10 @@ Focus on:
|
|||||||
- Integration points
|
- Integration points
|
||||||
- Error handling
|
- Error handling
|
||||||
|
|
||||||
Format as JSON array.
|
Format response as TOON tabular:
|
||||||
"""
|
[N,]{{name,description,steps,expected}}:
|
||||||
|
Acceptance Criteria,Verify all criteria met,Review implementation|Check each criterion,All criteria satisfied
|
||||||
|
""" # noqa: E501
|
||||||
_response = await self.think(prompt) # Response informs test case structure
|
_response = await self.think(prompt) # Response informs test case structure
|
||||||
|
|
||||||
# Create test cases (simplified parsing)
|
# Create test cases (simplified parsing)
|
||||||
@@ -289,24 +286,26 @@ Format as JSON array.
|
|||||||
|
|
||||||
test_case = ctx.test_cases[ctx.current_test]
|
test_case = ctx.test_cases[ctx.current_test]
|
||||||
|
|
||||||
# Use LLM to execute test
|
# Use TOON for token-efficient context encoding
|
||||||
prompt = f"""
|
test_context = self.format_context_labeled(
|
||||||
Execute this test case:
|
"Test Case",
|
||||||
|
{
|
||||||
|
"name": test_case.name,
|
||||||
|
"description": test_case.description,
|
||||||
|
"steps": test_case.steps,
|
||||||
|
"expected": test_case.expected,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
Test: {test_case.name}
|
prompt = f"""Execute this test case:
|
||||||
Description: {test_case.description}
|
|
||||||
Steps: {", ".join(test_case.steps)}
|
|
||||||
Expected: {test_case.expected}
|
|
||||||
|
|
||||||
Simulate executing this test and provide:
|
{test_context}
|
||||||
1. RESULT: PASS or FAIL
|
|
||||||
2. ACTUAL: What was observed
|
|
||||||
3. NOTES: Any additional findings
|
|
||||||
|
|
||||||
Format:
|
Simulate executing this test and provide results.
|
||||||
RESULT: [PASS|FAIL]
|
|
||||||
ACTUAL: [observation]
|
Format response as TOON:
|
||||||
NOTES: [notes]
|
{{result,actual,notes}}:
|
||||||
|
PASS,All criteria verified successfully,No issues found
|
||||||
"""
|
"""
|
||||||
response = await self.think(prompt)
|
response = await self.think(prompt)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
"""
|
||||||
|
LLM Communication Layer
|
||||||
|
|
||||||
|
Provides utilities for efficient communication with Large Language Models,
|
||||||
|
including TOON serialization for token-efficient data transfer.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from roboco.llm.metrics import ToonMetrics
|
||||||
|
from roboco.llm.toon_adapter import ToonAdapter, ToonConfig
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"ToonAdapter",
|
||||||
|
"ToonConfig",
|
||||||
|
"ToonMetrics",
|
||||||
|
]
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
"""
|
||||||
|
TOON Metrics
|
||||||
|
|
||||||
|
Tracks token savings and usage statistics for TOON vs JSON serialization.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from datetime import UTC, datetime
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ToonMetrics:
|
||||||
|
"""
|
||||||
|
Metrics for tracking TOON serialization efficiency.
|
||||||
|
|
||||||
|
Tracks character counts (as proxy for tokens) for JSON vs TOON
|
||||||
|
to measure actual savings in production.
|
||||||
|
"""
|
||||||
|
|
||||||
|
json_chars: int = 0
|
||||||
|
toon_chars: int = 0
|
||||||
|
encode_count: int = 0
|
||||||
|
decode_count: int = 0
|
||||||
|
decode_fallback_count: int = 0
|
||||||
|
started_at: datetime = field(default_factory=lambda: datetime.now(UTC))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def savings_percent(self) -> float:
|
||||||
|
"""Calculate percentage of characters saved using TOON."""
|
||||||
|
if self.json_chars == 0:
|
||||||
|
return 0.0
|
||||||
|
return (1 - self.toon_chars / self.json_chars) * 100
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fallback_rate(self) -> float:
|
||||||
|
"""Calculate rate of fallback to JSON decoding."""
|
||||||
|
if self.decode_count == 0:
|
||||||
|
return 0.0
|
||||||
|
return (self.decode_fallback_count / self.decode_count) * 100
|
||||||
|
|
||||||
|
def record_encode(self, json_chars: int, toon_chars: int) -> None:
|
||||||
|
"""Record an encode operation with character counts."""
|
||||||
|
self.json_chars += json_chars
|
||||||
|
self.toon_chars += toon_chars
|
||||||
|
self.encode_count += 1
|
||||||
|
|
||||||
|
def record_decode(self, used_fallback: bool = False) -> None:
|
||||||
|
"""Record a decode operation."""
|
||||||
|
self.decode_count += 1
|
||||||
|
if used_fallback:
|
||||||
|
self.decode_fallback_count += 1
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
"""Convert metrics to dictionary for logging/reporting."""
|
||||||
|
return {
|
||||||
|
"json_chars": self.json_chars,
|
||||||
|
"toon_chars": self.toon_chars,
|
||||||
|
"savings_percent": round(self.savings_percent, 2),
|
||||||
|
"encode_count": self.encode_count,
|
||||||
|
"decode_count": self.decode_count,
|
||||||
|
"fallback_rate": round(self.fallback_rate, 2),
|
||||||
|
"started_at": self.started_at.isoformat(),
|
||||||
|
}
|
||||||
|
|
||||||
|
def reset(self) -> None:
|
||||||
|
"""Reset all metrics."""
|
||||||
|
self.json_chars = 0
|
||||||
|
self.toon_chars = 0
|
||||||
|
self.encode_count = 0
|
||||||
|
self.decode_count = 0
|
||||||
|
self.decode_fallback_count = 0
|
||||||
|
self.started_at = datetime.now(UTC)
|
||||||
|
|
||||||
|
|
||||||
|
# Global metrics holder
|
||||||
|
class _MetricsHolder:
|
||||||
|
"""Holder for singleton ToonMetrics instance."""
|
||||||
|
|
||||||
|
instance: ToonMetrics | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_toon_metrics() -> ToonMetrics:
|
||||||
|
"""Get the global TOON metrics instance."""
|
||||||
|
if _MetricsHolder.instance is None:
|
||||||
|
_MetricsHolder.instance = ToonMetrics()
|
||||||
|
return _MetricsHolder.instance
|
||||||
@@ -0,0 +1,191 @@
|
|||||||
|
"""
|
||||||
|
TOON Adapter
|
||||||
|
|
||||||
|
Provides serialization/deserialization between Python objects and TOON format
|
||||||
|
for token-efficient LLM communication. TOON (Token-Oriented Object Notation)
|
||||||
|
achieves 30-60% fewer tokens than JSON while maintaining semantic clarity.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
adapter = ToonAdapter()
|
||||||
|
|
||||||
|
# Encode data for LLM prompt
|
||||||
|
toon_str = adapter.encode({"name": "Alice", "age": 30})
|
||||||
|
|
||||||
|
# Decode LLM response (falls back to JSON if TOON fails)
|
||||||
|
data = adapter.decode(response_text)
|
||||||
|
|
||||||
|
# Format for embedding in prompt
|
||||||
|
prompt_section = adapter.format_for_prompt("Task Context", task_data)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import structlog
|
||||||
|
import toon
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
logger = structlog.get_logger()
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ToonConfig:
|
||||||
|
"""Configuration for TOON encoding."""
|
||||||
|
|
||||||
|
delimiter: str = ","
|
||||||
|
indent: int = 2
|
||||||
|
include_length: bool = True
|
||||||
|
|
||||||
|
|
||||||
|
class ToonAdapter:
|
||||||
|
"""
|
||||||
|
Adapter for TOON serialization at LLM boundaries.
|
||||||
|
|
||||||
|
Converts Python dicts/Pydantic models to TOON for sending to LLMs,
|
||||||
|
and parses TOON responses back to Python objects. Falls back to
|
||||||
|
JSON parsing if TOON decode fails.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, config: ToonConfig | None = None) -> None:
|
||||||
|
"""
|
||||||
|
Initialize the TOON adapter.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
config: Optional configuration for TOON encoding.
|
||||||
|
"""
|
||||||
|
self.config = config or ToonConfig()
|
||||||
|
self.log = logger.bind(component="toon_adapter")
|
||||||
|
|
||||||
|
def encode(self, data: dict[str, Any] | list[Any] | BaseModel) -> str:
|
||||||
|
"""
|
||||||
|
Convert Python object to TOON for LLM consumption.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data: Dictionary, list, or Pydantic model to encode.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
TOON-formatted string.
|
||||||
|
"""
|
||||||
|
if isinstance(data, BaseModel):
|
||||||
|
data = data.model_dump()
|
||||||
|
|
||||||
|
return toon.encode(data, indent=self.config.indent)
|
||||||
|
|
||||||
|
def decode(self, toon_str: str) -> dict[str, Any] | list[Any]:
|
||||||
|
"""
|
||||||
|
Parse TOON response from LLM.
|
||||||
|
|
||||||
|
Falls back to JSON parsing if TOON decode fails, logging a warning.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
toon_str: TOON-formatted string from LLM response.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Parsed Python dict or list.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If neither TOON nor JSON parsing succeeds.
|
||||||
|
"""
|
||||||
|
# Try TOON first
|
||||||
|
toon_err_msg = ""
|
||||||
|
try:
|
||||||
|
return toon.decode(toon_str)
|
||||||
|
except Exception as toon_error:
|
||||||
|
toon_err_msg = str(toon_error)
|
||||||
|
self.log.warning(
|
||||||
|
"TOON decode failed, trying JSON fallback",
|
||||||
|
error=toon_err_msg,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Fallback to JSON
|
||||||
|
try:
|
||||||
|
return json.loads(toon_str)
|
||||||
|
except json.JSONDecodeError as json_error:
|
||||||
|
self.log.error(
|
||||||
|
"Both TOON and JSON decode failed",
|
||||||
|
toon_error=toon_err_msg,
|
||||||
|
json_error=str(json_error),
|
||||||
|
)
|
||||||
|
raise ValueError(
|
||||||
|
f"Failed to decode response as TOON or JSON: {toon_str[:100]}..."
|
||||||
|
) from json_error
|
||||||
|
|
||||||
|
def format_for_prompt(self, label: str, data: dict[str, Any]) -> str:
|
||||||
|
"""
|
||||||
|
Format data with label for embedding in LLM prompt.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
label: Section label (e.g., "Task Context", "Requirements").
|
||||||
|
data: Data to encode.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Formatted string suitable for prompt inclusion.
|
||||||
|
"""
|
||||||
|
encoded = self.encode(data)
|
||||||
|
return f"{label}:\n{encoded}"
|
||||||
|
|
||||||
|
def format_tabular_request(
|
||||||
|
self,
|
||||||
|
fields: list[str],
|
||||||
|
description: str,
|
||||||
|
example_rows: list[list[str]] | None = None,
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
Format a request for tabular TOON response.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
fields: Column names for the table.
|
||||||
|
description: What the LLM should return.
|
||||||
|
example_rows: Optional example data rows.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Formatted instruction for LLM to return TOON tabular data.
|
||||||
|
"""
|
||||||
|
fields_str = ",".join(fields)
|
||||||
|
header = f"[N,]{{{fields_str}}}:"
|
||||||
|
instruction = f"{description}\n\nFormat response as TOON tabular:\n{header}"
|
||||||
|
|
||||||
|
if example_rows:
|
||||||
|
instruction += "\n"
|
||||||
|
for row in example_rows:
|
||||||
|
instruction += f"{self.config.delimiter.join(row)}\n"
|
||||||
|
|
||||||
|
return instruction
|
||||||
|
|
||||||
|
def estimate_token_savings(
|
||||||
|
self,
|
||||||
|
data: dict[str, Any] | list[Any],
|
||||||
|
) -> tuple[int, int, float]:
|
||||||
|
"""
|
||||||
|
Estimate token savings of TOON vs JSON for given data.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data: Data to compare.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple of (json_chars, toon_chars, savings_percent).
|
||||||
|
"""
|
||||||
|
json_str = json.dumps(data, separators=(",", ":"))
|
||||||
|
toon_str = self.encode(data)
|
||||||
|
|
||||||
|
json_chars = len(json_str)
|
||||||
|
toon_chars = len(toon_str)
|
||||||
|
|
||||||
|
savings = (1 - toon_chars / json_chars) * 100 if json_chars > 0 else 0.0
|
||||||
|
|
||||||
|
return json_chars, toon_chars, savings
|
||||||
|
|
||||||
|
|
||||||
|
# Module-level singleton holder
|
||||||
|
class _AdapterHolder:
|
||||||
|
"""Holder for singleton ToonAdapter instance."""
|
||||||
|
|
||||||
|
instance: ToonAdapter | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_toon_adapter() -> ToonAdapter:
|
||||||
|
"""Get the default TOON adapter singleton."""
|
||||||
|
if _AdapterHolder.instance is None:
|
||||||
|
_AdapterHolder.instance = ToonAdapter()
|
||||||
|
return _AdapterHolder.instance
|
||||||
@@ -21,6 +21,10 @@ from fastapi import status
|
|||||||
from mcp.server.fastmcp import FastMCP
|
from mcp.server.fastmcp import FastMCP
|
||||||
|
|
||||||
from roboco.config import settings
|
from roboco.config import settings
|
||||||
|
from roboco.llm import ToonAdapter
|
||||||
|
|
||||||
|
# Global TOON adapter for encoding journal data
|
||||||
|
_toon = ToonAdapter()
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# HELPER FUNCTIONS
|
# HELPER FUNCTIONS
|
||||||
@@ -141,6 +145,7 @@ def create_journal_mcp_server(agent_id: str) -> FastMCP:
|
|||||||
return {
|
return {
|
||||||
"status": "created",
|
"status": "created",
|
||||||
"entry": entry,
|
"entry": entry,
|
||||||
|
"entry_toon": _toon.encode(entry), # TOON-encoded for LLM token efficiency
|
||||||
"guidance": "Journal entry saved. Use roboco_journal_search to find past entries.",
|
"guidance": "Journal entry saved. Use roboco_journal_search to find past entries.",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ from mcp.server.fastmcp import FastMCP
|
|||||||
|
|
||||||
from roboco.agents_config import CHANNEL_ACCESS
|
from roboco.agents_config import CHANNEL_ACCESS
|
||||||
from roboco.config import settings
|
from roboco.config import settings
|
||||||
|
from roboco.llm import ToonAdapter
|
||||||
|
|
||||||
|
# Global TOON adapter for encoding message data
|
||||||
|
_toon = ToonAdapter()
|
||||||
|
|
||||||
|
|
||||||
def _check_channel_access(agent_id: str, channel_slug: str, action: str) -> bool:
|
def _check_channel_access(agent_id: str, channel_slug: str, action: str) -> bool:
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ from fastapi import status
|
|||||||
from mcp.server.fastmcp import FastMCP
|
from mcp.server.fastmcp import FastMCP
|
||||||
|
|
||||||
from roboco.config import settings
|
from roboco.config import settings
|
||||||
|
from roboco.llm import ToonAdapter
|
||||||
|
|
||||||
|
# Global TOON adapter for encoding task data
|
||||||
|
_toon = ToonAdapter()
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# VALID STATE TRANSITIONS
|
# VALID STATE TRANSITIONS
|
||||||
@@ -63,15 +67,25 @@ def _format_task_response(
|
|||||||
guidance: str,
|
guidance: str,
|
||||||
project: dict[str, Any] | None = None,
|
project: dict[str, Any] | None = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Format a standardized task response with guidance."""
|
"""
|
||||||
|
Format a standardized task response with guidance.
|
||||||
|
|
||||||
|
Includes both JSON task data and TOON-encoded version for
|
||||||
|
token-efficient LLM consumption.
|
||||||
|
"""
|
||||||
|
# Encode task data as TOON for token efficiency when LLM processes response
|
||||||
|
task_toon = _toon.encode(task)
|
||||||
|
|
||||||
response = {
|
response = {
|
||||||
"status": task.get("status"),
|
"status": task.get("status"),
|
||||||
"task": task,
|
"task": task,
|
||||||
|
"task_toon": task_toon, # TOON-encoded for LLM token efficiency
|
||||||
"next_step": next_step,
|
"next_step": next_step,
|
||||||
"guidance": guidance,
|
"guidance": guidance,
|
||||||
}
|
}
|
||||||
if project:
|
if project:
|
||||||
response["project"] = project
|
response["project"] = project
|
||||||
|
response["project_toon"] = _toon.encode(project)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+22
-16
@@ -5,6 +5,7 @@ Documenter handoffs contain all the information needed for
|
|||||||
a Documenter to create production documentation from developer work.
|
a Documenter to create production documentation from developer work.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from dataclasses import dataclass, field
|
||||||
from datetime import UTC, datetime
|
from datetime import UTC, datetime
|
||||||
from uuid import UUID, uuid4
|
from uuid import UUID, uuid4
|
||||||
|
|
||||||
@@ -225,24 +226,29 @@ class DocumenterHandoff(TimestampMixin):
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
|
|
||||||
def create_handoff(
|
@dataclass
|
||||||
task_id: UUID,
|
class HandoffParams:
|
||||||
summary: str,
|
"""Parameters for creating a handoff document."""
|
||||||
commits: list[dict[str, str]],
|
|
||||||
dev_notes_location: str,
|
task_id: UUID
|
||||||
new_functionality: list[str] | None = None,
|
summary: str
|
||||||
modified_behavior: list[str] | None = None,
|
commits: list[dict[str, str]]
|
||||||
breaking_changes: list[str] | None = None,
|
dev_notes_location: str
|
||||||
) -> DocumenterHandoff:
|
new_functionality: list[str] = field(default_factory=list)
|
||||||
|
modified_behavior: list[str] = field(default_factory=list)
|
||||||
|
breaking_changes: list[str] = field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
def create_handoff(params: HandoffParams) -> DocumenterHandoff:
|
||||||
"""Create a basic handoff document."""
|
"""Create a basic handoff document."""
|
||||||
handoff = DocumenterHandoff(
|
handoff = DocumenterHandoff(
|
||||||
task_id=task_id,
|
task_id=params.task_id,
|
||||||
summary=summary,
|
summary=params.summary,
|
||||||
commits=commits,
|
commits=params.commits,
|
||||||
dev_notes_location=dev_notes_location,
|
dev_notes_location=params.dev_notes_location,
|
||||||
new_functionality=new_functionality or [],
|
new_functionality=params.new_functionality,
|
||||||
modified_behavior=modified_behavior or [],
|
modified_behavior=params.modified_behavior,
|
||||||
breaking_changes=breaking_changes or [],
|
breaking_changes=params.breaking_changes,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Always add changelog as required
|
# Always add changelog as required
|
||||||
|
|||||||
+117
-91
@@ -5,6 +5,7 @@ Personal agent journals for reflection, growth tracking, and debugging.
|
|||||||
Each agent maintains their own journal with entries tied to tasks and sessions.
|
Each agent maintains their own journal with entries tied to tasks and sessions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from dataclasses import dataclass, field
|
||||||
from datetime import UTC, datetime
|
from datetime import UTC, datetime
|
||||||
from uuid import UUID, uuid4
|
from uuid import UUID, uuid4
|
||||||
|
|
||||||
@@ -114,170 +115,195 @@ class Journal(TimestampMixin):
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
|
|
||||||
def create_task_reflection(
|
@dataclass
|
||||||
journal_id: UUID,
|
class TaskReflectionParams:
|
||||||
task_id: UUID,
|
"""Parameters for creating a task reflection entry."""
|
||||||
title: str,
|
|
||||||
what_done: str,
|
journal_id: UUID
|
||||||
what_learned: str,
|
task_id: UUID
|
||||||
what_struggled: str,
|
title: str
|
||||||
next_steps: list[str],
|
what_done: str
|
||||||
tags: list[str] | None = None,
|
what_learned: str
|
||||||
) -> JournalEntry:
|
what_struggled: str
|
||||||
|
next_steps: list[str]
|
||||||
|
tags: list[str] = field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class DecisionLogParams:
|
||||||
|
"""Parameters for creating a decision log entry."""
|
||||||
|
|
||||||
|
journal_id: UUID
|
||||||
|
title: str
|
||||||
|
context: str
|
||||||
|
options: list[dict[str, str]]
|
||||||
|
chosen: str
|
||||||
|
rationale: str
|
||||||
|
consequences: list[str]
|
||||||
|
task_id: UUID | None = None
|
||||||
|
tags: list[str] = field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class LearningEntryParams:
|
||||||
|
"""Parameters for creating a learning entry."""
|
||||||
|
|
||||||
|
journal_id: UUID
|
||||||
|
title: str
|
||||||
|
what_learned: str
|
||||||
|
how_applied: str | None = None
|
||||||
|
source: str | None = None
|
||||||
|
task_id: UUID | None = None
|
||||||
|
tags: list[str] = field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class StruggleEntryParams:
|
||||||
|
"""Parameters for creating a struggle entry."""
|
||||||
|
|
||||||
|
journal_id: UUID
|
||||||
|
title: str
|
||||||
|
what_struggled: str
|
||||||
|
attempted_solutions: list[str]
|
||||||
|
resolution: str | None = None
|
||||||
|
help_needed: str | None = None
|
||||||
|
task_id: UUID | None = None
|
||||||
|
tags: list[str] = field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class GeneralEntryParams:
|
||||||
|
"""Parameters for creating a general journal entry."""
|
||||||
|
|
||||||
|
journal_id: UUID
|
||||||
|
title: str
|
||||||
|
content: str
|
||||||
|
task_id: UUID | None = None
|
||||||
|
session_id: UUID | None = None
|
||||||
|
tags: list[str] = field(default_factory=list)
|
||||||
|
is_private: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
def create_task_reflection(params: TaskReflectionParams) -> JournalEntry:
|
||||||
"""Create a task reflection entry."""
|
"""Create a task reflection entry."""
|
||||||
content = f"""## What I Did
|
content = f"""## What I Did
|
||||||
{what_done}
|
{params.what_done}
|
||||||
|
|
||||||
## What I Learned
|
## What I Learned
|
||||||
{what_learned}
|
{params.what_learned}
|
||||||
|
|
||||||
## What I Struggled With
|
## What I Struggled With
|
||||||
{what_struggled}
|
{params.what_struggled}
|
||||||
|
|
||||||
## Next Steps
|
## Next Steps
|
||||||
{chr(10).join(f"- [ ] {step}" for step in next_steps)}
|
{chr(10).join(f"- [ ] {step}" for step in params.next_steps)}
|
||||||
"""
|
"""
|
||||||
return JournalEntry(
|
return JournalEntry(
|
||||||
journal_id=journal_id,
|
journal_id=params.journal_id,
|
||||||
type=JournalEntryType.TASK_REFLECTION,
|
type=JournalEntryType.TASK_REFLECTION,
|
||||||
title=title,
|
title=params.title,
|
||||||
content=content,
|
content=content,
|
||||||
task_id=task_id,
|
task_id=params.task_id,
|
||||||
tags=tags or [],
|
tags=params.tags,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def create_decision_log(
|
def create_decision_log(params: DecisionLogParams) -> JournalEntry:
|
||||||
journal_id: UUID,
|
|
||||||
title: str,
|
|
||||||
context: str,
|
|
||||||
options: list[dict[str, str]],
|
|
||||||
chosen: str,
|
|
||||||
rationale: str,
|
|
||||||
consequences: list[str],
|
|
||||||
task_id: UUID | None = None,
|
|
||||||
tags: list[str] | None = None,
|
|
||||||
) -> JournalEntry:
|
|
||||||
"""Create a decision log entry."""
|
"""Create a decision log entry."""
|
||||||
options_text = ""
|
options_text = ""
|
||||||
for i, opt in enumerate(options, 1):
|
for i, opt in enumerate(params.options, 1):
|
||||||
options_text += f"\n**Option {i}: {opt.get('name', f'Option {i}')}**\n"
|
options_text += f"\n**Option {i}: {opt.get('name', f'Option {i}')}**\n"
|
||||||
options_text += f"- Pros: {opt.get('pros', 'N/A')}\n"
|
options_text += f"- Pros: {opt.get('pros', 'N/A')}\n"
|
||||||
options_text += f"- Cons: {opt.get('cons', 'N/A')}\n"
|
options_text += f"- Cons: {opt.get('cons', 'N/A')}\n"
|
||||||
|
|
||||||
content = f"""## Context
|
content = f"""## Context
|
||||||
{context}
|
{params.context}
|
||||||
|
|
||||||
## Options Considered
|
## Options Considered
|
||||||
{options_text}
|
{options_text}
|
||||||
|
|
||||||
## Decision
|
## Decision
|
||||||
Chose **{chosen}** because {rationale}
|
Chose **{params.chosen}** because {params.rationale}
|
||||||
|
|
||||||
## Consequences
|
## Consequences
|
||||||
{chr(10).join(f"- {c}" for c in consequences)}
|
{chr(10).join(f"- {c}" for c in params.consequences)}
|
||||||
"""
|
"""
|
||||||
return JournalEntry(
|
return JournalEntry(
|
||||||
journal_id=journal_id,
|
journal_id=params.journal_id,
|
||||||
type=JournalEntryType.DECISION_LOG,
|
type=JournalEntryType.DECISION_LOG,
|
||||||
title=title,
|
title=params.title,
|
||||||
content=content,
|
content=content,
|
||||||
task_id=task_id,
|
task_id=params.task_id,
|
||||||
tags=tags or [],
|
tags=params.tags,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def create_learning_entry(
|
def create_learning_entry(params: LearningEntryParams) -> JournalEntry:
|
||||||
journal_id: UUID,
|
|
||||||
title: str,
|
|
||||||
what_learned: str,
|
|
||||||
how_applied: str | None = None,
|
|
||||||
source: str | None = None,
|
|
||||||
task_id: UUID | None = None,
|
|
||||||
tags: list[str] | None = None,
|
|
||||||
) -> JournalEntry:
|
|
||||||
"""Create a learning entry."""
|
"""Create a learning entry."""
|
||||||
content = f"""## What I Learned
|
content = f"""## What I Learned
|
||||||
{what_learned}
|
{params.what_learned}
|
||||||
"""
|
"""
|
||||||
if how_applied:
|
if params.how_applied:
|
||||||
content += f"""
|
content += f"""
|
||||||
## How I Applied It
|
## How I Applied It
|
||||||
{how_applied}
|
{params.how_applied}
|
||||||
"""
|
"""
|
||||||
if source:
|
if params.source:
|
||||||
content += f"""
|
content += f"""
|
||||||
## Source
|
## Source
|
||||||
{source}
|
{params.source}
|
||||||
"""
|
"""
|
||||||
return JournalEntry(
|
return JournalEntry(
|
||||||
journal_id=journal_id,
|
journal_id=params.journal_id,
|
||||||
type=JournalEntryType.LEARNING,
|
type=JournalEntryType.LEARNING,
|
||||||
title=title,
|
title=params.title,
|
||||||
content=content,
|
content=content,
|
||||||
task_id=task_id,
|
task_id=params.task_id,
|
||||||
tags=tags or [],
|
tags=params.tags,
|
||||||
sentiment="positive",
|
sentiment="positive",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def create_struggle_entry(
|
def create_struggle_entry(params: StruggleEntryParams) -> JournalEntry:
|
||||||
journal_id: UUID,
|
|
||||||
title: str,
|
|
||||||
what_struggled: str,
|
|
||||||
attempted_solutions: list[str],
|
|
||||||
resolution: str | None = None,
|
|
||||||
help_needed: str | None = None,
|
|
||||||
task_id: UUID | None = None,
|
|
||||||
tags: list[str] | None = None,
|
|
||||||
) -> JournalEntry:
|
|
||||||
"""Create a struggle/difficulty entry."""
|
"""Create a struggle/difficulty entry."""
|
||||||
content = f"""## What I Struggled With
|
content = f"""## What I Struggled With
|
||||||
{what_struggled}
|
{params.what_struggled}
|
||||||
|
|
||||||
## What I Tried
|
## What I Tried
|
||||||
{chr(10).join(f"- {s}" for s in attempted_solutions)}
|
{chr(10).join(f"- {s}" for s in params.attempted_solutions)}
|
||||||
"""
|
"""
|
||||||
if resolution:
|
if params.resolution:
|
||||||
content += f"""
|
content += f"""
|
||||||
## Resolution
|
## Resolution
|
||||||
{resolution}
|
{params.resolution}
|
||||||
"""
|
"""
|
||||||
if help_needed:
|
if params.help_needed:
|
||||||
content += f"""
|
content += f"""
|
||||||
## Help Needed
|
## Help Needed
|
||||||
{help_needed}
|
{params.help_needed}
|
||||||
"""
|
"""
|
||||||
return JournalEntry(
|
return JournalEntry(
|
||||||
journal_id=journal_id,
|
journal_id=params.journal_id,
|
||||||
type=JournalEntryType.STRUGGLE,
|
type=JournalEntryType.STRUGGLE,
|
||||||
title=title,
|
title=params.title,
|
||||||
content=content,
|
content=content,
|
||||||
task_id=task_id,
|
task_id=params.task_id,
|
||||||
tags=tags or [],
|
tags=params.tags,
|
||||||
sentiment="frustrated",
|
sentiment="frustrated",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def create_general_entry(
|
def create_general_entry(params: GeneralEntryParams) -> JournalEntry:
|
||||||
journal_id: UUID,
|
|
||||||
title: str,
|
|
||||||
content: str,
|
|
||||||
task_id: UUID | None = None,
|
|
||||||
session_id: UUID | None = None,
|
|
||||||
tags: list[str] | None = None,
|
|
||||||
is_private: bool = False,
|
|
||||||
) -> JournalEntry:
|
|
||||||
"""Create a general journal entry."""
|
"""Create a general journal entry."""
|
||||||
return JournalEntry(
|
return JournalEntry(
|
||||||
journal_id=journal_id,
|
journal_id=params.journal_id,
|
||||||
type=JournalEntryType.GENERAL,
|
type=JournalEntryType.GENERAL,
|
||||||
title=title,
|
title=params.title,
|
||||||
content=content,
|
content=params.content,
|
||||||
task_id=task_id,
|
task_id=params.task_id,
|
||||||
session_id=session_id,
|
session_id=params.session_id,
|
||||||
tags=tags or [],
|
tags=params.tags,
|
||||||
is_private=is_private,
|
is_private=params.is_private,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,19 @@ logger = structlog.get_logger()
|
|||||||
# Maximum length for raw excerpt storage
|
# Maximum length for raw excerpt storage
|
||||||
MAX_EXCERPT_LENGTH = 200
|
MAX_EXCERPT_LENGTH = 200
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ExtractionContext:
|
||||||
|
"""Context for message extraction."""
|
||||||
|
|
||||||
|
content: str
|
||||||
|
agent_id: UUID
|
||||||
|
channel_id: UUID
|
||||||
|
session_id: UUID
|
||||||
|
group_id: UUID
|
||||||
|
task_id: UUID | None = None
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# EXTRACTION PATTERNS
|
# EXTRACTION PATTERNS
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -202,40 +215,27 @@ class ExtractionService:
|
|||||||
# Mention pattern
|
# Mention pattern
|
||||||
self._mention_pattern = re.compile(r"@(\w+)")
|
self._mention_pattern = re.compile(r"@(\w+)")
|
||||||
|
|
||||||
async def extract(
|
async def extract(self, ctx: ExtractionContext) -> ExtractionResult:
|
||||||
self,
|
|
||||||
content: str,
|
|
||||||
agent_id: UUID,
|
|
||||||
channel_id: UUID,
|
|
||||||
session_id: UUID,
|
|
||||||
group_id: UUID,
|
|
||||||
task_id: UUID | None = None,
|
|
||||||
) -> ExtractionResult:
|
|
||||||
"""
|
"""
|
||||||
Extract messages from raw content.
|
Extract messages from raw content.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
content: Raw LLM output text
|
ctx: Extraction context with content and metadata
|
||||||
agent_id: Agent who produced the content
|
|
||||||
channel_id: Target channel
|
|
||||||
session_id: Current session
|
|
||||||
group_id: Group within channel
|
|
||||||
task_id: Optional related task
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
ExtractionResult with extracted messages
|
ExtractionResult with extracted messages
|
||||||
"""
|
"""
|
||||||
if len(content) < self.config.min_content_length:
|
if len(ctx.content) < self.config.min_content_length:
|
||||||
return ExtractionResult(
|
return ExtractionResult(
|
||||||
messages=[],
|
messages=[],
|
||||||
raw_content=content,
|
raw_content=ctx.content,
|
||||||
agent_id=agent_id,
|
agent_id=ctx.agent_id,
|
||||||
channel_id=channel_id,
|
channel_id=ctx.channel_id,
|
||||||
session_id=session_id,
|
session_id=ctx.session_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Segment the content
|
# Segment the content
|
||||||
segments = self._segment_content(content)
|
segments = self._segment_content(ctx.content)
|
||||||
|
|
||||||
messages: list[ExtractedMessage] = []
|
messages: list[ExtractedMessage] = []
|
||||||
pattern_matches: dict[str, list[str]] = {}
|
pattern_matches: dict[str, list[str]] = {}
|
||||||
@@ -264,15 +264,15 @@ class ExtractionService:
|
|||||||
# Create message
|
# Create message
|
||||||
message = ExtractedMessage(
|
message = ExtractedMessage(
|
||||||
id=uuid4(),
|
id=uuid4(),
|
||||||
agent_id=agent_id,
|
agent_id=ctx.agent_id,
|
||||||
channel_id=channel_id,
|
channel_id=ctx.channel_id,
|
||||||
group_id=group_id,
|
group_id=ctx.group_id,
|
||||||
session_id=session_id,
|
session_id=ctx.session_id,
|
||||||
type=msg_type,
|
type=msg_type,
|
||||||
content=segment.strip(),
|
content=segment.strip(),
|
||||||
content_length=len(segment.strip()),
|
content_length=len(segment.strip()),
|
||||||
mentions=mentions,
|
mentions=mentions,
|
||||||
task_id=task_id,
|
task_id=ctx.task_id,
|
||||||
confidence=confidence,
|
confidence=confidence,
|
||||||
raw_excerpt=segment[:MAX_EXCERPT_LENGTH]
|
raw_excerpt=segment[:MAX_EXCERPT_LENGTH]
|
||||||
if len(segment) > MAX_EXCERPT_LENGTH
|
if len(segment) > MAX_EXCERPT_LENGTH
|
||||||
@@ -284,17 +284,17 @@ class ExtractionService:
|
|||||||
|
|
||||||
result = ExtractionResult(
|
result = ExtractionResult(
|
||||||
messages=messages,
|
messages=messages,
|
||||||
raw_content=content,
|
raw_content=ctx.content,
|
||||||
agent_id=agent_id,
|
agent_id=ctx.agent_id,
|
||||||
channel_id=channel_id,
|
channel_id=ctx.channel_id,
|
||||||
session_id=session_id,
|
session_id=ctx.session_id,
|
||||||
pattern_matches=pattern_matches,
|
pattern_matches=pattern_matches,
|
||||||
confidence_scores=confidence_scores,
|
confidence_scores=confidence_scores,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.log.info(
|
self.log.info(
|
||||||
"Extraction complete",
|
"Extraction complete",
|
||||||
agent_id=str(agent_id),
|
agent_id=str(ctx.agent_id),
|
||||||
message_count=result.message_count,
|
message_count=result.message_count,
|
||||||
types=result.types_extracted,
|
types=result.types_extracted,
|
||||||
)
|
)
|
||||||
@@ -365,46 +365,41 @@ class ExtractionService:
|
|||||||
|
|
||||||
return best_type, confidence, matches
|
return best_type, confidence, matches
|
||||||
|
|
||||||
async def extract_with_llm(
|
async def extract_with_llm(self, ctx: ExtractionContext) -> ExtractionResult:
|
||||||
self,
|
|
||||||
content: str,
|
|
||||||
agent_id: UUID,
|
|
||||||
channel_id: UUID,
|
|
||||||
session_id: UUID,
|
|
||||||
group_id: UUID,
|
|
||||||
task_id: UUID | None = None,
|
|
||||||
) -> ExtractionResult:
|
|
||||||
"""
|
"""
|
||||||
Extract messages using LLM classification.
|
Extract messages using LLM classification.
|
||||||
|
|
||||||
This is more accurate but slower and more expensive.
|
This is more accurate but slower and more expensive.
|
||||||
Falls back to pattern matching if LLM unavailable.
|
Falls back to pattern matching if LLM unavailable.
|
||||||
|
Uses TOON format for token-efficient communication.
|
||||||
"""
|
"""
|
||||||
from anthropic import AsyncAnthropic
|
from anthropic import AsyncAnthropic
|
||||||
|
|
||||||
from roboco.config import settings
|
from roboco.config import settings
|
||||||
|
from roboco.llm import ToonAdapter
|
||||||
|
|
||||||
|
toon = ToonAdapter()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
client = AsyncAnthropic(api_key=settings.anthropic_api_key)
|
client = AsyncAnthropic(api_key=settings.anthropic_api_key)
|
||||||
|
|
||||||
# Build prompt for LLM classification
|
# Build prompt for LLM classification using TOON
|
||||||
prompt = f"""Analyze this agent output and classify each distinct segment.
|
prompt = f"""Analyze this agent output and classify each distinct segment.
|
||||||
|
|
||||||
Agent output:
|
Agent output:
|
||||||
{content}
|
{ctx.content}
|
||||||
|
|
||||||
For each segment, identify:
|
For each segment, identify:
|
||||||
- type: one of [reasoning, dialogue, decision, action, blocker, technical]
|
- type: one of [reasoning, dialogue, decision, action, blocker, technical]
|
||||||
- content: the segment text
|
- content: the segment text
|
||||||
- confidence: 0.0 to 1.0
|
- confidence: 0.0 to 1.0
|
||||||
|
|
||||||
Return as JSON array of objects. Example:
|
Return as TOON tabular format:
|
||||||
[
|
[N,]{{type,content,confidence}}:
|
||||||
{{"type": "reasoning", "content": "Analyzing the problem...", "confidence": 0.9}},
|
reasoning,Analyzing the problem...,0.9
|
||||||
{{"type": "action", "content": "Creating file utils.py", "confidence": 0.95}}
|
action,Creating file utils.py,0.95
|
||||||
]
|
|
||||||
|
|
||||||
Output only valid JSON, no other text."""
|
Output only valid TOON, no other text."""
|
||||||
|
|
||||||
response = await client.messages.create(
|
response = await client.messages.create(
|
||||||
model="claude-3-haiku-20240307", # Fast, cheap for classification
|
model="claude-3-haiku-20240307", # Fast, cheap for classification
|
||||||
@@ -412,11 +407,9 @@ Output only valid JSON, no other text."""
|
|||||||
messages=[{"role": "user", "content": prompt}],
|
messages=[{"role": "user", "content": prompt}],
|
||||||
)
|
)
|
||||||
|
|
||||||
# Parse response
|
# Parse response using TOON (falls back to JSON)
|
||||||
import json
|
|
||||||
|
|
||||||
response_text = response.content[0].text
|
response_text = response.content[0].text
|
||||||
segments = json.loads(response_text)
|
segments = toon.decode(response_text)
|
||||||
|
|
||||||
messages: list[ExtractedMessage] = []
|
messages: list[ExtractedMessage] = []
|
||||||
for segment in segments:
|
for segment in segments:
|
||||||
@@ -430,11 +423,11 @@ Output only valid JSON, no other text."""
|
|||||||
id=uuid4(),
|
id=uuid4(),
|
||||||
content=msg_content,
|
content=msg_content,
|
||||||
message_type=msg_type,
|
message_type=msg_type,
|
||||||
agent_id=agent_id,
|
agent_id=ctx.agent_id,
|
||||||
channel_id=channel_id,
|
channel_id=ctx.channel_id,
|
||||||
session_id=session_id,
|
session_id=ctx.session_id,
|
||||||
group_id=group_id,
|
group_id=ctx.group_id,
|
||||||
task_id=task_id,
|
task_id=ctx.task_id,
|
||||||
confidence=confidence,
|
confidence=confidence,
|
||||||
metadata={"extraction_method": "llm"},
|
metadata={"extraction_method": "llm"},
|
||||||
)
|
)
|
||||||
@@ -442,7 +435,7 @@ Output only valid JSON, no other text."""
|
|||||||
|
|
||||||
return ExtractionResult(
|
return ExtractionResult(
|
||||||
messages=messages,
|
messages=messages,
|
||||||
raw_content=content,
|
raw_content=ctx.content,
|
||||||
extraction_time=0.0, # Could measure actual time
|
extraction_time=0.0, # Could measure actual time
|
||||||
confidence=sum(m.confidence for m in messages) / max(1, len(messages)),
|
confidence=sum(m.confidence for m in messages) / max(1, len(messages)),
|
||||||
)
|
)
|
||||||
@@ -450,14 +443,7 @@ Output only valid JSON, no other text."""
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Fall back to pattern matching
|
# Fall back to pattern matching
|
||||||
self.log.warning("LLM extraction failed, using patterns", error=str(e))
|
self.log.warning("LLM extraction failed, using patterns", error=str(e))
|
||||||
return await self.extract(
|
return await self.extract(ctx)
|
||||||
content=content,
|
|
||||||
agent_id=agent_id,
|
|
||||||
channel_id=channel_id,
|
|
||||||
session_id=session_id,
|
|
||||||
group_id=group_id,
|
|
||||||
task_id=task_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -496,26 +482,11 @@ class ExtractionPipeline:
|
|||||||
"""Register a callback for extracted messages."""
|
"""Register a callback for extracted messages."""
|
||||||
self._message_callbacks.append(callback)
|
self._message_callbacks.append(callback)
|
||||||
|
|
||||||
async def process_buffer(
|
async def process_buffer(self, ctx: ExtractionContext) -> ExtractionResult:
|
||||||
self,
|
|
||||||
content: str,
|
|
||||||
agent_id: UUID,
|
|
||||||
channel_id: UUID,
|
|
||||||
session_id: UUID,
|
|
||||||
group_id: UUID,
|
|
||||||
task_id: UUID | None = None,
|
|
||||||
) -> ExtractionResult:
|
|
||||||
"""
|
"""
|
||||||
Process a buffer and invoke callbacks for each message.
|
Process a buffer and invoke callbacks for each message.
|
||||||
"""
|
"""
|
||||||
result = await self.extraction.extract(
|
result = await self.extraction.extract(ctx)
|
||||||
content=content,
|
|
||||||
agent_id=agent_id,
|
|
||||||
channel_id=channel_id,
|
|
||||||
session_id=session_id,
|
|
||||||
group_id=group_id,
|
|
||||||
task_id=task_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Invoke callbacks for each message
|
# Invoke callbacks for each message
|
||||||
for message in result.messages:
|
for message in result.messages:
|
||||||
|
|||||||
@@ -1796,7 +1796,7 @@ name = "nvidia-cudnn-cu12"
|
|||||||
version = "9.10.2.21"
|
version = "9.10.2.21"
|
||||||
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
|
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "nvidia-cublas-cu12" },
|
{ name = "nvidia-cublas-cu12", marker = "sys_platform != 'win32'" },
|
||||||
]
|
]
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/ba/51/e123d997aa098c61d029f76663dedbfb9bc8dcf8c60cbd6adbe42f76d049/nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:949452be657fa16687d0930933f032835951ef0892b37d2d53824d1a84dc97a8", size = 706758467, upload-time = "2025-06-06T21:54:08.597Z" },
|
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/ba/51/e123d997aa098c61d029f76663dedbfb9bc8dcf8c60cbd6adbe42f76d049/nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:949452be657fa16687d0930933f032835951ef0892b37d2d53824d1a84dc97a8", size = 706758467, upload-time = "2025-06-06T21:54:08.597Z" },
|
||||||
@@ -1807,7 +1807,7 @@ name = "nvidia-cufft-cu12"
|
|||||||
version = "11.3.3.83"
|
version = "11.3.3.83"
|
||||||
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
|
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "nvidia-nvjitlink-cu12" },
|
{ name = "nvidia-nvjitlink-cu12", marker = "sys_platform != 'win32'" },
|
||||||
]
|
]
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/1f/13/ee4e00f30e676b66ae65b4f08cb5bcbb8392c03f54f2d5413ea99a5d1c80/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d2dd21ec0b88cf61b62e6b43564355e5222e4a3fb394cac0db101f2dd0d4f74", size = 193118695, upload-time = "2025-03-07T01:45:27.821Z" },
|
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/1f/13/ee4e00f30e676b66ae65b4f08cb5bcbb8392c03f54f2d5413ea99a5d1c80/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d2dd21ec0b88cf61b62e6b43564355e5222e4a3fb394cac0db101f2dd0d4f74", size = 193118695, upload-time = "2025-03-07T01:45:27.821Z" },
|
||||||
@@ -1834,9 +1834,9 @@ name = "nvidia-cusolver-cu12"
|
|||||||
version = "11.7.3.90"
|
version = "11.7.3.90"
|
||||||
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
|
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "nvidia-cublas-cu12" },
|
{ name = "nvidia-cublas-cu12", marker = "sys_platform != 'win32'" },
|
||||||
{ name = "nvidia-cusparse-cu12" },
|
{ name = "nvidia-cusparse-cu12", marker = "sys_platform != 'win32'" },
|
||||||
{ name = "nvidia-nvjitlink-cu12" },
|
{ name = "nvidia-nvjitlink-cu12", marker = "sys_platform != 'win32'" },
|
||||||
]
|
]
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/85/48/9a13d2975803e8cf2777d5ed57b87a0b6ca2cc795f9a4f59796a910bfb80/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:4376c11ad263152bd50ea295c05370360776f8c3427b30991df774f9fb26c450", size = 267506905, upload-time = "2025-03-07T01:47:16.273Z" },
|
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/85/48/9a13d2975803e8cf2777d5ed57b87a0b6ca2cc795f9a4f59796a910bfb80/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:4376c11ad263152bd50ea295c05370360776f8c3427b30991df774f9fb26c450", size = 267506905, upload-time = "2025-03-07T01:47:16.273Z" },
|
||||||
@@ -1847,7 +1847,7 @@ name = "nvidia-cusparse-cu12"
|
|||||||
version = "12.5.8.93"
|
version = "12.5.8.93"
|
||||||
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
|
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "nvidia-nvjitlink-cu12" },
|
{ name = "nvidia-nvjitlink-cu12", marker = "sys_platform != 'win32'" },
|
||||||
]
|
]
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/c2/f5/e1854cb2f2bcd4280c44736c93550cc300ff4b8c95ebe370d0aa7d2b473d/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ec05d76bbbd8b61b06a80e1eaf8cf4959c3d4ce8e711b65ebd0443bb0ebb13b", size = 288216466, upload-time = "2025-03-07T01:48:13.779Z" },
|
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/c2/f5/e1854cb2f2bcd4280c44736c93550cc300ff4b8c95ebe370d0aa7d2b473d/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ec05d76bbbd8b61b06a80e1eaf8cf4959c3d4ce8e711b65ebd0443bb0ebb13b", size = 288216466, upload-time = "2025-03-07T01:48:13.779Z" },
|
||||||
@@ -2062,7 +2062,7 @@ name = "pexpect"
|
|||||||
version = "4.9.0"
|
version = "4.9.0"
|
||||||
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
|
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "ptyprocess" },
|
{ name = "ptyprocess", marker = "sys_platform != 'win32'" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" }
|
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
@@ -2577,6 +2577,15 @@ wheels = [
|
|||||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/d9/4f/00be2196329ebbff56ce564aa94efb0fbc828d00de250b1980de1a34ab49/python_pptx-1.0.2-py3-none-any.whl", hash = "sha256:160838e0b8565a8b1f67947675886e9fea18aa5e795db7ae531606d68e785cba", size = 472788, upload-time = "2024-08-07T17:33:28.192Z" },
|
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/d9/4f/00be2196329ebbff56ce564aa94efb0fbc828d00de250b1980de1a34ab49/python_pptx-1.0.2-py3-none-any.whl", hash = "sha256:160838e0b8565a8b1f67947675886e9fea18aa5e795db7ae531606d68e785cba", size = 472788, upload-time = "2024-08-07T17:33:28.192Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "python-toon"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
|
||||||
|
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/4e/92/640c83ca46d5fe9c49895449a8932f55252537dd13dd22186cbac3a1ce59/python_toon-0.1.3.tar.gz", hash = "sha256:ca348b214c4f1cdad3579fd83dd60032d9eb87eb349c2d430ad9eb6371f174bf", size = 31280, upload-time = "2025-11-04T09:12:20.949Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/26/a4/2f2def0378b44f913d2d6cb3bc5b1a15267b363937ab1cb9afb07ce2313c/python_toon-0.1.3-py3-none-any.whl", hash = "sha256:a27b0ee4a729e730d1037d0a63eb8b344b3e5a26e3dc9a173067b6c31a868ee6", size = 21797, upload-time = "2025-11-04T09:12:19.443Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytz"
|
name = "pytz"
|
||||||
version = "2025.2"
|
version = "2025.2"
|
||||||
@@ -2781,6 +2790,7 @@ dependencies = [
|
|||||||
{ name = "pydantic-settings" },
|
{ name = "pydantic-settings" },
|
||||||
{ name = "python-jose", extra = ["cryptography"] },
|
{ name = "python-jose", extra = ["cryptography"] },
|
||||||
{ name = "python-multipart" },
|
{ name = "python-multipart" },
|
||||||
|
{ name = "python-toon" },
|
||||||
{ name = "redis" },
|
{ name = "redis" },
|
||||||
{ name = "sqlalchemy", extra = ["asyncio"] },
|
{ name = "sqlalchemy", extra = ["asyncio"] },
|
||||||
{ name = "structlog" },
|
{ name = "structlog" },
|
||||||
@@ -2842,6 +2852,7 @@ requires-dist = [
|
|||||||
{ name = "pytest-xdist", marker = "extra == 'dev'" },
|
{ name = "pytest-xdist", marker = "extra == 'dev'" },
|
||||||
{ name = "python-jose", extras = ["cryptography"] },
|
{ name = "python-jose", extras = ["cryptography"] },
|
||||||
{ name = "python-multipart" },
|
{ name = "python-multipart" },
|
||||||
|
{ name = "python-toon" },
|
||||||
{ name = "redis" },
|
{ name = "redis" },
|
||||||
{ name = "rich", marker = "extra == 'dev'" },
|
{ name = "rich", marker = "extra == 'dev'" },
|
||||||
{ name = "ruff", marker = "extra == 'dev'" },
|
{ name = "ruff", marker = "extra == 'dev'" },
|
||||||
|
|||||||
Reference in New Issue
Block a user