"""线程安全对话历史""" import json import threading from .types import Message class History: def __init__(self, max_size=999999): self._msgs = [] self._lock = threading.RLock() self._max = max_size def add_user(self, text): with self._lock: self._msgs.append(Message("user", text)) self._trim() def add_assistant(self, text, tool_calls=None): with self._lock: m = Message("assistant", text) if tool_calls: m.tool_calls = tool_calls self._msgs.append(m) self._trim() def add_tool(self, call_id, name, result): with self._lock: self._msgs.append(Message("tool", result, tool_call_id=call_id, name=name)) self._trim() def add_cancel(self, name, args): with self._lock: self._msgs.append(Message("user", f"工具 {name} 已取消。参数: {args}")) self._trim() def _trim(self): if len(self._msgs) > self._max: self._msgs = self._msgs[-self._max:] def render(self, system_prompt="", current=None, fc=False): """渲染为消息列表。fc=True 用 role:tool,否则工具结果用 role:user""" with self._lock: result = [] if system_prompt: result.append({"role": "system", "content": system_prompt}) for m in self._msgs: if m.role == "tool" and not fc: result.append({"role": "user", "content": m.content}) else: entry = {"role": m.role, "content": m.content} if fc and m.role == "tool" and m.tool_call_id: entry["tool_call_id"] = m.tool_call_id if m.name: entry["name"] = m.name if fc and m.role == "assistant" and m.tool_calls: # content 为空时设为 None(OpenAI 规范:有 tool_calls 时 content 可为 null) if not entry.get("content"): entry["content"] = None # arguments 必须是 JSON 字符串(OpenAI 规范) entry["tool_calls"] = [ {"id": tc.id, "type": "function", "function": { "name": tc.name, "arguments": json.dumps(tc.arguments, ensure_ascii=False) if tc.arguments else json.dumps({"args": tc.args}, ensure_ascii=False) }} for tc in m.tool_calls ] result.append(entry) if current: result.append({"role": "user", "content": current}) return result def snapshot(self): with self._lock: return list(self._msgs) def clear(self): with self._lock: self._msgs.clear() def __len__(self): with self._lock: return len(self._msgs)