Skip to content

Commit 14a0a68

Browse files
author
Giulia Stefania Imbrea
committed
feat(tests): added integration tests
1 parent c24c5b0 commit 14a0a68

37 files changed

+16358
-2
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: Integration testing
2+
3+
on:
4+
push:
5+
branches: [ main, develop ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
jobs:
10+
integration-tests:
11+
runs-on: ubuntu-latest
12+
permissions:
13+
contents: read
14+
pull-requests: write
15+
16+
strategy:
17+
matrix:
18+
include:
19+
- build-dir: company-research-agent
20+
context: ./testcases/company-research-agent
21+
agent-input: '{"company_name":"uipath"}'
22+
- build-dir: simple-local-mcp
23+
context: ./testcases/simple-local-mcp
24+
agent-input: '{"messages": [{"type": "human", "content": "What is 2+2"}]}'
25+
- build-dir: ticket-classification
26+
context: ./testcases/ticket-classification
27+
agent-input: '{"message": "GET Assets API does not enforce proper permissions Assets.View", "ticket_id": "TICKET-2345"}'
28+
has-human-loop: "true"
29+
- build-dir: multi-agent-supervisor-researcher-coder
30+
context: ./testcases/multi-agent-supervisor-researcher-coder
31+
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."}'
32+
33+
steps:
34+
- name: Checkout code
35+
uses: actions/checkout@v4
36+
37+
- name: Set up Docker Buildx
38+
uses: docker/setup-buildx-action@v3
39+
40+
- name: Build Docker image (${{ matrix.build-dir }})
41+
run: |
42+
docker build -f Dockerfile \
43+
-t ${{ matrix.build-dir }}:test \
44+
--build-arg CLIENT_ID="${{ secrets.ALPHA_TEST_CLIENT_ID }}" \
45+
--build-arg CLIENT_SECRET="${{ secrets.ALPHA_TEST_CLIENT_SECRET }}" \
46+
--build-arg BASE_URL="${{ secrets.ALPHA_BASE_URL }}" \
47+
--build-arg AGENT_INPUT='${{ matrix.agent-input }}' \
48+
${{ matrix.has-human-loop == 'true' && '--build-arg SKIP_HUMAN_APPROVAL=false --build-arg USE_REGULAR_INTERRUPT=true' || '' }} \
49+
${{ matrix.context }}

Dockerfile

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
FROM ghcr.io/astral-sh/uv:python3.12-bookworm
2+
3+
WORKDIR /app
4+
5+
COPY . .
6+
7+
RUN uv sync
8+
9+
ARG CLIENT_ID
10+
ARG CLIENT_SECRET
11+
ARG BASE_URL
12+
ARG AGENT_INPUT
13+
ARG SKIP_HUMAN_APPROVAL=false
14+
ARG USE_REGULAR_INTERRUPT=false
15+
16+
# Validate required environment variables
17+
RUN if [ -z "$CLIENT_ID" ]; then echo "CLIENT_ID build arg is required" && exit 1; fi
18+
RUN if [ -z "$CLIENT_SECRET" ]; then echo "CLIENT_SECRET build arg is required" && exit 1; fi
19+
RUN if [ -z "$BASE_URL" ]; then echo "BASE_URL build arg is required" && exit 1; fi
20+
RUN if [ -z "$AGENT_INPUT" ]; then echo "AGENT_INPUT build arg is required" && exit 1; fi
21+
22+
# Set environment variables for runtime
23+
ENV CLIENT_ID=$CLIENT_ID
24+
ENV CLIENT_SECRET=$CLIENT_SECRET
25+
ENV BASE_URL=$BASE_URL
26+
ENV TAVILY_API_KEY=${TAVILY_API_KEY:-""}
27+
ENV UIPATH_TENANT_ID=${UIPATH_TENANT_ID:-""}
28+
29+
# Human-in-the-loop settings (only for agents that support it)
30+
ENV SKIP_HUMAN_APPROVAL=${SKIP_HUMAN_APPROVAL:-"false"}
31+
ENV USE_REGULAR_INTERRUPT=${USE_REGULAR_INTERRUPT:-"false"}
32+
33+
# Authenticate with UiPath during build
34+
RUN uv run uipath auth --client-id="$CLIENT_ID" --client-secret="$CLIENT_SECRET" --base-url="$BASE_URL"
35+
36+
RUN uv run uipath pack
37+
38+
RUN uv run uipath run agent "$AGENT_INPUT"
39+
40+
# Only run resume if both conditions are met:
41+
# 1. USE_REGULAR_INTERRUPT is explicitly set to true (agent supports interrupts)
42+
# 2. SKIP_HUMAN_APPROVAL is false (interrupt will actually happen)
43+
RUN if [ "${USE_REGULAR_INTERRUPT:-false}" = "true" ] && [ "${SKIP_HUMAN_APPROVAL:-false}" = "false" ]; then \
44+
echo "Running resume for agent with regular interrupt..."; \
45+
uv run uipath run agent '{"Answer": true}' --resume; \
46+
else \
47+
echo "Skipping resume - no interrupt expected or not supported"; \
48+
fi

pyproject.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ packages = ["src/uipath_langchain"]
6666
[tool.ruff]
6767
line-length = 88
6868
indent-width = 4
69-
exclude = ["samples/**"]
69+
exclude = ["samples/**", "testcases/**"]
7070

7171
[tool.ruff.lint]
7272
select = ["E", "F", "B", "I"]
@@ -85,7 +85,8 @@ plugins = [
8585
"pydantic.mypy"
8686
]
8787
exclude = [
88-
"samples/.*"
88+
"samples/.*",
89+
"testcases/.*"
8990
]
9091

9192
follow_imports = "silent"

testcases/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Project Samples
2+
3+
## [Multi agent supervisor, researcher, coder](multi-agent-supervisor-researcher-coder)
4+
This sample showcases a multi-agent system, involving a supervisor, a researcher, and a coder working in coordination to tackle complex tasks.
5+
6+
## [Multi agent planner, researcher, coder distributed](multi-agent-planner-researcher-coder-distributed)
7+
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.
8+
9+
## [RAG-quiz-generator](RAG-quiz-generator)
10+
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).
11+
12+
## [Retrieval chain](retrieval-chain)
13+
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.
14+
15+
## [Simple local MCP](simple-local-mcp)
16+
This sample shows how to create an agent using LangGraph with multiple tool servers via the MCP (Model Context Protocol) standard.
17+
18+
## [Simple remote MCP](simple-remote-mcp)
19+
This sample demonstrates the creation of an agent using LangGraph, which connects to a remote MCP (Model Context Protocol) Server.
20+
21+
## [Ticket classification](ticket-classification)
22+
This sample demonstrates automatic classification of support tickets into categories. It includes a human approval step via UiPath Action Center.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
UIPATH_URL=https://alpha.uipath.com/<organization>/<tenant>
2+
UIPATH_ACCESS_TOKEN=YOUR TOKEN HERE
3+
UIPATH_TENANT_ID=YOUR_TENANT_ID
4+
UIPATH_ORGANIZATION_ID=YOUR_ORGANIZATION_ID
5+
TAVILY_API_KEY=your_tavily_api_key
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
config:
3+
flowchart:
4+
curve: linear
5+
---
6+
graph TD;
7+
__start__([<p>__start__</p>]):::first
8+
__end__([<p>__end__</p>]):::last
9+
__start__ --> researcher___start__;
10+
researcher___end__ --> __end__;
11+
subgraph researcher
12+
researcher___start__(<p>__start__</p>)
13+
researcher_agent(agent)
14+
researcher_tools(tools)
15+
researcher___end__(<p>__end__</p>)
16+
researcher___start__ --> researcher_agent;
17+
researcher_tools --> researcher_agent;
18+
researcher_agent -.-> researcher_tools;
19+
researcher_agent -.-> researcher___end__;
20+
end
21+
classDef default fill:#f2f0ff,line-height:1.2
22+
classDef first fill-opacity:0
23+
classDef last fill:#bfb6fc
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import os
2+
from typing import Union
3+
4+
from langchain_community.tools import DuckDuckGoSearchResults
5+
from langchain_community.tools.tavily_search import TavilySearchResults
6+
from langgraph.graph import END, START, MessagesState, StateGraph
7+
from langgraph.prebuilt import create_react_agent
8+
from pydantic import BaseModel
9+
10+
from uipath_langchain.chat import UiPathAzureChatOpenAI
11+
12+
# Configuration constants
13+
MAX_SEARCH_RESULTS = 5
14+
DEFAULT_MODEL = "gpt-4o-2024-08-06"
15+
ALTERNATIVE_MODEL = "claude-3-5-sonnet-latest"
16+
17+
18+
def get_search_tool() -> Union[TavilySearchResults, DuckDuckGoSearchResults]:
19+
"""Get the appropriate search tool based on available API keys."""
20+
if os.getenv("TAVILY_API_KEY"):
21+
return TavilySearchResults(max_results=MAX_SEARCH_RESULTS)
22+
return DuckDuckGoSearchResults()
23+
24+
25+
# System prompt for the research agent
26+
SYSTEM_PROMPT = """You are an advanced AI assistant specializing in corporate research and outreach strategy development. Your primary functions are:
27+
1. Researching target companies: Gather comprehensive information about the specified company, including its history, industry position, products/services, and recent news.
28+
2. Analyzing organizational structures: Investigate and outline the company's internal structure, departments, and hierarchy.
29+
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.
30+
4. Developing outreach strategies: Based on the gathered information, create tailored strategies for effective communication and engagement with the company and its key personnel.
31+
32+
To accomplish these tasks, follow these steps:
33+
1. Use the TavilySearchResults tool to find recent and relevant information about the company.
34+
2. Analyze the collected data to form insights about the company's structure, key decision-makers, and potential outreach strategies.
35+
36+
When using the search tool:
37+
- Clearly state the purpose of each search.
38+
- Formulate effective search queries to find specific information about different aspects of the company.
39+
- If a search doesn't provide the expected information, try refining your query.
40+
41+
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.
42+
43+
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.
44+
45+
DO NOT do any math as specified in your instructions.
46+
"""
47+
48+
49+
def create_llm() -> UiPathAzureChatOpenAI:
50+
"""Create and configure the language model."""
51+
return UiPathAzureChatOpenAI(model=DEFAULT_MODEL)
52+
# Alternative model option:
53+
# return ChatAnthropic(model=ALTERNATIVE_MODEL)
54+
55+
56+
def create_research_agent():
57+
"""Create the research agent with configured LLM and tools."""
58+
llm = create_llm()
59+
search_tool = get_search_tool()
60+
return create_react_agent(llm, tools=[search_tool], prompt=SYSTEM_PROMPT)
61+
62+
63+
class GraphState(BaseModel):
64+
"""State model for the research graph."""
65+
company_name: str
66+
67+
68+
class GraphOutput(BaseModel):
69+
"""Output model for the research graph."""
70+
response: str
71+
72+
73+
def create_user_message(company_name: str) -> str:
74+
"""Create a formatted user message for company research."""
75+
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:
76+
77+
1. Company Overview
78+
2. Organizational Structure
79+
3. Key Decision-Makers
80+
4. Outreach Strategy
81+
5. Additional Information Needed (if applicable)
82+
83+
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.
84+
"""
85+
86+
87+
async def research_node(state: GraphState) -> GraphOutput:
88+
"""Research node that performs company analysis."""
89+
research_agent = create_research_agent()
90+
user_message = create_user_message(state.company_name)
91+
92+
# Create message state for the agent
93+
message_state = MessagesState(messages=[{"role": "user", "content": user_message}])
94+
95+
# Invoke the research agent
96+
result = await research_agent.ainvoke(message_state)
97+
98+
return GraphOutput(response=result["messages"][-1].content)
99+
100+
101+
def build_research_graph() -> StateGraph:
102+
"""Build and compile the research graph."""
103+
builder = StateGraph(GraphState, output=GraphOutput)
104+
105+
# Add nodes
106+
builder.add_node("researcher", research_node)
107+
108+
# Add edges
109+
builder.add_edge(START, "researcher")
110+
builder.add_edge("researcher", END)
111+
112+
return builder.compile()
113+
114+
115+
# Create the compiled graph
116+
graph = build_research_graph()
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"dependencies": ["."],
3+
"graphs": {
4+
"agent": "./graph.py:graph"
5+
},
6+
"env": ".env"
7+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
[project]
2+
name = "company-research-agent"
3+
version = "0.0.1"
4+
description = "Company research agent with Tavily web search"
5+
authors = [{ name = "John Doe", email = "[email protected]" }]
6+
7+
requires-python = ">=3.10"
8+
dependencies = [
9+
"langgraph>=0.2.55",
10+
"langchain-anthropic>=0.3.8",
11+
"tavily-python>=0.5.0",
12+
"uipath>=2.0.79",
13+
"uipath-langchain>=0.0.117",
14+
"duckduckgo-search>=8.1.1",
15+
"langchain-community>=0.3.21",
16+
"debugpy>=1.8.15",
17+
]
18+
19+
[project.optional-dependencies]
20+
dev = ["mypy>=1.11.1", "ruff>=0.6.1"]
21+
22+
[build-system]
23+
requires = ["setuptools>=73.0.0", "wheel"]
24+
build-backend = "setuptools.build_meta"
25+
26+
[tool.setuptools.package-data]
27+
"*" = ["py.typed"]
28+
29+
[tool.ruff]
30+
lint.select = [
31+
"E", # pycodestyle
32+
"F", # pyflakes
33+
"I", # isort
34+
"D", # pydocstyle
35+
"D401", # First line should be in imperative mood
36+
"T201",
37+
"UP",
38+
]
39+
lint.ignore = [
40+
"UP006",
41+
"UP007",
42+
"UP035",
43+
"D417",
44+
"E501",
45+
]
46+
47+
[tool.ruff.lint.per-file-ignores]
48+
"tests/*" = ["D", "UP"]
49+
50+
[[tool.uv.index]]
51+
name = "testpypi"
52+
url = "https://test.pypi.org/simple/"
53+
publish-url = "https://test.pypi.org/legacy/"
54+
explicit = true
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"entryPoints": [
3+
{
4+
"filePath": "agent",
5+
"uniqueId": "0bf221a3-6cf6-4b81-bce3-d0ce260521f2",
6+
"type": "agent",
7+
"input": {
8+
"type": "object",
9+
"properties": {
10+
"company_name": {
11+
"title": "Company Name",
12+
"type": "string"
13+
}
14+
},
15+
"required": [
16+
"company_name"
17+
]
18+
},
19+
"output": {
20+
"type": "object",
21+
"properties": {
22+
"response": {
23+
"title": "Response",
24+
"type": "string"
25+
}
26+
},
27+
"required": [
28+
"response"
29+
]
30+
}
31+
}
32+
],
33+
"bindings": {
34+
"version": "2.0",
35+
"resources": []
36+
}
37+
}

0 commit comments

Comments
 (0)