Skip to content
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
17 changes: 17 additions & 0 deletions samples/github-slack-agent/agent.mermaid
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
config:
flowchart:
curve: linear
---
graph TD;
__start__([<p>__start__</p>]):::first
agent(agent)
tools(tools)
__end__([<p>__end__</p>]):::last
__start__ --> agent;
agent -.-> __end__;
agent -.-> tools;
tools --> agent;
classDef default fill:#f2f0ff,line-height:1.2
classDef first fill-opacity:0
classDef last fill:#bfb6fc
7 changes: 7 additions & 0 deletions samples/github-slack-agent/langgraph.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"dependencies": ["."],
"graphs": {
"agent": "./main.py:make_graph"
},
"env": ".env"
}
119 changes: 119 additions & 0 deletions samples/github-slack-agent/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import os
from contextlib import asynccontextmanager

import dotenv
from langchain_mcp_adapters.tools import load_mcp_tools
from langgraph.prebuilt import create_react_agent
from langgraph.prebuilt.chat_agent_executor import AgentState
from mcp import ClientSession
from mcp.client.sse import sse_client
from uipath_langchain.chat.models import UiPathAzureChatOpenAI

dotenv.load_dotenv()


GITHUB_MCP_SERVER_URL = os.getenv("GITHUB_MCP_SERVER_URL")
SLACK_MCP_SERVER_URL = os.getenv("SLACK_MCP_SERVER_URL")
SLACK_CHANNEL_ID = os.getenv("SLACK_CHANNEL_ID")
UIPATH_ACCESS_TOKEN = os.getenv("UIPATH_ACCESS_TOKEN")


@asynccontextmanager
async def make_graph():
async with sse_client(
url=GITHUB_MCP_SERVER_URL,
headers={"Authorization": f"Bearer {UIPATH_ACCESS_TOKEN}"},
timeout=60,
) as (git_read, git_write):
async with ClientSession(git_read, git_write) as git_session:
all_github_tools = await load_mcp_tools(git_session)

async with sse_client(
url=SLACK_MCP_SERVER_URL,
headers={"Authorization": f"Bearer {UIPATH_ACCESS_TOKEN}"},
timeout=60,
) as (slack_read, slack_write):
async with ClientSession(slack_read, slack_write) as slack_session:
all_slack_tools = await load_mcp_tools(slack_session)

# Keep only the necessary tools
# LLMs get confused with too many choices
allowed_git_tool_names = {
"get_pull_request",
"get_pull_request_files",
"get_file_contents",
}

allowed_slack_tool_names = {
"slack_post_message",
"slack_reply_to_thread",
}

github_tools = [
tool
for tool in all_github_tools
if tool.name in allowed_git_tool_names
]
slack_tools = [
tool
for tool in all_slack_tools
if tool.name in allowed_slack_tool_names
]

all_tools = github_tools + slack_tools

model = UiPathAzureChatOpenAI(
model="gpt-4.1-2025-04-14",
temperature=0,
max_tokens=10000,
timeout=120,
max_retries=2,
)

def system_prompt(state: AgentState) -> AgentState:
system_message = f"""
You are a professional senior Python developer and GitHub reviewer.

YOU MUST FOLLOW THESE RULES WITHOUT EXCEPTION:

1. ALWAYS begin your review by reading the contents of the changed files.
2. ONLY use the contents of the changed files as context — do not assume.
3. If you encounter an issue or uncertainty, explain clearly or return an error.
4. DO NOT skip steps or speculate — be factual and grounded in the code.

At the end of your review, you MUST post a message to Slack channel `{SLACK_CHANNEL_ID}` using the `slack_post_message` tool.
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.

Afterward, you MUST use the `slack_reply_to_thread` tool to reply to the thread with a detailed review.

FORMAT THE REVIEW MESSAGE AS FOLLOWS, USING SLACK MARKDOWN:

*🧠 Summary:*
Briefly explain what the pull request does.

*✅ Pros:*
• List strengths of the code
• Mention clarity, structure, naming, tests, etc.

*❌ Issues:*
• `filename.py` line 42: Describe the issue
• Be precise and line-specific

*💡 Suggestions:*
• Recommend cleanups, refactors, or improvements

Wrap multi-line code suggestions in triple backticks (```python ... ```).
End with:

_This review was generated automatically._
"""

return [{"role": "system", "content": system_message}] + state["messages"]

agent = create_react_agent(
model,
tools=all_tools,
prompt=system_prompt,
)

yield agent
11 changes: 11 additions & 0 deletions samples/github-slack-agent/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[project]
name = "github-slack-agent"
version = "0.0.1"
description = "An automated agent that reviews GitHub pull requests and provides feedback on Slack"
authors = [{ name = "Cristi Pufu", email = "[email protected]" }]
dependencies = [
"uipath-langchain==0.0.93",
"langgraph>=0.3.34",
"langchain-mcp-adapters>=0.0.9"
]
requires-python = ">=3.10"
181 changes: 181 additions & 0 deletions samples/github-slack-agent/uipath.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
{
"entryPoints": [
{
"filePath": "agent",
"uniqueId": "8d9655f0-1977-4a7e-9083-7d5a1e8233f1",
"type": "agent",
"input": {
"type": "object",
"properties": {
"messages": {
"items": {
"additionalProperties": true,
"description": "Base abstract message class.\n\nMessages are the inputs and outputs of ChatModels.",
"properties": {
"content": {
"anyOf": [
{
"type": "string"
},
{
"items": {
"anyOf": [
{
"type": "string"
},
{
"additionalProperties": true,
"type": "object"
}
]
},
"type": "array"
}
],
"title": "Content"
},
"additional_kwargs": {
"additionalProperties": true,
"title": "Additional Kwargs",
"type": "object"
},
"response_metadata": {
"additionalProperties": true,
"title": "Response Metadata",
"type": "object"
},
"type": {
"title": "Type",
"type": "string"
},
"name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Name"
},
"id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Id"
}
},
"required": [
"content",
"type"
],
"title": "BaseMessage",
"type": "object"
},
"title": "Messages",
"type": "array"
}
},
"required": [
"messages"
]
},
"output": {
"type": "object",
"properties": {
"messages": {
"items": {
"additionalProperties": true,
"description": "Base abstract message class.\n\nMessages are the inputs and outputs of ChatModels.",
"properties": {
"content": {
"anyOf": [
{
"type": "string"
},
{
"items": {
"anyOf": [
{
"type": "string"
},
{
"additionalProperties": true,
"type": "object"
}
]
},
"type": "array"
}
],
"title": "Content"
},
"additional_kwargs": {
"additionalProperties": true,
"title": "Additional Kwargs",
"type": "object"
},
"response_metadata": {
"additionalProperties": true,
"title": "Response Metadata",
"type": "object"
},
"type": {
"title": "Type",
"type": "string"
},
"name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Name"
},
"id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Id"
}
},
"required": [
"content",
"type"
],
"title": "BaseMessage",
"type": "object"
},
"title": "Messages",
"type": "array"
}
},
"required": [
"messages"
]
}
}
],
"bindings": {
"version": "2.0",
"resources": []
}
}
Loading