Skip to content

tests: added integration tests #128

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 1 commit into from
Jul 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions .github/workflows/integration_tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Integration testing

on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]

jobs:
integration-tests:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write

strategy:
matrix:
include:
- build-dir: company-research-agent
context: ./testcases/company-research-agent
agent-input: '{"company_name":"uipath"}'
- build-dir: simple-local-mcp
context: ./testcases/simple-local-mcp
agent-input: '{"messages": [{"type": "human", "content": "What is 2+2"}]}'
- build-dir: ticket-classification
context: ./testcases/ticket-classification
agent-input: '{"message": "GET Assets API does not enforce proper permissions Assets.View", "ticket_id": "TICKET-2345"}'
has-human-loop: "true"
- build-dir: multi-agent-supervisor-researcher-coder
context: ./testcases/multi-agent-supervisor-researcher-coder
agent-input: '{"question": "First, please state the Pythagorean theorem. Give only the formula, using variables a, b, and c. Then apply this formula to calculate the value when a=2 and b=3."}'

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow Medium

Unpinned 3rd party Action 'Integration testing' step
Uses Step
uses 'docker/setup-buildx-action' with ref 'v3', not a pinned commit hash

- name: Build Docker image (${{ matrix.build-dir }})
run: |
docker build -f Dockerfile \
-t ${{ matrix.build-dir }}:test \
--build-arg CLIENT_ID="${{ secrets.ALPHA_TEST_CLIENT_ID }}" \
--build-arg CLIENT_SECRET="${{ secrets.ALPHA_TEST_CLIENT_SECRET }}" \
--build-arg BASE_URL="${{ secrets.ALPHA_BASE_URL }}" \
--build-arg AGENT_INPUT='${{ matrix.agent-input }}' \
${{ matrix.has-human-loop == 'true' && '--build-arg SKIP_HUMAN_APPROVAL=false --build-arg USE_REGULAR_INTERRUPT=true' || '' }} \
${{ matrix.context }}
48 changes: 48 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
FROM ghcr.io/astral-sh/uv:python3.12-bookworm

WORKDIR /app

COPY . .

RUN uv sync

ARG CLIENT_ID
ARG CLIENT_SECRET
ARG BASE_URL
ARG AGENT_INPUT
ARG SKIP_HUMAN_APPROVAL=false
ARG USE_REGULAR_INTERRUPT=false

# Validate required environment variables
RUN if [ -z "$CLIENT_ID" ]; then echo "CLIENT_ID build arg is required" && exit 1; fi
RUN if [ -z "$CLIENT_SECRET" ]; then echo "CLIENT_SECRET build arg is required" && exit 1; fi
RUN if [ -z "$BASE_URL" ]; then echo "BASE_URL build arg is required" && exit 1; fi
RUN if [ -z "$AGENT_INPUT" ]; then echo "AGENT_INPUT build arg is required" && exit 1; fi

# Set environment variables for runtime
ENV CLIENT_ID=$CLIENT_ID
ENV CLIENT_SECRET=$CLIENT_SECRET
ENV BASE_URL=$BASE_URL
ENV TAVILY_API_KEY=${TAVILY_API_KEY:-""}
ENV UIPATH_TENANT_ID=${UIPATH_TENANT_ID:-""}

# Human-in-the-loop settings (only for agents that support it)
ENV SKIP_HUMAN_APPROVAL=${SKIP_HUMAN_APPROVAL:-"false"}
ENV USE_REGULAR_INTERRUPT=${USE_REGULAR_INTERRUPT:-"false"}

# Authenticate with UiPath during build
RUN uv run uipath auth --client-id="$CLIENT_ID" --client-secret="$CLIENT_SECRET" --base-url="$BASE_URL"

RUN uv run uipath pack

RUN uv run uipath run agent "$AGENT_INPUT"

# Only run resume if both conditions are met:
# 1. USE_REGULAR_INTERRUPT is explicitly set to true (agent supports interrupts)
# 2. SKIP_HUMAN_APPROVAL is false (interrupt will actually happen)
RUN if [ "${USE_REGULAR_INTERRUPT:-false}" = "true" ] && [ "${SKIP_HUMAN_APPROVAL:-false}" = "false" ]; then \
echo "Running resume for agent with regular interrupt..."; \
uv run uipath run agent '{"Answer": true}' --resume; \
else \
echo "Skipping resume - no interrupt expected or not supported"; \
fi
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ packages = ["src/uipath_langchain"]
[tool.ruff]
line-length = 88
indent-width = 4
exclude = ["samples/**"]
exclude = ["samples/**", "testcases/**"]

[tool.ruff.lint]
select = ["E", "F", "B", "I"]
Expand All @@ -85,7 +85,8 @@ plugins = [
"pydantic.mypy"
]
exclude = [
"samples/.*"
"samples/.*",
"testcases/.*"
]

