-
Notifications
You must be signed in to change notification settings - Fork 29
fix: update llm guardrail payload extraction #374
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
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
3213c29
fix: update llm guardrail payload extraction
ctiliescuuipath e3cf9ac
fix: rename to avoid conflicts
ctiliescuuipath efba4e2
fix: address comments
ctiliescuuipath 25eac41
Merge branch 'main' into fix_llm_guardrails_check
ctiliescuuipath ff5e04b
fix: bump up version
ctiliescuuipath File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,153 @@ | ||
| """Tests for guardrail utility functions.""" | ||
|
|
||
| import json | ||
|
|
||
| from langchain_core.messages import AIMessage, HumanMessage, ToolMessage | ||
|
|
||
| from uipath_langchain.agent.guardrails.utils import ( | ||
| _extract_tool_args_from_message, | ||
| _extract_tool_output_data, | ||
| _extract_tools_args_from_message, | ||
| get_message_content, | ||
| ) | ||
| from uipath_langchain.agent.react.types import AgentGuardrailsGraphState | ||
|
|
||
|
|
||
| class TestExtractToolArgsFromMessage: | ||
| """Tests for _extract_tool_args_from_message function.""" | ||
|
|
||
| def test_extracts_args_from_matching_tool(self): | ||
| """Should extract args from matching tool call.""" | ||
| message = AIMessage( | ||
| content="", | ||
| tool_calls=[ | ||
| { | ||
| "name": "test_tool", | ||
| "args": {"param1": "value1", "param2": 123}, | ||
| "id": "call_1", | ||
| } | ||
| ], | ||
| ) | ||
| result = _extract_tool_args_from_message(message, "test_tool") | ||
| assert result == {"param1": "value1", "param2": 123} | ||
|
|
||
| def test_returns_empty_dict_for_non_matching_tool(self): | ||
| """Should return empty dict when tool name doesn't match.""" | ||
| message = AIMessage( | ||
| content="", | ||
| tool_calls=[ | ||
| {"name": "other_tool", "args": {"data": "value"}, "id": "call_1"} | ||
| ], | ||
| ) | ||
| result = _extract_tool_args_from_message(message, "test_tool") | ||
| assert result == {} | ||
|
|
||
| def test_returns_empty_dict_for_non_ai_message(self): | ||
| """Should return empty dict when message is not AIMessage.""" | ||
| message = HumanMessage(content="Test message") | ||
| result = _extract_tool_args_from_message(message, "test_tool") | ||
| assert result == {} | ||
|
|
||
| def test_returns_first_matching_tool_when_multiple(self): | ||
| """Should return args from first matching tool call.""" | ||
| message = AIMessage( | ||
| content="", | ||
| tool_calls=[ | ||
| {"name": "test_tool", "args": {"first": "call"}, "id": "call_1"}, | ||
| {"name": "test_tool", "args": {"second": "call"}, "id": "call_2"}, | ||
| ], | ||
| ) | ||
| result = _extract_tool_args_from_message(message, "test_tool") | ||
| assert result == {"first": "call"} | ||
|
|
||
|
|
||
| class TestExtractToolsArgsFromMessage: | ||
| """Tests for _extract_tools_args_from_message function.""" | ||
|
|
||
| def test_extracts_args_from_all_tool_calls(self): | ||
| """Should extract args from all tool calls.""" | ||
| message = AIMessage( | ||
| content="", | ||
| tool_calls=[ | ||
| {"name": "tool1", "args": {"arg1": "val1"}, "id": "call_1"}, | ||
| {"name": "tool2", "args": {"arg2": "val2"}, "id": "call_2"}, | ||
| {"name": "tool3", "args": {"arg3": "val3"}, "id": "call_3"}, | ||
| ], | ||
| ) | ||
| result = _extract_tools_args_from_message(message) | ||
| assert result == [{"arg1": "val1"}, {"arg2": "val2"}, {"arg3": "val3"}] | ||
|
|
||
| def test_returns_empty_list_for_non_ai_message(self): | ||
| """Should return empty list when message is not AIMessage.""" | ||
| message = HumanMessage(content="Test message") | ||
| result = _extract_tools_args_from_message(message) | ||
| assert result == [] | ||
|
|
||
| def test_returns_empty_list_when_no_tool_calls(self): | ||
| """Should return empty list when AIMessage has no tool calls.""" | ||
| message = AIMessage(content="Test response") | ||
| result = _extract_tools_args_from_message(message) | ||
| assert result == [] | ||
|
|
||
|
|
||
| class TestExtractToolOutputData: | ||
| """Tests for _extract_tool_output_data function.""" | ||
|
|
||
| def test_extracts_json_dict_content(self): | ||
| """Should parse and return dict when content is JSON string.""" | ||
| json_content = json.dumps({"result": "success", "data": {"value": 42}}) | ||
| state = AgentGuardrailsGraphState( | ||
| messages=[ToolMessage(content=json_content, tool_call_id="call_1")] | ||
| ) | ||
| result = _extract_tool_output_data(state) | ||
| assert result == {"result": "success", "data": {"value": 42}} | ||
|
|
||
| def test_wraps_non_json_string_in_output_field(self): | ||
| """Should wrap non-JSON string content in 'output' field.""" | ||
| state = AgentGuardrailsGraphState( | ||
| messages=[ToolMessage(content="Plain text result", tool_call_id="call_1")] | ||
| ) | ||
| result = _extract_tool_output_data(state) | ||
| assert result == {"output": "Plain text result"} | ||
|
|
||
| def test_returns_empty_dict_for_empty_messages(self): | ||
| """Should return empty dict when state has no messages.""" | ||
| state = AgentGuardrailsGraphState(messages=[]) | ||
| result = _extract_tool_output_data(state) | ||
| assert result == {} | ||
|
|
||
| def test_returns_empty_dict_for_non_tool_message(self): | ||
| """Should return empty dict when last message is not ToolMessage.""" | ||
| state = AgentGuardrailsGraphState( | ||
| messages=[AIMessage(content="Not a tool message")] | ||
| ) | ||
| result = _extract_tool_output_data(state) | ||
| assert result == {} | ||
|
|
||
|
|
||
| class TestGetMessageContent: | ||
| """Tests for get_message_content function.""" | ||
|
|
||
| def test_extracts_string_content_from_human_message(self): | ||
| """Should extract string content from HumanMessage.""" | ||
| message = HumanMessage(content="Hello from human") | ||
| result = get_message_content(message) | ||
| assert result == "Hello from human" | ||
|
|
||
| def test_extracts_content_from_ai_message(self): | ||
| """Should extract content from AIMessage.""" | ||
| message = AIMessage(content="AI response") | ||
| result = get_message_content(message) | ||
| assert result == "AI response" | ||
|
|
||
| def test_extracts_content_from_tool_message(self): | ||
| """Should extract content from ToolMessage.""" | ||
| message = ToolMessage(content="Tool result", tool_call_id="call_1") | ||
| result = get_message_content(message) | ||
| assert result == "Tool result" | ||
|
|
||
| def test_handles_empty_content(self): | ||
| """Should handle empty content string.""" | ||
| message = AIMessage(content="") | ||
| result = get_message_content(message) | ||
| assert result == "" |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.