mirror of
https://github.com/Pythagora-io/gpt-pilot.git
synced 2026-01-09 13:17:55 -05:00
This is a complete rewrite of the GPT Pilot core, from the ground up, making the agentic architecture front and center, and also fixing some long-standing problems with the database architecture that weren't feasible to solve without breaking compatibility. As the database structure and config file syntax have changed, we have automatic imports for projects and current configs, see the README.md file for details. This also relicenses the project to FSL-1.1-MIT license.
164 lines
5.0 KiB
Python
164 lines
5.0 KiB
Python
from copy import deepcopy
|
|
from typing import Iterator, Optional
|
|
|
|
|
|
class Convo:
|
|
"""
|
|
A conversation between a user and a Large Language Model (LLM) assistant.
|
|
"""
|
|
|
|
ROLES = ["system", "user", "assistant", "function"]
|
|
|
|
messages: list[dict[str, str]]
|
|
|
|
def __init__(self, content: Optional[str] = None):
|
|
"""
|
|
Initialize a new conversation.
|
|
|
|
:param content: Initial system message (optional).
|
|
"""
|
|
self.messages = []
|
|
if content is not None:
|
|
self.system(content)
|
|
|
|
@staticmethod
|
|
def _dedent(text: str) -> str:
|
|
"""
|
|
Remove common leading whitespace from every line of text.
|
|
|
|
:param text: Text to dedent.
|
|
:return: Dedented text.
|
|
"""
|
|
indent = len(text)
|
|
lines = text.splitlines()
|
|
for line in lines:
|
|
if line.strip():
|
|
indent = min(indent, len(line) - len(line.lstrip()))
|
|
dedented_lines = [line[indent:].rstrip() for line in lines]
|
|
return "\n".join(line for line in dedented_lines)
|
|
|
|
def add(self, role: str, content: str, name: Optional[str] = None) -> "Convo":
|
|
"""
|
|
Add a message to the conversation.
|
|
|
|
In most cases, you should use the convenience methods instead.
|
|
|
|
:param role: Role of the message (system, user, assistant, function).
|
|
:param content: Content of the message.
|
|
:param name: Name of the message sender (optional).
|
|
:return: The conv object.
|
|
"""
|
|
|
|
if role not in self.ROLES:
|
|
raise ValueError(f"Unknown role: {role}")
|
|
if not content:
|
|
raise ValueError("Empty message content")
|
|
if not isinstance(content, str) and not isinstance(content, dict):
|
|
raise TypeError(f"Invalid message content: {type(content).__name__}")
|
|
|
|
message = {
|
|
"role": role,
|
|
"content": self._dedent(content) if isinstance(content, str) else content,
|
|
}
|
|
if name is not None:
|
|
message["name"] = name
|
|
|
|
self.messages.append(message)
|
|
return self
|
|
|
|
def system(self, content: str, name: Optional[str] = None) -> "Convo":
|
|
"""
|
|
Add a system message to the conversation.
|
|
|
|
System messages can use `name` for showing example conversations
|
|
between an example user and an example assistant.
|
|
|
|
:param content: Content of the message.
|
|
:param name: Name of the message sender (optional).
|
|
:return: The convo object.
|
|
"""
|
|
return self.add("system", content, name)
|
|
|
|
def user(self, content: str, name: Optional[str] = None) -> "Convo":
|
|
"""
|
|
Add a user message to the conversation.
|
|
|
|
:param content: Content of the message.
|
|
:param name: User name (optional).
|
|
:return: The convo object.
|
|
"""
|
|
return self.add("user", content, name)
|
|
|
|
def assistant(self, content: str, name: Optional[str] = None) -> "Convo":
|
|
"""
|
|
Add an assistant message to the conversation.
|
|
|
|
:param content: Content of the message.
|
|
:param name: Assistant name (optional).
|
|
:return: The convo object.
|
|
"""
|
|
return self.add("assistant", content, name)
|
|
|
|
def function(self, content: str, name: Optional[str] = None) -> "Convo":
|
|
"""
|
|
Add a function (tool) response to the conversation.
|
|
|
|
:param content: Content of the message.
|
|
:param name: Function/tool name (optional).
|
|
:return: The convo object.
|
|
"""
|
|
return self.add("function", content, name)
|
|
|
|
def fork(self) -> "Convo":
|
|
"""
|
|
Create an identical copy of the conversation.
|
|
|
|
This performs a deep copy of all the message
|
|
contents, so you can safely modify both the
|
|
parent and the child conversation.
|
|
|
|
:return: A copy of the conversation.
|
|
"""
|
|
child = Convo()
|
|
child.messages = deepcopy(self.messages)
|
|
return child
|
|
|
|
def after(self, parent: "Convo") -> "Convo":
|
|
"""
|
|
Create a chat with only messages after the last common
|
|
message (that appears in both parent conversation and
|
|
this one).
|
|
|
|
:param parent: Parent conversation.
|
|
:return: A new conversation with only new messages.
|
|
"""
|
|
index = 0
|
|
while index < min(len(self.messages), len(parent.messages)) and self.messages[index] == parent.messages[index]:
|
|
index += 1
|
|
|
|
child = Convo()
|
|
child.messages = [deepcopy(msg) for msg in self.messages[index:]]
|
|
return child
|
|
|
|
def last(self) -> Optional[dict[str, str]]:
|
|
"""
|
|
Get the last message in the conversation.
|
|
|
|
:return: The last message, or None if the conversation is empty.
|
|
"""
|
|
return self.messages[-1] if self.messages else None
|
|
|
|
def __iter__(self) -> Iterator[dict[str, str]]:
|
|
"""
|
|
Iterate over the messages in the conversation.
|
|
|
|
:return: An iterator over the messages.
|
|
"""
|
|
return iter(self.messages)
|
|
|
|
def __repr__(self) -> str:
|
|
return f"<Convo({self.messages})>"
|
|
|
|
|
|
__all__ = ["Convo"]
|