follow_imports = "silent"
Expand Down
22 changes: 22 additions & 0 deletions testcases/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Project Samples

## [Multi agent supervisor, researcher, coder](multi-agent-supervisor-researcher-coder)
This sample showcases a multi-agent system, involving a supervisor, a researcher, and a coder working in coordination to tackle complex tasks.

## [Multi agent planner, researcher, coder distributed](multi-agent-planner-researcher-coder-distributed)
Here, a multi-agent system breaks down complex tasks into discreet steps, routing them to specialized agents. Each agent operates **independently** and can be deployed as a separate process. The system includes a planner, researcher, and coder agent.

## [RAG-quiz-generator](RAG-quiz-generator)
The RAG Sample project demonstrates the implementation of a Retrieval-Augmented Generation (RAG) system using [UiPath Context Grounding](https://docs.uipath.com/automation-cloud/automation-cloud/latest/admin-guide/about-context-grounding).

## [Retrieval chain](retrieval-chain)
This example shows how to retrieve relevant documents for a query, using the UiPath Context Grounding vectorstore. It also integrates this into a Langchain retrieval chain for response formulation.

## [Simple local MCP](simple-local-mcp)
This sample shows how to create an agent using LangGraph with multiple tool servers via the MCP (Model Context Protocol) standard.

## [Simple remote MCP](simple-remote-mcp)
This sample demonstrates the creation of an agent using LangGraph, which connects to a remote MCP (Model Context Protocol) Server.

## [Ticket classification](ticket-classification)
This sample demonstrates automatic classification of support tickets into categories. It includes a human approval step via UiPath Action Center.
5 changes: 5 additions & 0 deletions testcases/company-research-agent/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
UIPATH_URL=https://alpha.uipath.com/<organization>/<tenant>
UIPATH_ACCESS_TOKEN=YOUR TOKEN HERE
UIPATH_TENANT_ID=YOUR_TENANT_ID
UIPATH_ORGANIZATION_ID=YOUR_ORGANIZATION_ID
TAVILY_API_KEY=your_tavily_api_key
23 changes: 23 additions & 0 deletions testcases/company-research-agent/agent.mermaid
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
config:
flowchart:
curve: linear
---
graph TD;
__start__([<p>__start__</p>]):::first
__end__([<p>__end__</p>]):::last
__start__ --> researcher___start__;
researcher___end__ --> __end__;
subgraph researcher
researcher___start__(<p>__start__</p>)
researcher_agent(agent)
researcher_tools(tools)
researcher___end__(<p>__end__</p>)
researcher___start__ --> researcher_agent;
researcher_tools --> researcher_agent;
researcher_agent -.-> researcher_tools;
researcher_agent -.-> researcher___end__;
end
classDef default fill:#f2f0ff,line-height:1.2
classDef first fill-opacity:0
classDef last fill:#bfb6fc
116 changes: 116 additions & 0 deletions testcases/company-research-agent/graph.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import os
from typing import Union

from langchain_community.tools import DuckDuckGoSearchResults
from langchain_community.tools.tavily_search import TavilySearchResults
from langgraph.graph import END, START, MessagesState, StateGraph
from langgraph.prebuilt import create_react_agent
from pydantic import BaseModel

from uipath_langchain.chat import UiPathAzureChatOpenAI

# Configuration constants
MAX_SEARCH_RESULTS = 5
DEFAULT_MODEL = "gpt-4o-2024-08-06"
ALTERNATIVE_MODEL = "claude-3-5-sonnet-latest"


def get_search_tool() -> Union[TavilySearchResults, DuckDuckGoSearchResults]:
"""Get the appropriate search tool based on available API keys."""
if os.getenv("TAVILY_API_KEY"):
return TavilySearchResults(max_results=MAX_SEARCH_RESULTS)
return DuckDuckGoSearchResults()


# System prompt for the research agent
SYSTEM_PROMPT = """You are an advanced AI assistant specializing in corporate research and outreach strategy development. Your primary functions are:
1. Researching target companies: Gather comprehensive information about the specified company, including its history, industry position, products/services, and recent news.
2. Analyzing organizational structures: Investigate and outline the company's internal structure, departments, and hierarchy.
3. Identifying key decision-makers: Find and provide information on important executives and leaders within the company who are likely to be involved in decision-making processes.
4. Developing outreach strategies: Based on the gathered information, create tailored strategies for effective communication and engagement with the company and its key personnel.

To accomplish these tasks, follow these steps:
1. Use the TavilySearchResults tool to find recent and relevant information about the company.
2. Analyze the collected data to form insights about the company's structure, key decision-makers, and potential outreach strategies.

When using the search tool:
- Clearly state the purpose of each search.
- Formulate effective search queries to find specific information about different aspects of the company.
- If a search doesn't provide the expected information, try refining your query.

When responding, structure your output as a comprehensive analysis. Use clear section headers to organize the information. Provide concise, actionable insights. If you need more information to complete any part of your analysis, clearly state what additional details would be helpful.

Always maintain a professional and objective tone in your research and recommendations. Your goal is to provide accurate, valuable information that can be used to inform business decisions and outreach efforts.

DO NOT do any math as specified in your instructions.
"""


def create_llm() -> UiPathAzureChatOpenAI:
"""Create and configure the language model."""
return UiPathAzureChatOpenAI(model=DEFAULT_MODEL)
# Alternative model option:
# return ChatAnthropic(model=ALTERNATIVE_MODEL)


def create_research_agent():
"""Create the research agent with configured LLM and tools."""
llm = create_llm()
search_tool = get_search_tool()
return create_react_agent(llm, tools=[search_tool], prompt=SYSTEM_PROMPT)


class GraphState(BaseModel):
"""State model for the research graph."""
company_name: str


class GraphOutput(BaseModel):
"""Output model for the research graph."""
response: str


def create_user_message(company_name: str) -> str:
"""Create a formatted user message for company research."""
return f"""Please provide a comprehensive analysis and outreach strategy for the company: {company_name}. Use the TavilySearchResults tool to gather information. Include detailed research on the company's background, organizational structure, key decision-makers, and a tailored outreach strategy. Format your response using the following section headers:

1. Company Overview
2. Organizational Structure
3. Key Decision-Makers
4. Outreach Strategy
5. Additional Information Needed (if applicable)

Ensure that each section is clearly labeled and contains relevant, concise information. If you need any additional specific information about {company_name} or its industry to enhance your analysis, please include your questions in the 'Additional Information Needed' section.
"""


async def research_node(state: GraphState) -> GraphOutput:
"""Research node that performs company analysis."""
research_agent = create_research_agent()
user_message = create_user_message(state.company_name)

# Create message state for the agent
message_state = MessagesState(messages=[{"role": "user", "content": user_message}])

# Invoke the research agent
result = await research_agent.ainvoke(message_state)

return GraphOutput(response=result["messages"][-1].content)


def build_research_graph() -> StateGraph:
"""Build and compile the research graph."""
builder = StateGraph(GraphState, output=GraphOutput)

# Add nodes
builder.add_node("researcher", research_node)

# Add edges
builder.add_edge(START, "researcher")
builder.add_edge("researcher", END)

return builder.compile()


# Create the compiled graph
graph = build_research_graph()
7 changes: 7 additions & 0 deletions testcases/company-research-agent/langgraph.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"dependencies": ["."],
"graphs": {
"agent": "./graph.py:graph"
},
"env": ".env"
}
54 changes: 54 additions & 0 deletions testcases/company-research-agent/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
[project]
name = "company-research-agent"
version = "0.0.1"
description = "Company research agent with Tavily web search"
authors = [{ name = "John Doe", email = "[email protected]" }]

