Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding a Character Chat Agent #4411

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions langchain/agents/character_chat/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""An agent designed to hold a conversation as a given character in addition to using tools."""
133 changes: 133 additions & 0 deletions langchain/agents/character_chat/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
"""An agent designed to hold a conversation as a given character in addition to using tools."""
from __future__ import annotations

from typing import Any, List, Optional, Sequence, Tuple

from pydantic import Field

from langchain.agents.agent import Agent, AgentOutputParser
from langchain.agents.character_chat.output_parser import ConvoOutputParser
from langchain.agents.character_chat.prompt import (CHARACTER_SUMMARY, PREFIX,
SUFFIX,
TEMPLATE_TOOL_RESPONSE)
from langchain.agents.utils import validate_tools_single_input
from langchain.base_language import BaseLanguageModel
from langchain.callbacks.base import BaseCallbackManager
from langchain.chains import LLMChain
from langchain.prompts.base import BasePromptTemplate
from langchain.prompts.chat import (ChatPromptTemplate,
HumanMessagePromptTemplate,
MessagesPlaceholder,
SystemMessagePromptTemplate)
from langchain.schema import (AgentAction, AIMessage, BaseMessage,
BaseOutputParser, SystemMessage)
from langchain.tools.base import BaseTool


class CharacterChatAgent(Agent):
"""An agent designed to hold a conversation in addition to using tools."""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be more accurate to rephrase this as "An agent designed to hold a conversation as a given character in addition to using tools."

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as above. Incorporating.


output_parser: AgentOutputParser = Field(default_factory=ConvoOutputParser)

@classmethod
def _get_default_output_parser(cls, **kwargs: Any) -> AgentOutputParser:
return ConvoOutputParser()

@property
def _agent_type(self) -> str:
raise NotImplementedError

@property
def observation_prefix(self) -> str:
"""Prefix to append the observation with."""
return "Observation: "

@property
def llm_prefix(self) -> str:
"""Prefix to append the llm call with."""
return "Thought:"

@classmethod
def _validate_tools(cls, tools: Sequence[BaseTool]) -> None:
super()._validate_tools(tools)
validate_tools_single_input(cls.__name__, tools)

@classmethod
def create_prompt(
cls,
tools: Sequence[BaseTool],
system_message: str = PREFIX,
human_message: str = SUFFIX,
character_summary: str = CHARACTER_SUMMARY,
input_variables: Optional[List[str]] = None,
output_parser: Optional[BaseOutputParser] = None,
) -> BasePromptTemplate:
character_message = system_message.format(character_summary=character_summary)
tool_strings = "\n".join(
[f"> {tool.name}: {tool.description}" for tool in tools]
)
tool_names = ", ".join([tool.name for tool in tools])
_output_parser = output_parser or cls._get_default_output_parser()
format_instructions = character_message.format(
format_instructions=_output_parser.get_format_instructions()
)
final_prompt = format_instructions.format(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
final_prompt = format_instructions.format(
system_prompt = format_instructions.format(

how about naming it to system_prompt as it is no more final?

tool_names=tool_names, tools=tool_strings
)
if input_variables is None:
input_variables = ["input", "chat_history", "agent_scratchpad"]
messages = [
SystemMessagePromptTemplate.from_template(final_prompt),
MessagesPlaceholder(variable_name="chat_history"),
HumanMessagePromptTemplate.from_template(human_message),
MessagesPlaceholder(variable_name="agent_scratchpad"),
]
return ChatPromptTemplate(input_variables=input_variables, messages=messages)

def _construct_scratchpad(
self, intermediate_steps: List[Tuple[AgentAction, str]]
) -> List[BaseMessage]:
"""Construct the scratchpad that lets the agent continue its thought process."""
thoughts: List[BaseMessage] = []
for action, observation in intermediate_steps:
thoughts.append(AIMessage(content=action.log))
system_message = SystemMessage(
content=TEMPLATE_TOOL_RESPONSE.format(observation=observation)
)
thoughts.append(system_message)
return thoughts

@classmethod
def from_llm_and_tools(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw this piece of code in several places, ever considered inheriting ConversationalChatAgent?

cls,
llm: BaseLanguageModel,
tools: Sequence[BaseTool],
callback_manager: Optional[BaseCallbackManager] = None,
output_parser: Optional[AgentOutputParser] = None,
system_message: str = PREFIX,
human_message: str = SUFFIX,
input_variables: Optional[List[str]] = None,
**kwargs: Any,
) -> Agent:
"""Construct an agent from an LLM and tools."""
cls._validate_tools(tools)
_output_parser = output_parser or cls._get_default_output_parser()
prompt = cls.create_prompt(
tools,
system_message=system_message,
human_message=human_message,
input_variables=input_variables,
output_parser=_output_parser,
)
llm_chain = LLMChain(
llm=llm,
prompt=prompt,
callback_manager=callback_manager,
)
tool_names = [tool.name for tool in tools]
return cls(
llm_chain=llm_chain,
allowed_tools=tool_names,
output_parser=_output_parser,
**kwargs,
)
Loading