-
Notifications
You must be signed in to change notification settings - Fork 31
add deeprag tool #455
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
add deeprag tool #455
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
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
159 changes: 159 additions & 0 deletions
159
src/uipath_langchain/agent/tools/internal_tools/batch_transform_tool.py
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,159 @@ | ||
| """Batch Transform tool for creating and retrieving batch transformations.""" | ||
|
|
||
| import uuid | ||
| from typing import Any | ||
|
|
||
| from langchain_core.language_models import BaseChatModel | ||
| from langchain_core.messages.tool import ToolCall | ||
| from langchain_core.tools import BaseTool, StructuredTool | ||
| from langgraph.types import interrupt | ||
| from uipath.agent.models.agent import ( | ||
| AgentInternalBatchTransformToolProperties, | ||
| AgentInternalToolResourceConfig, | ||
| ) | ||
| from uipath.eval.mocks import mockable | ||
| from uipath.platform import UiPath | ||
| from uipath.platform.common import CreateBatchTransform | ||
| from uipath.platform.common.interrupt_models import WaitEphemeralIndex | ||
| from uipath.platform.context_grounding import ( | ||
| BatchTransformOutputColumn, | ||
| EphemeralIndexUsage, | ||
| ) | ||
| from uipath.platform.context_grounding.context_grounding_index import ( | ||
| ContextGroundingIndex, | ||
| ) | ||
|
|
||
| from uipath_langchain.agent.react.jsonschema_pydantic_converter import create_model | ||
| from uipath_langchain.agent.react.types import AgentGraphState | ||
| from uipath_langchain.agent.tools.internal_tools.schema_utils import ( | ||
| add_query_field_to_schema, | ||
| ) | ||
| from uipath_langchain.agent.tools.static_args import handle_static_args | ||
| from uipath_langchain.agent.tools.structured_tool_with_argument_properties import ( | ||
| StructuredToolWithArgumentProperties, | ||
| ) | ||
| from uipath_langchain.agent.tools.tool_node import ToolWrapperReturnType | ||
| from uipath_langchain.agent.tools.utils import sanitize_tool_name | ||
|
|
||
|
|
||
| def create_batch_transform_tool( | ||
| resource: AgentInternalToolResourceConfig, llm: BaseChatModel | ||
| ) -> StructuredTool: | ||
| """Create a Batch Transform internal tool from resource configuration.""" | ||
| if not isinstance(resource.properties, AgentInternalBatchTransformToolProperties): | ||
| raise ValueError( | ||
| f"Expected AgentInternalBatchTransformToolProperties, got {type(resource.properties)}" | ||
| ) | ||
|
|
||
| tool_name = sanitize_tool_name(resource.name) | ||
| properties = resource.properties | ||
| settings = properties.settings | ||
|
|
||
| # Extract settings | ||
| query_setting = settings.query | ||
| folder_path_prefix_setting = settings.folder_path_prefix | ||
| output_columns_setting = settings.output_columns | ||
| web_search_grounding_setting = settings.web_search_grounding | ||
|
|
||
| is_query_static = query_setting and query_setting.variant == "static" | ||
| static_query = query_setting.value if is_query_static else None | ||
|
|
||
| static_folder_path_prefix = None | ||
| if folder_path_prefix_setting: | ||
| static_folder_path_prefix = getattr(folder_path_prefix_setting, "value", None) | ||
|
|
||
| static_web_search = False | ||
| if web_search_grounding_setting: | ||
| value = getattr(web_search_grounding_setting, "value", None) | ||
| static_web_search = value == "Enabled" if value else False | ||
|
|
||
| batch_transform_output_columns = [ | ||
| BatchTransformOutputColumn(name=col.name, description=col.description or "") | ||
| for col in output_columns_setting | ||
| ] | ||
|
|
||
| # Use resource input schema and add query field if dynamic | ||
| input_schema = dict(resource.input_schema) | ||
| if not is_query_static: | ||
| add_query_field_to_schema( | ||
| input_schema, | ||
| query_description=query_setting.description if query_setting else None, | ||
| default_description="Describe the task: what to research, what to synthesize.", | ||
| ) | ||
|
|
||
| # Create input model from modified schema | ||
| input_model = create_model(input_schema) | ||
| output_model = create_model(resource.output_schema) | ||
|
|
||
| @mockable( | ||
| name=resource.name, | ||
| description=resource.description, | ||
| input_schema=input_model.model_json_schema() if input_model else None, | ||
| output_schema=output_model.model_json_schema(), | ||
| example_calls=[], # Examples cannot be provided for internal tools | ||
| ) | ||
| async def batch_transform_tool_fn(**kwargs: Any) -> dict[str, Any]: | ||
| query = kwargs.get("query") if not is_query_static else static_query | ||
| if not query: | ||
| raise ValueError("Query is required for Batch Transform tool") | ||
|
|
||
| if "attachment" not in kwargs: | ||
| raise ValueError("Argument 'attachment' is not available") | ||
|
|
||
| attachment = kwargs.get("attachment") | ||
| if not attachment: | ||
| raise ValueError("Attachment is required for Batch Transform tool") | ||
|
|
||
| attachment_id = getattr(attachment, "ID", None) | ||
| if not attachment_id: | ||
| raise ValueError("Attachment ID is required") | ||
|
|
||
| destination_path = kwargs.get("destination_path", "output.csv") | ||
|
|
||
| uipath = UiPath() | ||
| ephemeral_index = await uipath.context_grounding.create_ephemeral_index_async( | ||
| usage=EphemeralIndexUsage.BATCH_RAG, | ||
| attachments=[attachment_id], | ||
| ) | ||
|
|
||
| if ephemeral_index.in_progress_ingestion(): | ||
| ephemeral_index_dict = interrupt(WaitEphemeralIndex(index=ephemeral_index)) | ||
| ephemeral_index = ContextGroundingIndex(**ephemeral_index_dict) | ||
|
|
||
| return interrupt( | ||
| CreateBatchTransform( | ||
| name=f"task-{uuid.uuid4()}", | ||
| index_name=ephemeral_index.name, | ||
| index_id=ephemeral_index.id, | ||
| prompt=query, | ||
| output_columns=batch_transform_output_columns, | ||
| storage_bucket_folder_path_prefix=static_folder_path_prefix, | ||
| enable_web_search_grounding=static_web_search, | ||
| destination_path=destination_path, | ||
| is_ephemeral_index=True, | ||
| ) | ||
| ) | ||
|
|
||
| # Import here to avoid circular dependency | ||
| from uipath_langchain.agent.wrappers import get_job_attachment_wrapper | ||
|
|
||
| job_attachment_wrapper = get_job_attachment_wrapper(output_type=output_model) | ||
|
|
||
| async def batch_transform_tool_wrapper( | ||
| tool: BaseTool, | ||
| call: ToolCall, | ||
| state: AgentGraphState, | ||
| ) -> ToolWrapperReturnType: | ||
| call["args"] = handle_static_args(resource, state, call["args"]) | ||
| return await job_attachment_wrapper(tool, call, state) | ||
|
|
||
| tool = StructuredToolWithArgumentProperties( | ||
| name=tool_name, | ||
| description=resource.description, | ||
| args_schema=input_model, | ||
| coroutine=batch_transform_tool_fn, | ||
| output_type=output_model, | ||
| argument_properties=resource.argument_properties, | ||
| ) | ||
| tool.set_tool_wrappers(awrapper=batch_transform_tool_wrapper) | ||
| return tool | ||
142 changes: 142 additions & 0 deletions
142
src/uipath_langchain/agent/tools/internal_tools/deeprag_tool.py
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,142 @@ | ||
| """Deeprag tool for creation and retrieval of deeprags.""" | ||
|
|
||
| import uuid | ||
| from typing import Any | ||
|
|
||
| from langchain_core.language_models import BaseChatModel | ||
| from langchain_core.messages.tool import ToolCall | ||
| from langchain_core.tools import BaseTool, StructuredTool | ||
| from langgraph.types import interrupt | ||
| from uipath.agent.models.agent import ( | ||
| AgentInternalDeepRagToolProperties, | ||
| AgentInternalToolResourceConfig, | ||
| ) | ||
| from uipath.eval.mocks import mockable | ||
| from uipath.platform import UiPath | ||
| from uipath.platform.common import CreateDeepRag | ||
| from uipath.platform.common.interrupt_models import WaitEphemeralIndex | ||
| from uipath.platform.context_grounding import ( | ||
| CitationMode, | ||
| EphemeralIndexUsage, | ||
| ) | ||
| from uipath.platform.context_grounding.context_grounding_index import ( | ||
| ContextGroundingIndex, | ||
| ) | ||
|
|
||
| from uipath_langchain.agent.react.jsonschema_pydantic_converter import create_model | ||
| from uipath_langchain.agent.react.types import AgentGraphState | ||
| from uipath_langchain.agent.tools.internal_tools.schema_utils import ( | ||
| add_query_field_to_schema, | ||
| ) | ||
| from uipath_langchain.agent.tools.static_args import handle_static_args | ||
| from uipath_langchain.agent.tools.structured_tool_with_argument_properties import ( | ||
| StructuredToolWithArgumentProperties, | ||
| ) | ||
| from uipath_langchain.agent.tools.tool_node import ToolWrapperReturnType | ||
| from uipath_langchain.agent.tools.utils import sanitize_tool_name | ||
|
|
||
|
|
||
| def create_deeprag_tool( | ||
| resource: AgentInternalToolResourceConfig, llm: BaseChatModel | ||
| ) -> StructuredTool: | ||
| """Create a DeepRAG internal tool from resource configuration.""" | ||
| if not isinstance(resource.properties, AgentInternalDeepRagToolProperties): | ||
| raise ValueError( | ||
| f"Expected AgentInternalDeepRagToolProperties, got {type(resource.properties)}" | ||
| ) | ||
|
|
||
| tool_name = sanitize_tool_name(resource.name) | ||
| properties = resource.properties | ||
| settings = properties.settings | ||
|
|
||
| # Extract settings | ||
| query_setting = settings.query | ||
| citation_mode_setting = settings.citation_mode | ||
|
|
||
| citation_mode = ( | ||
| CitationMode(citation_mode_setting.value) | ||
| if citation_mode_setting | ||
| else CitationMode.INLINE | ||
| ) | ||
|
|
||
| is_query_static = query_setting and query_setting.variant == "static" | ||
| static_query = query_setting.value if is_query_static else None | ||
|
|
||
| input_schema = dict(resource.input_schema) | ||
| if not is_query_static: | ||
CalebMartinUiPath marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| add_query_field_to_schema( | ||
| input_schema, | ||
| query_description=query_setting.description if query_setting else None, | ||
| default_description="Describe the task: what to research across documents, what to synthesize and how to cite sources.", | ||
| ) | ||
|
|
||
| input_model = create_model(input_schema) | ||
| output_model = create_model(resource.output_schema) | ||
|
|
||
| @mockable( | ||
| name=resource.name, | ||
| description=resource.description, | ||
| input_schema=input_model.model_json_schema() if input_model else None, | ||
| output_schema=output_model.model_json_schema(), | ||
| example_calls=[], # Examples cannot be provided for internal tools | ||
| ) | ||
| async def deeprag_tool_fn(**kwargs: Any) -> dict[str, Any]: | ||
| query = kwargs.get("query") if not is_query_static else static_query | ||
| if not query: | ||
| raise ValueError("Query is required for DeepRAG tool") | ||
|
|
||
| if "attachment" not in kwargs: | ||
| raise ValueError("Argument 'attachment' is not available") | ||
|
|
||
| attachment = kwargs.get("attachment") | ||
| if not attachment: | ||
| raise ValueError("Attachment is required for DeepRAG tool") | ||
|
|
||
| attachment_id = getattr(attachment, "ID", None) | ||
| if not attachment_id: | ||
| raise ValueError("Attachment ID is required") | ||
|
|
||
| uipath = UiPath() | ||
| ephemeral_index = await uipath.context_grounding.create_ephemeral_index_async( | ||
| usage=EphemeralIndexUsage.DEEP_RAG, | ||
| attachments=[attachment_id], | ||
| ) | ||
|
|
||
| if ephemeral_index.in_progress_ingestion(): | ||
| ephemeral_index_dict = interrupt(WaitEphemeralIndex(index=ephemeral_index)) | ||
| ephemeral_index = ContextGroundingIndex(**ephemeral_index_dict) | ||
|
|
||
| return interrupt( | ||
| CreateDeepRag( | ||
| name=f"task-{uuid.uuid4()}", | ||
| index_name=ephemeral_index.name, | ||
| index_id=ephemeral_index.id, | ||
| prompt=query, | ||
| citation_mode=citation_mode, | ||
| is_ephemeral_index=True, | ||
| ) | ||
| ) | ||
|
|
||
| # Import here to avoid circular dependency | ||
| from uipath_langchain.agent.wrappers import get_job_attachment_wrapper | ||
|
|
||
| job_attachment_wrapper = get_job_attachment_wrapper(output_type=output_model) | ||
|
|
||
| async def deeprag_tool_wrapper( | ||
| tool: BaseTool, | ||
| call: ToolCall, | ||
| state: AgentGraphState, | ||
| ) -> ToolWrapperReturnType: | ||
| call["args"] = handle_static_args(resource, state, call["args"]) | ||
| return await job_attachment_wrapper(tool, call, state) | ||
|
|
||
| tool = StructuredToolWithArgumentProperties( | ||
| name=tool_name, | ||
| description=resource.description, | ||
| args_schema=input_model, | ||
| coroutine=deeprag_tool_fn, | ||
| output_type=output_model, | ||
| argument_properties=resource.argument_properties, | ||
| ) | ||
| tool.set_tool_wrappers(awrapper=deeprag_tool_wrapper) | ||
| return tool | ||
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
33 changes: 33 additions & 0 deletions
33
src/uipath_langchain/agent/tools/internal_tools/schema_utils.py
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,33 @@ | ||
| """Utility functions for internal tool schema manipulation.""" | ||
|
|
||
| from typing import Any | ||
|
|
||
|
|
||
| def add_query_field_to_schema( | ||
| input_schema: dict[str, Any], | ||
| query_description: str | None = None, | ||
| default_description: str = "Query or prompt for the operation.", | ||
| ) -> None: | ||
| """Add a dynamic query field to an input schema. | ||
|
|
||
| This modifies the input schema in-place by adding a 'query' property | ||
| and marking it as required. | ||
|
|
||
| Args: | ||
| input_schema: The JSON schema dict to modify | ||
| query_description: Custom description for the query field | ||
| default_description: Default description if query_description is not provided | ||
| """ | ||
| if "properties" not in input_schema: | ||
| input_schema["properties"] = {} | ||
|
|
||
| input_schema["properties"]["query"] = { | ||
| "type": "string", | ||
| "description": query_description if query_description else default_description, | ||
| } | ||
|
|
||
| if "required" not in input_schema: | ||
| input_schema["required"] = [] | ||
|
|
||
| if "query" not in input_schema["required"]: | ||
| input_schema["required"].append("query") |
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
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.
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.
can the args not be typed?
Uh oh!
There was an error while loading. Please reload this page.
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 problem with typing the args is that then I would have to duplicate the function. 1 for static which doesn't expect a query and 1 for dynamic which needs a query. which I could do idc