requires-python = ">=3.10"
dependencies = [
"langgraph>=0.2.55",
"langchain-anthropic>=0.3.8",
"tavily-python>=0.5.0",
"uipath>=2.0.79",
"uipath-langchain>=0.0.117",
"duckduckgo-search>=8.1.1",
"langchain-community>=0.3.21",
"debugpy>=1.8.15",
]

[project.optional-dependencies]
dev = ["mypy>=1.11.1", "ruff>=0.6.1"]

[build-system]
requires = ["setuptools>=73.0.0", "wheel"]
build-backend = "setuptools.build_meta"

[tool.setuptools.package-data]
"*" = ["py.typed"]

[tool.ruff]
lint.select = [
"E", # pycodestyle
"F", # pyflakes
"I", # isort
"D", # pydocstyle
"D401", # First line should be in imperative mood
"T201",
"UP",
]
lint.ignore = [
"UP006",
"UP007",
"UP035",
"D417",
"E501",
]

[tool.ruff.lint.per-file-ignores]
"tests/*" = ["D", "UP"]

[[tool.uv.index]]
name = "testpypi"
url = "https://test.pypi.org/simple/"
publish-url = "https://test.pypi.org/legacy/"
explicit = true
37 changes: 37 additions & 0 deletions testcases/company-research-agent/uipath.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"entryPoints": [
{
"filePath": "agent",
"uniqueId": "0bf221a3-6cf6-4b81-bce3-d0ce260521f2",
"type": "agent",
"input": {
"type": "object",
"properties": {
"company_name": {
"title": "Company Name",
"type": "string"
}
},
"required": [
"company_name"
]
},
"output": {
"type": "object",
"properties": {
"response": {
"title": "Response",
"type": "string"
}
},
"required": [
"response"
]
}
}
],
"bindings": {
"version": "2.0",
"resources": []
}
}
Loading