-
Notifications
You must be signed in to change notification settings - Fork 16
Update conversation history handling #24
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
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR updates conversation history handling to normalize conversation data across different endpoints and provide consistent access to conversation history for guardrails. The main goal is to ensure that conversation history is accessible by all guardrail checks regardless of which API endpoint is used.
- Extracts conversation normalization logic to a dedicated
conversation.pyutility module - Updates
GuardrailAgentto use session-based conversation history when available, falling back to input conversation - Fixes streaming to provide proper conversation history context during output guardrails
Reviewed Changes
Copilot reviewed 16 out of 17 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
src/guardrails/utils/conversation.py |
New module providing conversation normalization utilities across different API formats |
src/guardrails/agents.py |
Refactors agent conversation handling to use sessions and normalized conversation history |
src/guardrails/client.py |
Updates client methods to use normalized conversation handling |
src/guardrails/_streaming.py |
Fixes streaming to include conversation history in output guardrail checks |
src/guardrails/resources/responses/responses.py |
Updates to use normalized conversation history |
src/guardrails/resources/chat/chat.py |
Updates to use normalized conversation history |
tests/unit/test_agents.py |
Updates tests for new agent conversation handling patterns |
tests/unit/test_client_sync.py |
Updates test assertions for normalized conversation format |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| """Normalized representation of a conversation item. | ||
| Attributes: | ||
| role: Logical speaker role (user, assistant, system, tool, etc.). | ||
| type: Optional type discriminator for non-message items such as | ||
| ``function_call`` or ``function_call_output``. | ||
| content: Primary text payload for message-like items. | ||
| tool_name: Name of the tool/function associated with the entry. | ||
| arguments: Serialized tool/function arguments when available. | ||
| output: Serialized tool result payload when available. | ||
| call_id: Identifier that links tool calls and outputs. |
Copilot
AI
Oct 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ConversationEntry class is missing a comprehensive docstring that should explain its purpose as a normalized conversation item and provide examples of usage.
| """Normalized representation of a conversation item. | |
| Attributes: | |
| role: Logical speaker role (user, assistant, system, tool, etc.). | |
| type: Optional type discriminator for non-message items such as | |
| ``function_call`` or ``function_call_output``. | |
| content: Primary text payload for message-like items. | |
| tool_name: Name of the tool/function associated with the entry. | |
| arguments: Serialized tool/function arguments when available. | |
| output: Serialized tool result payload when available. | |
| call_id: Identifier that links tool calls and outputs. | |
| """ | |
| Represents a normalized item in a conversation history, such as a user message, | |
| assistant response, tool/function call, or tool output. This class provides a | |
| consistent structure for conversation entries, regardless of the originating | |
| API or provider, enabling guardrails to process and reason about conversation | |
| turns in a uniform way. | |
| Each entry may represent a message (from user, assistant, or system), a tool | |
| call (function invocation), or a tool output (function result). The fields | |
| capture the semantic role, type, content, and any associated tool/function | |
| metadata. | |
| Attributes: | |
| role: Logical speaker role (e.g., "user", "assistant", "system", "tool"). | |
| type: Optional type discriminator for non-message items such as | |
| "function_call" or "function_call_output". | |
| content: Primary text payload for message-like items. | |
| tool_name: Name of the tool/function associated with the entry. | |
| arguments: Serialized tool/function arguments when available. | |
| output: Serialized tool result payload when available. | |
| call_id: Identifier that links tool calls and outputs. | |
| Example: | |
| >>> # User message | |
| >>> entry = ConversationEntry(role="user", content="Hello, assistant!") | |
| >>> entry.to_payload() | |
| {'role': 'user', 'content': 'Hello, assistant!'} | |
| >>> # Assistant response | |
| >>> entry = ConversationEntry(role="assistant", content="Hello! How can I help you?") | |
| >>> entry.to_payload() | |
| {'role': 'assistant', 'content': 'Hello! How can I help you?'} | |
| >>> # Tool/function call | |
| >>> entry = ConversationEntry( | |
| ... role="assistant", | |
| ... type="function_call", | |
| ... tool_name="get_weather", | |
| ... arguments='{"location": "London"}', | |
| ... call_id="abc123" | |
| ... ) | |
| >>> entry.to_payload() | |
| { | |
| ... 'role': 'assistant', | |
| ... 'type': 'function_call', | |
| ... 'tool_name': 'get_weather', | |
| ... 'arguments': '{"location": "London"}', | |
| ... 'call_id': 'abc123' | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my opinion this is an unnecessary expansion of the current doc string. The current version is concise and matches the codebase style.
| return text | ||
| return _extract_text(content.get("content")) | ||
|
|
||
| if isinstance(content, Sequence) and not isinstance(content, bytes | bytearray): |
Copilot
AI
Oct 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The union syntax bytes | bytearray requires Python 3.10+. Consider using (bytes, bytearray) for broader compatibility.
| if isinstance(content, Sequence) and not isinstance(content, bytes | bytearray): | |
| if isinstance(content, Sequence) and not isinstance(content, (bytes, bytearray)): |
| """Stub tool context carrying name, arguments, and optional call id.""" | ||
|
|
||
| tool_name: str | ||
| tool_arguments: dict[str, Any] | ||
| tool_arguments: dict[str, Any] | str | ||
| tool_call_id: str | None = None |
Copilot
AI
Oct 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The union syntax dict[str, Any] | str and str | None require Python 3.10+. Consider using Union[dict[str, Any], str] and Optional[str] for broader compatibility.
| _user_messages: ContextVar[list[str]] = ContextVar("user_messages", default=[]) # noqa: B039 | ||
| # Context variables used to expose conversation information during guardrail checks. | ||
| _agent_session: ContextVar[Any | None] = ContextVar("guardrails_agent_session", default=None) | ||
| _agent_conversation: ContextVar[tuple[dict[str, Any], ...] | None] = ContextVar( |
Copilot
AI
Oct 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The union syntax tuple[dict[str, Any], ...] | None requires Python 3.10+. Consider using Optional[tuple[dict[str, Any], ...]] for broader compatibility.
| llm_stream: Any, # coroutine or async iterator of OpenAI chunks | ||
| preflight_results: list[GuardrailResult], | ||
| input_results: list[GuardrailResult], | ||
| conversation_history: list[dict[str, Any]] | None = None, |
Copilot
AI
Oct 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The union syntax list[dict[str, Any]] | None requires Python 3.10+. Consider using Optional[list[dict[str, Any]]] for broader compatibility.
| llm_stream: Any, # iterator of OpenAI chunks | ||
| preflight_results: list[GuardrailResult], | ||
| input_results: list[GuardrailResult], | ||
| conversation_history: list[dict[str, Any]] | None = None, |
Copilot
AI
Oct 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The union syntax list[dict[str, Any]] | None requires Python 3.10+. Consider using Optional[list[dict[str, Any]]] for broader compatibility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Updates handling of conversation history
conversation.pyhelperprevious_response_idfor conversation history withresponsesendpointGuardrailAgentusessessionif passed intoRunner.run(). Otherwise treats theinputas the conversation history