Source code for aidputils.agents.toolkit.agent_name

import re
from typing import Literal, Sequence, TypeGuard, cast

from langchain_core.language_models import LanguageModelLike
from langchain_core.messages import (
    AIMessage,
    BaseMessage,
    MessageLikeRepresentation,
    convert_to_messages,
)
from langchain_core.prompt_values import PromptValue
from langchain_core.runnables import RunnableLambda

NAME_PATTERN = re.compile(r"<name>(.*?)</name>", re.DOTALL)
CONTENT_PATTERN = re.compile(r"<content>(.*?)</content>", re.DOTALL)

AgentNameMode = Literal["inline"]


def _is_content_blocks_content(content: list[dict | str] | str) -> TypeGuard[list[dict]]:
    return (
        isinstance(content, list)
        and len(content) > 0
        and isinstance(content[0], dict)
        and "type" in content[0]
    )


[docs] def add_inline_agent_name(message: BaseMessage) -> BaseMessage: """Add name and content XML tags to the message content. Examples: >>> add_inline_agent_name(AIMessage(content="Hello", name="assistant")) AIMessage(content="<name>assistant</name><content>Hello</content>", name="assistant") >>> add_inline_agent_name(AIMessage(content=[{"type": "text", "text": "Hello"}], name="assistant")) AIMessage(content=[{"type": "text", "text": "<name>assistant</name><content>Hello</content>"}], name="assistant") """ if not isinstance(message, AIMessage) or not message.name: return message formatted_message = message.model_copy() if _is_content_blocks_content(message.content): text_blocks = [block for block in message.content if block["type"] == "text"] # type: ignore[invalid-argument-type] non_text_blocks = [block for block in message.content if block["type"] != "text"] # type: ignore[invalid-argument-type] content = text_blocks[0]["text"] if text_blocks else "" formatted_content = f"<name>{message.name}</name><content>{content}</content>" formatted_message_content = [{"type": "text", "text": formatted_content}] + non_text_blocks formatted_message.content = formatted_message_content else: formatted_message.content = ( f"<name>{message.name}</name><content>{formatted_message.content}</content>" ) return formatted_message
[docs] def remove_inline_agent_name(message: BaseMessage) -> BaseMessage: """Remove explicit name and content XML tags from the AI message content. Examples: >>> remove_inline_agent_name(AIMessage(content="<name>assistant</name><content>Hello</content>", name="assistant")) AIMessage(content="Hello", name="assistant") >>> remove_inline_agent_name(AIMessage(content=[{"type": "text", "text": "<name>assistant</name><content>Hello</content>"}], name="assistant")) AIMessage(content=[{"type": "text", "text": "Hello"}], name="assistant") """ if not isinstance(message, AIMessage) or not message.content: return message if is_content_blocks_content := _is_content_blocks_content(message.content): text_blocks = [block for block in message.content if block["type"] == "text"] # type: ignore[invalid-argument-type] if not text_blocks: return message non_text_blocks = [block for block in message.content if block["type"] != "text"] # type: ignore[invalid-argument-type] content = text_blocks[0]["text"] else: content = message.content name_match: re.Match | None = NAME_PATTERN.search(content) content_match: re.Match | None = CONTENT_PATTERN.search(content) if not name_match or not content_match: return message parsed_content = content_match.group(1) parsed_message = message.model_copy() if is_content_blocks_content: content_blocks = non_text_blocks if parsed_content: content_blocks = [{"type": "text", "text": parsed_content}] + content_blocks parsed_message.content = cast(list[str | dict], content_blocks) else: parsed_message.content = parsed_content return parsed_message
[docs] def with_agent_name( model: LanguageModelLike, agent_name_mode: AgentNameMode, ) -> LanguageModelLike: """Attach formatted agent names to the messages passed to and from a language model. This is useful for making a message history with multiple agents more coherent. NOTE: agent name is consumed from the message.name field. If you're using an agent built with create_react_agent, name is automatically set. If you're building a custom agent, make sure to set the name on the AI message returned by the LLM. Args: model: Language model to add agent name formatting to. agent_name_mode: Use to specify how to expose the agent name to the LLM. - "inline": Add the agent name directly into the content field of the AI message using XML-style tags. Example: "How can I help you" -> "<name>agent_name</name><content>How can I help you?</content>". """ if agent_name_mode == "inline": process_input_message = add_inline_agent_name process_output_message = remove_inline_agent_name else: raise ValueError( f"Invalid agent name mode: {agent_name_mode}. Needs to be one of: {AgentNameMode.__args__}" ) def process_input_messages( input: Sequence[MessageLikeRepresentation] | PromptValue, ) -> list[BaseMessage]: messages = convert_to_messages(input) return [process_input_message(message) for message in messages] chain = ( process_input_messages | model | RunnableLambda(process_output_message, name="process_output_message") ) return cast(LanguageModelLike, chain)