zai / app /core /session /session_content.py
bg5's picture
Upload 85 files
e0b68d2 verified
Raw History Blame Contribute Delete
8.18 kB
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Message content preparation helpers.
Provides functions to flatten message content, concatenate conversation
history, choose the correct turn content, inject system prompts, and
build the final messages list used in session / direct modes.
"""
from __future__ import annotations
from typing import Any, Dict, List
# ---------------------------------------------------------------------------
# Low-level content helpers
# ---------------------------------------------------------------------------
def content_to_text(content: Any) -> str:
"""Best-effort flatten message content into plain text."""
if isinstance(content, str):
return content
if isinstance(content, list):
parts: List[str] = []
for item in content:
if isinstance(item, str) and item:
parts.append(item)
continue
if isinstance(item, dict) and item.get("type") == "text":
text = item.get("text")
if isinstance(text, str) and text:
parts.append(text)
return " ".join(parts)
if content is None:
return ""
return str(content)
def concat_history(messages: List[Dict[str, Any]]) -> str:
"""Concatenate non-system messages into a single text block.
Used when a session is lost / new and we need to replay history into
a single upstream user message. System messages are skipped because
the upstream model configuration handles them separately.
"""
parts: List[str] = []
for msg in messages:
role = msg.get("role", "user")
if role == "system":
continue
content = msg.get("content", "")
if isinstance(content, list):
content = " ".join(
item.get("text", "")
for item in content
if isinstance(item, dict) and item.get("type") == "text"
)
if content:
parts.append(f"[{role}]: {content}")
return "\n\n".join(parts)
def extract_turn_content(
raw_messages: List[Dict[str, Any]],
normalized_messages: List[Dict[str, Any]],
fallback_user_text: str,
) -> str:
"""Choose the appropriate content for the current turn.
- last raw role == user -> send that user text
- last raw role == tool -> send the last user text from normalized messages
- otherwise -> fallback to *fallback_user_text*
"""
if raw_messages:
last_raw = raw_messages[-1]
if isinstance(last_raw, dict):
last_role = str(last_raw.get("role") or "")
if last_role == "user":
text = content_to_text(last_raw.get("content"))
if text.strip():
return text
elif last_role == "tool":
# tool messages are converted to plain user messages during normalization
for msg in reversed(normalized_messages):
if not isinstance(msg, dict) or msg.get("role") != "user":
continue
content = content_to_text(msg.get("content"))
if content.strip():
return content
return fallback_user_text
# ---------------------------------------------------------------------------
# System prompt injection (works for both session and direct modes)
# ---------------------------------------------------------------------------
def inject_system_prompt(
messages: List[Dict[str, Any]],
) -> List[Dict[str, Any]]:
"""Merge all system messages into the first user message.
Extracts every ``role=system`` entry, concatenates their text, and
prepends it to the first ``role=user`` message's content. The
system entries are removed from the returned list.
If there are no system messages or no user messages, the list is
returned unchanged (shallow copy).
This is used in **both** session mode and direct mode when
``SESSION_SYSTEM_INJECT`` is enabled.
"""
# Collect system text
system_parts: List[str] = []
for msg in messages:
if msg.get("role") == "system":
text = content_to_text(msg.get("content"))
if text.strip():
system_parts.append(text.strip())
if not system_parts:
return list(messages)
system_text = "\n\n".join(system_parts)
# Build new list: skip system, inject into first user message
result: List[Dict[str, Any]] = []
injected = False
for msg in messages:
if msg.get("role") == "system":
continue
if not injected and msg.get("role") == "user":
user_content = content_to_text(msg.get("content"))
result.append({
**msg,
"content": system_text + "\n\n" + user_content,
})
injected = True
else:
result.append(msg)
return result
# ---------------------------------------------------------------------------
# High-level session body builders
# ---------------------------------------------------------------------------
def build_session_body_messages(
normalized_messages: List[Dict[str, Any]],
session_turn_content: str,
is_new_session: bool,
inject_system: bool = True,
) -> List[Dict[str, Any]]:
"""Build the messages list for session mode body.
Args:
normalized_messages: Full preprocessed message list (for extracting
system prompts and history).
session_turn_content: The current turn's content (from
``extract_turn_content``).
is_new_session: ``True`` if no existing session was found.
inject_system: If ``True``, concatenate the system prompt into the
user message content. If ``False``, pass the system prompt as
a separate ``system`` message.
Returns:
Messages list for the upstream body. In session mode this is
typically 1 message (inject) or 2 messages (pass-through with
system).
"""
if not is_new_session:
# Reuse session: upstream already has the system prompt from turn 1
return [{"role": "user", "content": session_turn_content}]
# ---- New session: handle system prompt ----
system_parts: List[str] = []
for msg in normalized_messages:
if msg.get("role") != "system":
continue
system_text_part = content_to_text(msg.get("content")).strip()
if system_text_part:
system_parts.append(system_text_part)
system_text = "\n\n".join(system_parts)
# Build history content (for multi-message recovery)
raw_msg_count = sum(1 for m in normalized_messages if m.get("role") != "system")
if raw_msg_count > 1:
# Multiple non-system messages: concatenate history
history_text = concat_history(normalized_messages)
else:
history_text = session_turn_content
if not system_text:
# No system prompt: just send user content
return [{"role": "user", "content": history_text}]
if inject_system:
# Inject mode: concatenate system + content into single user message
return [{"role": "user", "content": system_text + "\n\n" + history_text}]
else:
# Pass-through mode: system as separate message
return [
{"role": "system", "content": system_text},
{"role": "user", "content": history_text},
]
def get_precreate_content(
session_body_messages: List[Dict[str, Any]],
) -> str:
"""Extract the text content to pass to ``_precreate_chat`` from session body messages.
Simply concatenates all message contents into a single string.
"""
parts: List[str] = []
for msg in session_body_messages:
text = content_to_text(msg.get("content")).strip()
if text:
parts.append(text)
return "\n\n".join(parts)