mirror of
https://github.com/agentscope-ai/ReMe.git
synced 2026-08-28 05:25:04 +00:00
164 lines
5.5 KiB
Python
164 lines
5.5 KiB
Python
"""Data models for multi-modal conversation history and LLM interaction trajectories."""
|
|
|
|
import datetime
|
|
import json
|
|
import re
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
|
|
from .tool_call import ToolCall
|
|
from ..enumeration import Role
|
|
|
|
|
|
class ContentBlock(BaseModel):
|
|
"""
|
|
Individual unit of multi-modal content like text, images, or video.
|
|
examples:
|
|
{
|
|
"type": "image_url",
|
|
"image_url": {
|
|
"url": "https://img.alicdn.com/imgextra/i1/O1CN01gDEY8M1W114Hi3XcN_!!6000000002727-0-tps-1024-406.jpg"
|
|
},
|
|
}
|
|
|
|
{
|
|
"type": "video",
|
|
"video": [
|
|
"https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/xzsgiz/football1.jpg",
|
|
"https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/tdescd/football2.jpg",
|
|
"https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/zefdja/football3.jpg",
|
|
"https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/aedbqh/football4.jpg",
|
|
],
|
|
}
|
|
|
|
{
|
|
"type": "text",
|
|
"text": "How do you solve this problem?"
|
|
}
|
|
"""
|
|
|
|
model_config = ConfigDict(extra="allow")
|
|
|
|
type: str = Field(default="")
|
|
content: str | dict | list = Field(default="")
|
|
|
|
@model_validator(mode="before")
|
|
@classmethod
|
|
def init_block(cls, data: dict) -> dict:
|
|
"""Dynamically maps the type-specific key to the content field."""
|
|
content_type = data.get("type", "")
|
|
if content_type and content_type in data:
|
|
data["content"] = data[content_type]
|
|
return data
|
|
|
|
def simple_dump(self) -> dict:
|
|
"""Serializes the block into an API-compatible dictionary format."""
|
|
return {
|
|
"type": self.type,
|
|
self.type: self.content,
|
|
**self.model_extra,
|
|
}
|
|
|
|
|
|
class Message(BaseModel):
|
|
"""Data model for a single dialogue entry including roles and tool interactions."""
|
|
|
|
name: str | None = Field(default=None)
|
|
role: Role = Field(default=Role.USER)
|
|
content: str | list[ContentBlock] = Field(default="")
|
|
reasoning_content: str = Field(default="")
|
|
tool_calls: list[ToolCall] = Field(default_factory=list)
|
|
tool_call_id: str = Field(default="")
|
|
time_created: str = Field(default_factory=lambda: datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
|
|
metadata: dict = Field(default_factory=dict)
|
|
|
|
def dump_content(self) -> str | list[dict]:
|
|
"""Returns content as a raw string or a list of serialized blocks."""
|
|
if isinstance(self.content, str):
|
|
return self.content
|
|
return [block.simple_dump() for block in self.content]
|
|
|
|
def simple_dump(
|
|
self,
|
|
add_name: bool = False,
|
|
add_reasoning: bool = True,
|
|
add_time_created: bool = False,
|
|
add_metadata: bool = False,
|
|
enable_json_dump: bool = False,
|
|
) -> dict | str:
|
|
"""Transforms the message into a simplified dictionary for standard APIs."""
|
|
result = {}
|
|
if add_name and self.name:
|
|
result["name"] = self.name
|
|
|
|
result["role"] = self.role.value
|
|
result["content"] = self.dump_content()
|
|
|
|
if add_reasoning and self.reasoning_content:
|
|
result["reasoning_content"] = self.reasoning_content
|
|
|
|
if self.tool_calls:
|
|
result["tool_calls"] = [tc.simple_output_dump() for tc in self.tool_calls]
|
|
|
|
if self.tool_call_id:
|
|
result["tool_call_id"] = self.tool_call_id
|
|
|
|
if add_time_created:
|
|
result["time_created"] = self.time_created
|
|
|
|
if add_metadata:
|
|
result["metadata"] = self.metadata
|
|
|
|
if enable_json_dump:
|
|
return json.dumps(result, ensure_ascii=False)
|
|
else:
|
|
return result
|
|
|
|
def format_message(
|
|
self,
|
|
index: int | None = None,
|
|
add_time: bool = False,
|
|
use_name: bool = False,
|
|
add_reasoning: bool = True,
|
|
add_tools: bool = True,
|
|
strip_markdown_headers: bool = False,
|
|
) -> str:
|
|
"""Generates a human-readable string representation of the message."""
|
|
prefix = f"round{index} " if index is not None else ""
|
|
time_str = f"[{self.time_created}] " if add_time else ""
|
|
header = f"{self.name or self.role.value if use_name else self.role.value}:"
|
|
|
|
lines = [f"{prefix}{time_str}{header}"]
|
|
|
|
def strip_md_func(line):
|
|
if strip_markdown_headers:
|
|
line = re.sub(r'\n##+ +', '\n', line)
|
|
return line
|
|
|
|
if add_reasoning and self.reasoning_content:
|
|
lines.append(self.reasoning_content)
|
|
|
|
if isinstance(self.content, str):
|
|
lines.append(strip_md_func(self.content))
|
|
|
|
elif isinstance(self.content, list):
|
|
for block in self.content:
|
|
text = block.content if isinstance(block.content, str) else \
|
|
json.dumps(block.content, ensure_ascii=False)
|
|
text = str(text)
|
|
lines.append(strip_md_func(text))
|
|
|
|
if add_tools and self.tool_calls:
|
|
for tc in self.tool_calls:
|
|
lines.append(f" - tool_call={tc.name} params={tc.arguments}")
|
|
|
|
return " ".join(lines).strip()
|
|
|
|
|
|
class Trajectory(BaseModel):
|
|
"""Sequence of messages representing a full conversation session and its evaluation."""
|
|
|
|
task_id: str = Field(default="")
|
|
messages: list[Message] = Field(default_factory=list)
|
|
score: float = Field(default=0.0)
|
|
metadata: dict = Field(default_factory=dict)
|