Spaces:
Running
Running
File size: 11,199 Bytes
5249791 ccb935d 5249791 ccb935d 5249791 ccb935d 5249791 ccb935d 5249791 ccb935d 5249791 ccb935d 5249791 ccb935d 5249791 ccb935d 5249791 ccb935d 5249791 ccb935d 5249791 ccb935d 5249791 ccb935d 5249791 ccb935d 5249791 ccb935d 5249791 ccb935d 5249791 ccb935d 5249791 ccb935d 5249791 ccb935d e6e75d6 5249791 ccb935d 5249791 ccb935d 5249791 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 | """Chat helper functions — history conversion, prompt building, iteration context.
Enhanced version with:
- Better context management
- Conversation summarization for long histories
- Prompt optimization
- Token counting
- Session awareness
"""
from __future__ import annotations
import logging
from typing import Any, Optional
from dataclasses import dataclass
from code.config.constants import (
SYSTEM_PROMPT,
MAX_SESSION_HISTORY,
)
from code.execution.code_extractor import strip_thinking_blocks
from code.model.inference import estimate_tokens
logger = logging.getLogger(__name__)
@dataclass
class ContextInfo:
"""Information about the current conversation context."""
message_count: int = 0
estimated_tokens: int = 0
has_images: bool = False
has_code_blocks: bool = False
is_truncated: bool = False
summary: str | None = None
def chat_history_to_messages(
history: list[dict[str, str]],
max_messages: int | None = None,
) -> list[dict[str, Any]]:
"""Convert chat history list to messages format for the model.
Enhanced with:
- Message limit handling
- Context info tracking
- Automatic truncation for long conversations
Args:
history: Chat history from frontend.
max_messages: Maximum messages to include (None for default).
Returns:
Messages in OpenAI format with system prompt prepended.
"""
if max_messages is None:
max_messages = MAX_SESSION_HISTORY
messages: list[dict[str, Any]] = [{"role": "system", "content": SYSTEM_PROMPT}]
# Take only the most recent messages if history is too long
working_history = history[-max_messages:] if len(history) > max_messages else history
context_info = ContextInfo()
for item in working_history:
role = item.get("role")
content = str(item.get("content") or "").strip()
if role not in {"user", "assistant"} or not content:
continue
if role == "assistant":
content = strip_thinking_blocks(content)
messages.append({"role": role, "content": content})
# Track context info
context_info.message_count += 1
context_info.estimated_tokens += estimate_tokens(content)
if not context_info.has_images:
context_info.has_images = "image" in content.lower() or "![" in content
if not context_info.has_code_blocks:
context_info.has_code_blocks = "```" in content
context_info.is_truncated = len(history) > max_messages
if context_info.is_truncated:
logger.info(
"History truncated from %d to %d messages",
len(history), len(working_history)
)
return messages
def clip_context(text: str, limit: int = 4_000) -> str:
"""Truncate text to a character limit with a note.
Preserves code blocks when possible by truncating outside of them.
Args:
text: Text to truncate.
limit: Maximum character count.
Returns:
Truncated text with ellipsis note if truncated.
"""
if len(text) <= limit:
return text
# Simple truncation (could be enhanced to preserve code blocks)
return text[:limit] + f"\n... truncated {len(text) - limit} characters ..."
def iteration_context(execution_context: dict[str, Any] | None) -> str:
"""Build a context string from previous execution results.
This allows the model to reference prior code, stdout, and stderr
when the user asks to iterate or debug.
Args:
execution_context: Dict with previous execution details.
Returns:
Formatted context string for inclusion in prompts.
"""
if not execution_context or not execution_context.get("code"):
return ""
code = clip_context(str(execution_context.get("code") or ""), 6_000)
target = str(execution_context.get("target") or "code")
fence_lang = str(execution_context.get("fence_lang") or target)
status = str(execution_context.get("status") or "")
stdout = clip_context(str(execution_context.get("stdout") or ""), 2_000)
stderr = clip_context(str(execution_context.get("stderr") or ""), 2_000)
parts = [
"Previous generated code and run result are available for iteration.",
f"Previous target: {target}",
f"Previous status: {status}",
f"Previous code:\n```{fence_lang}\n{code}\n```",
]
if stdout:
parts.append(f"Previous stdout:\n{stdout}")
if stderr:
parts.append(f"Previous stderr / traceback:\n{stderr}")
parts.append(
"If the user asks to revise, debug, extend, or explain the prior code, use this context."
)
return "\n\n".join(parts)
def targeted_prompt(
prompt: str,
target_language: str,
target_framework: str = "",
execution_context: dict[str, Any] | None = None,
search_context: str = "",
) -> str:
"""Build the full user prompt with language, framework, search, and iteration context.
Enhanced with:
- Better framework-specific hints
- Code quality instructions
- Security reminders
Args:
prompt: User's original prompt.
target_language: Target programming language.
target_framework: Optional target framework.
execution_context: Previous execution context for iterations.
search_context: Web search results to incorporate.
Returns:
Complete formatted prompt for the model.
"""
iter_ctx = iteration_context(execution_context)
context_block = f"\n\n{iter_ctx}" if iter_ctx else ""
search_block = ""
if search_context:
search_block = (
f"\n\n{search_context}\n\n"
"Use the above search results to inform your code generation if relevant."
)
framework_hint = f" using {target_framework}" if target_framework else ""
gradio_hint = ""
if target_framework == "Gradio":
gradio_hint = (
"\n\nIMPORTANT: This is a Gradio app. Create a complete Python script that:\n"
"- Imports gradio as gr\n"
"- Defines the UI using gr.Interface() or gr.Blocks()\n"
"- Includes all processing logic inline\n"
"- Calls .launch(server_name='0.0.0.0', server_port=7860) at the end\n"
"- Uses only standard library + gradio + common packages (PIL, matplotlib, numpy)\n"
"- Make the UI clean, modern, and functional\n"
"- Include proper error handling and loading states"
)
# Framework-specific hints
react_hint = ""
if target_framework == "React":
react_hint = (
"\n\nREACT SPECIFIC:\n"
"- Use functional components and hooks\n"
"- Include proper TypeScript types if applicable\n"
"- Use CSS modules or styled-components for styling\n"
"- Make components reusable and composable"
)
flask_hint = ""
if target_framework == "Flask":
flask_hint = (
"\n\nFLASK SPECIFIC:\n"
"- Use Flask blueprints for larger apps\n"
"- Include proper error handlers\n"
"- Add input validation using marshmallow or similar\n"
"- Structure with separate routes, models, and templates directories"
)
security_reminder = (
"\n\nSECURITY REMINDERS:\n"
"- Validate all user inputs\n"
"- Use parameterized queries for database operations\n"
"- Never hardcode secrets or API keys\n"
"- Implement proper authentication/authorization where needed\n"
"- Sanitize outputs to prevent XSS"
)
return (
f"Target: {target_language}{framework_hint}. Generate a complete, runnable application. "
"Use the `write_file` tool to save each file to the workspace. "
"Do NOT paste code in markdown blocks — always use `write_file`. "
"For multi-file projects, call `write_file` once per file. "
"After writing files, give a short summary of what you created. "
"Include proper error handling, comments, and follow best practices. "
f"{gradio_hint}"
f"{react_hint}"
f"{flask_hint}"
f"{security_reminder}"
f"{search_block}"
f"{context_block}\n\n"
f"User request:\n{prompt}"
)
def summarize_conversation(
history: list[dict[str, str]],
max_summary_length: int = 500,
) -> str | None:
"""Create a summary of the conversation for context preservation.
This is useful when the conversation gets too long and needs to be
compressed while preserving important context.
Args:
history: Full conversation history.
max_summary_length: Maximum length of the summary.
Returns:
Summary string or None if summarization not needed.
"""
if len(history) < 10: # Only summarize longer conversations
return None
# Extract key information
user_requests = []
files_created = []
for msg in history:
role = msg.get("role")
content = msg.get("content", "")
if role == "user":
# Get first sentence as summary of request
first_sentence = content.split('.')[0].split('\n')[0]
if first_sentence:
user_requests.append(first_sentence)
elif role == "assistant":
# Look for file creation mentions
if "created" in content.lower() or "wrote" in content.lower():
# Simple extraction - could be enhanced with NLP
pass
if not user_requests:
return None
summary_parts = [
"Conversation summary:",
f"User made {len(user_requests)} requests.",
"Key requests: " + "; ".join(user_requests[-5:]), # Last 5 requests
]
summary = "\n".join(summary_parts)
if len(summary) > max_summary_length:
summary = summary[:max_summary_length] + "..."
return summary
def build_system_prompt_with_context(
custom_instructions: str | None = None,
active_skills: list[str] | None = None,
active_agent: str | None = None,
) -> str:
"""Build system prompt with additional context.
Args:
custom_instructions: Additional custom instructions.
active_skills: List of currently active skill names.
active_agent: Name of the active agent if any.
Returns:
Complete system prompt string.
"""
base_prompt = SYSTEM_PROMPT
additions = []
if active_agent:
additions.append(f"You are currently operating as the '{active_agent}' agent.")
if active_skills:
skills_str = ", ".join(active_skills)
additions.append(f"Active skills: {skills_str}. Apply these skill guidelines.")
if custom_instructions:
additions.append(f"ADDITIONAL INSTRUCTIONS:\n{custom_instructions}")
if additions:
base_prompt += "\n\n" + "\n\n".join(additions)
return base_prompt
|