Skip to content

Commit 35833b4

Browse files
authored
Merge pull request #86 from UiPath/feat/slack_agent_smaple
samples: add slack agent sample
2 parents 5ce28c4 + d8fbbe1 commit 35833b4

File tree

6 files changed

+2583
-0
lines changed

6 files changed

+2583
-0
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
config:
3+
flowchart:
4+
curve: linear
5+
---
6+
graph TD;
7+
__start__([<p>__start__</p>]):::first
8+
agent(agent)
9+
tools(tools)
10+
__end__([<p>__end__</p>]):::last
11+
__start__ --> agent;
12+
agent -.-> __end__;
13+
agent -.-> tools;
14+
tools --> agent;
15+
classDef default fill:#f2f0ff,line-height:1.2
16+
classDef first fill-opacity:0
17+
classDef last fill:#bfb6fc
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": "./main.py:make_graph"
5+
},
6+
"env": ".env"
7+
}

samples/github-slack-agent/main.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import os
2+
from contextlib import asynccontextmanager
3+
4+
import dotenv
5+
from langchain_mcp_adapters.tools import load_mcp_tools
6+
from langgraph.prebuilt import create_react_agent
7+
from langgraph.prebuilt.chat_agent_executor import AgentState
8+
from mcp import ClientSession
9+
from mcp.client.sse import sse_client
10+
from uipath_langchain.chat.models import UiPathAzureChatOpenAI
11+
12+
dotenv.load_dotenv()
13+
14+
15+
GITHUB_MCP_SERVER_URL = os.getenv("GITHUB_MCP_SERVER_URL")
16+
SLACK_MCP_SERVER_URL = os.getenv("SLACK_MCP_SERVER_URL")
17+
SLACK_CHANNEL_ID = os.getenv("SLACK_CHANNEL_ID")
18+
UIPATH_ACCESS_TOKEN = os.getenv("UIPATH_ACCESS_TOKEN")
19+
20+
21+
@asynccontextmanager
22+
async def make_graph():
23+
async with sse_client(
24+
url=GITHUB_MCP_SERVER_URL,
25+
headers={"Authorization": f"Bearer {UIPATH_ACCESS_TOKEN}"},
26+
timeout=60,
27+
) as (git_read, git_write):
28+
async with ClientSession(git_read, git_write) as git_session:
29+
all_github_tools = await load_mcp_tools(git_session)
30+
31+
async with sse_client(
32+
url=SLACK_MCP_SERVER_URL,
33+
headers={"Authorization": f"Bearer {UIPATH_ACCESS_TOKEN}"},
34+
timeout=60,
35+
) as (slack_read, slack_write):
36+
async with ClientSession(slack_read, slack_write) as slack_session:
37+
all_slack_tools = await load_mcp_tools(slack_session)
38+
39+
# Keep only the necessary tools
40+
# LLMs get confused with too many choices
41+
allowed_git_tool_names = {
42+
"get_pull_request",
43+
"get_pull_request_files",
44+
"get_file_contents",
45+
}
46+
47+
allowed_slack_tool_names = {
48+
"slack_post_message",
49+
"slack_reply_to_thread",
50+
}
51+
52+
github_tools = [
53+
tool
54+
for tool in all_github_tools
55+
if tool.name in allowed_git_tool_names
56+
]
57+
slack_tools = [
58+
tool
59+
for tool in all_slack_tools
60+
if tool.name in allowed_slack_tool_names
61+
]
62+
63+
all_tools = github_tools + slack_tools
64+
65+
model = UiPathAzureChatOpenAI(
66+
model="gpt-4.1-2025-04-14",
67+
temperature=0,
68+
max_tokens=10000,
69+
timeout=120,
70+
max_retries=2,
71+
)
72+
73+
def system_prompt(state: AgentState) -> AgentState:
74+
system_message = f"""
75+
You are a professional senior Python developer and GitHub reviewer.
76+
77+
YOU MUST FOLLOW THESE RULES WITHOUT EXCEPTION:
78+
79+
1. ALWAYS begin your review by reading the contents of the changed files.
80+
2. ONLY use the contents of the changed files as context — do not assume.
81+
3. If you encounter an issue or uncertainty, explain clearly or return an error.
82+
4. DO NOT skip steps or speculate — be factual and grounded in the code.
83+
84+
At the end of your review, you MUST post a message to Slack channel `{SLACK_CHANNEL_ID}` using the `slack_post_message` tool.
85+
This first post MUST include the GitHub Pull Request Title, number, repo and URL: https://github.com/owner/repo/pull/number nicely formatted for SLACK.
86+
87+
Afterward, you MUST use the `slack_reply_to_thread` tool to reply to the thread with a detailed review.
88+
89+
FORMAT THE REVIEW MESSAGE AS FOLLOWS, USING SLACK MARKDOWN:
90+
91+
*🧠 Summary:*
92+
Briefly explain what the pull request does.
93+
94+
*✅ Pros:*
95+
• List strengths of the code
96+
• Mention clarity, structure, naming, tests, etc.
97+
98+
*❌ Issues:*
99+
• `filename.py` line 42: Describe the issue
100+
• Be precise and line-specific
101+
102+
*💡 Suggestions:*
103+
• Recommend cleanups, refactors, or improvements
104+
105+
Wrap multi-line code suggestions in triple backticks (```python ... ```).
106+
End with:
107+
108+
_This review was generated automatically._
109+
"""
110+
111+
return [{"role": "system", "content": system_message}] + state["messages"]
112+
113+
agent = create_react_agent(
114+
model,
115+
tools=all_tools,
116+
prompt=system_prompt,
117+
)
118+
119+
yield agent
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[project]
2+
name = "github-slack-agent"
3+
version = "0.0.1"
4+
description = "An automated agent that reviews GitHub pull requests and provides feedback on Slack"
5+
authors = [{ name = "Cristi Pufu", email = "[email protected]" }]
6+
dependencies = [
7+
"uipath-langchain==0.0.93",
8+
"langgraph>=0.3.34",
9+
"langchain-mcp-adapters>=0.0.9"
10+
]
11+
requires-python = ">=3.10"
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
{
2+
"entryPoints": [
3+
{
4+
"filePath": "agent",
5+
"uniqueId": "8d9655f0-1977-4a7e-9083-7d5a1e8233f1",
6+
"type": "agent",
7+
"input": {
8+
"type": "object",
9+
"properties": {
10+
"messages": {
11+
"items": {
12+
"additionalProperties": true,
13+
"description": "Base abstract message class.\n\nMessages are the inputs and outputs of ChatModels.",
14+
"properties": {
15+
"content": {
16+
"anyOf": [
17+
{
18+
"type": "string"
19+
},
20+
{
21+
"items": {
22+
"anyOf": [
23+
{
24+
"type": "string"
25+
},
26+
{
27+
"additionalProperties": true,
28+
"type": "object"
29+
}
30+
]
31+
},
32+
"type": "array"
33+
}
34+
],
35+
"title": "Content"
36+
},
37+
"additional_kwargs": {
38+
"additionalProperties": true,
39+
"title": "Additional Kwargs",
40+
"type": "object"
41+
},
42+
"response_metadata": {
43+
"additionalProperties": true,
44+
"title": "Response Metadata",
45+
"type": "object"
46+
},
47+
"type": {
48+
"title": "Type",
49+
"type": "string"
50+
},
51+
"name": {
52+
"anyOf": [
53+
{
54+
"type": "string"
55+
},
56+
{
57+
"type": "null"
58+
}
59+
],
60+
"default": null,
61+
"title": "Name"
62+
},
63+
"id": {
64+
"anyOf": [
65+
{
66+
"type": "string"
67+
},
68+
{
69+
"type": "null"
70+
}
71+
],
72+
"default": null,
73+
"title": "Id"
74+
}
75+
},
76+
"required": [
77+
"content",
78+
"type"
79+
],
80+
"title": "BaseMessage",
81+
"type": "object"
82+
},
83+
"title": "Messages",
84+
"type": "array"
85+
}
86+
},
87+
"required": [
88+
"messages"
89+
]
90+
},
91+
"output": {
92+
"type": "object",
93+
"properties": {
94+
"messages": {
95+
"items": {
96+
"additionalProperties": true,
97+
"description": "Base abstract message class.\n\nMessages are the inputs and outputs of ChatModels.",
98+
"properties": {
99+
"content": {
100+
"anyOf": [
101+
{
102+
"type": "string"
103+
},
104+
{
105+
"items": {
106+
"anyOf": [
107+
{
108+
"type": "string"
109+
},
110+
{
111+
"additionalProperties": true,
112+
"type": "object"
113+
}
114+
]
115+
},
116+
"type": "array"
117+
}
118+
],
119+
"title": "Content"
120+
},
121+
"additional_kwargs": {
122+
"additionalProperties": true,
123+
"title": "Additional Kwargs",
124+
"type": "object"
125+
},
126+
"response_metadata": {
127+
"additionalProperties": true,
128+
"title": "Response Metadata",
129+
"type": "object"
130+
},
131+
"type": {
132+
"title": "Type",
133+
"type": "string"
134+
},
135+
"name": {
136+
"anyOf": [
137+
{
138+
"type": "string"
139+
},
140+
{
141+
"type": "null"
142+
}
143+
],
144+
"default": null,
145+
"title": "Name"
146+
},
147+
"id": {
148+
"anyOf": [
149+
{
150+
"type": "string"
151+
},
152+
{
153+
"type": "null"
154+
}
155+
],
156+
"default": null,
157+
"title": "Id"
158+
}
159+
},
160+
"required": [
161+
"content",
162+
"type"
163+
],
164+
"title": "BaseMessage",
165+
"type": "object"
166+
},
167+
"title": "Messages",
168+
"type": "array"
169+
}
170+
},
171+
"required": [
172+
"messages"
173+
]
174+
}
175+
}
176+
],
177+
"bindings": {
178+
"version": "2.0",
179+
"resources": []
180+
}
181+
}

0 commit comments

Comments
 (0)