From 498380fba5080cedd40faf5ef9e318249f341586 Mon Sep 17 00:00:00 2001 From: Ionut Mihalache <67947900+ionut-mihalache-uipath@users.noreply.github.com> Date: Tue, 17 Mar 2026 21:34:10 +0200 Subject: [PATCH] feat: add integration test for files --- testcases/multimodal-invoke/input.json | 3 + testcases/multimodal-invoke/langgraph.json | 6 ++ testcases/multimodal-invoke/pyproject.toml | 16 +++ testcases/multimodal-invoke/run.sh | 21 ++++ testcases/multimodal-invoke/src/assert.py | 12 +++ testcases/multimodal-invoke/src/main.py | 116 +++++++++++++++++++++ 6 files changed, 174 insertions(+) create mode 100644 testcases/multimodal-invoke/input.json create mode 100644 testcases/multimodal-invoke/langgraph.json create mode 100644 testcases/multimodal-invoke/pyproject.toml create mode 100644 testcases/multimodal-invoke/run.sh create mode 100644 testcases/multimodal-invoke/src/assert.py create mode 100644 testcases/multimodal-invoke/src/main.py diff --git a/testcases/multimodal-invoke/input.json b/testcases/multimodal-invoke/input.json new file mode 100644 index 000000000..0d72708b1 --- /dev/null +++ b/testcases/multimodal-invoke/input.json @@ -0,0 +1,3 @@ +{ + "prompt": "Describe the content of this file in one sentence." +} diff --git a/testcases/multimodal-invoke/langgraph.json b/testcases/multimodal-invoke/langgraph.json new file mode 100644 index 000000000..a2f64dcb4 --- /dev/null +++ b/testcases/multimodal-invoke/langgraph.json @@ -0,0 +1,6 @@ +{ + "dependencies": ["."], + "graphs": { + "agent": "./src/main.py:graph" + } +} diff --git a/testcases/multimodal-invoke/pyproject.toml b/testcases/multimodal-invoke/pyproject.toml new file mode 100644 index 000000000..3c34afd16 --- /dev/null +++ b/testcases/multimodal-invoke/pyproject.toml @@ -0,0 +1,16 @@ +[project] +name = "multimodal-invoke" +version = "0.0.1" +description = "Test multimodal LLM invoke with file attachments via get_chat_model" +authors = [{ name = "John Doe", email = "john.doe@myemail.com" }] +dependencies = [ + "langgraph>=0.2.70", + "langchain-core>=0.3.34", + "langgraph-checkpoint-sqlite>=2.0.3", + "uipath-langchain[vertex,bedrock]", + "pydantic>=2.10.6", +] +requires-python = ">=3.11" + +[tool.uv.sources] +uipath-langchain = { path = "../../", editable = true } diff --git a/testcases/multimodal-invoke/run.sh b/testcases/multimodal-invoke/run.sh new file mode 100644 index 000000000..77c43f4d8 --- /dev/null +++ b/testcases/multimodal-invoke/run.sh @@ -0,0 +1,21 @@ +#!/bin/bash +set -e + +echo "Syncing dependencies..." +uv sync + +echo "Authenticating with UiPath..." +uv run uipath auth --client-id="$CLIENT_ID" --client-secret="$CLIENT_SECRET" --base-url="$BASE_URL" + +echo "Initializing the project..." +uv run uipath init + +echo "Packing agent..." +uv run uipath pack + +echo "Running agent..." +uv run uipath run agent --file input.json + +echo "Running agent again with empty UIPATH_JOB_KEY..." +export UIPATH_JOB_KEY="" +uv run uipath run agent --trace-file .uipath/traces.jsonl --file input.json >> local_run_output.log diff --git a/testcases/multimodal-invoke/src/assert.py b/testcases/multimodal-invoke/src/assert.py new file mode 100644 index 000000000..cd4e554b6 --- /dev/null +++ b/testcases/multimodal-invoke/src/assert.py @@ -0,0 +1,12 @@ +import json + +with open("__uipath/output.json", "r", encoding="utf-8") as f: + output_data = json.load(f) + +output_content = output_data["output"] +result_summary = output_content["result_summary"] + +print(f"Success: {output_content['success']}") +print(f"Summary:\n{result_summary}") + +assert output_content["success"] is True, "Test did not succeed. See summary above." diff --git a/testcases/multimodal-invoke/src/main.py b/testcases/multimodal-invoke/src/main.py new file mode 100644 index 000000000..da618b43e --- /dev/null +++ b/testcases/multimodal-invoke/src/main.py @@ -0,0 +1,116 @@ +import logging + +from langchain_core.messages import AIMessage, HumanMessage +from langgraph.checkpoint.memory import MemorySaver +from langgraph.graph import END, START, StateGraph, MessagesState +from pydantic import BaseModel, Field + +from uipath_langchain.agent.multimodal.invoke import llm_call_with_files +from uipath_langchain.agent.multimodal.types import FileInfo +from uipath_langchain.chat.chat_model_factory import get_chat_model + +logger = logging.getLogger(__name__) + +MODELS_TO_TEST = [ + "gpt-4.1-2025-04-14", + "gemini-2.5-pro", + "anthropic.claude-sonnet-4-5-20250929-v1:0", +] + +FILES_TO_TEST = [ + FileInfo( + url="https://www.w3schools.com/css/img_5terre.jpg", + name="img_5terre.jpg", + mime_type="image/jpeg", + ), + FileInfo( + url="https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf", + name="dummy.pdf", + mime_type="application/pdf", + ), +] + + +class GraphInput(BaseModel): + prompt: str = Field(default="Describe the content of this file in one sentence.") + + +class GraphOutput(BaseModel): + success: bool + result_summary: str + + +class GraphState(MessagesState): + prompt: str + success: bool + result_summary: str + model_results: dict + + +async def run_multimodal_invoke(state: GraphState) -> dict: + messages = [HumanMessage(content=state["prompt"])] + model_results = {} + + for model_name in MODELS_TO_TEST: + logger.info(f"Testing {model_name}...") + model = get_chat_model( + model=model_name, + temperature=0.0, + max_tokens=200, + agenthub_config="agentsplayground", + ) + test_results = {} + for file_info in FILES_TO_TEST: + label = file_info.name + logger.info(f" {label}...") + try: + response: AIMessage = await llm_call_with_files( + messages, [file_info], model + ) + logger.info(f" {label}: ✓") + test_results[label] = "✓" + except Exception as e: + logger.error(f" {label}: ✗ {e}") + test_results[label] = f"✗ {str(e)[:60]}" + model_results[model_name] = test_results + + summary_lines = [] + for model_name, results in model_results.items(): + summary_lines.append(f"{model_name}:") + for file_name, result in results.items(): + summary_lines.append(f" {file_name}: {result}") + has_failures = any( + "✗" in v for results in model_results.values() for v in results.values() + ) + + return { + "success": not has_failures, + "result_summary": "\n".join(summary_lines), + "model_results": model_results, + } + + +async def return_results(state: GraphState) -> GraphOutput: + logger.info(f"Success: {state['success']}") + logger.info(f"Summary:\n{state['result_summary']}") + return GraphOutput( + success=state["success"], + result_summary=state["result_summary"], + ) + + +def build_graph() -> StateGraph: + builder = StateGraph(GraphState, input_schema=GraphInput, output_schema=GraphOutput) + + builder.add_node("run_multimodal_invoke", run_multimodal_invoke) + builder.add_node("results", return_results) + + builder.add_edge(START, "run_multimodal_invoke") + builder.add_edge("run_multimodal_invoke", "results") + builder.add_edge("results", END) + + memory = MemorySaver() + return builder.compile(checkpointer=memory) + + +graph = build_graph()