Skip to content

Commit 738043c

Browse files
committed
fix: github agent sample
1 parent 66b3a7a commit 738043c

File tree

3 files changed

+90
-46
lines changed

3 files changed

+90
-46
lines changed

.github/workflows/trigger-github-agent.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,4 @@ jobs:
1515
env:
1616
COMMENT_BODY: ${{ toJson(github.event.comment.body) }}
1717
run: |
18-
python -c "import requests; import json; import os; import re; import base64; comment_json = os.environ['COMMENT_BODY']; comment = json.loads(comment_json); command = re.search(r'/help\s+(\w+)', comment); command_value = command.group(1) if command else 'help'; number = ${{ github.event.issue.number || github.event.pull_request.number || 0 }}; payload = json.dumps({'owner': '${{ github.repository_owner }}', 'repo': '${{ github.event.repository.name }}', 'pullNumber': number, 'command': command_value, 'in_reply_to': ${{ github.event.comment.id }}}); resp = requests.post('${{ secrets.UIPATH_URL }}/orchestrator_/odata/Jobs/UiPath.Server.Configuration.OData.StartJobs', headers={'Authorization': 'Bearer ${{ secrets.UIPATH_ACCESS_TOKEN }}', 'Content-Type': 'application/json', 'X-UiPath-FolderPath': 'MCP Folder'}, json={'startInfo': {'releaseName': 'github-helper-agent', 'inputArguments': payload}}); print(f'Status code: {resp.status_code}'); print(f'Response: {resp.text}')"
18+
python -c "import requests; import json; import os; import re; import base64; comment_json = os.environ['COMMENT_BODY']; comment = json.loads(comment_json); command = re.search(r'/help\s+(\w+)', comment); command_value = command.group(1) if command else 'help'; number = ${{ github.event.issue.number || github.event.pull_request.number || 0 }}; payload = json.dumps({'owner': '${{ github.repository_owner }}', 'repo': '${{ github.event.repository.name }}', 'pullNumber': number, 'command': command_value, 'commentNumber': ${{ github.event.comment.id }}}); resp = requests.post('${{ secrets.UIPATH_URL }}/orchestrator_/odata/Jobs/UiPath.Server.Configuration.OData.StartJobs', headers={'Authorization': 'Bearer ${{ secrets.UIPATH_ACCESS_TOKEN }}', 'Content-Type': 'application/json', 'X-UiPath-FolderPath': 'MCP Folder'}, json={'startInfo': {'releaseName': 'github-helper-agent', 'inputArguments': payload}}); print(f'Status code: {resp.status_code}'); print(f'Response: {resp.text}')"

samples/github-helper-agent/main.py

Lines changed: 88 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,31 @@
1111
from langgraph.prebuilt.chat_agent_executor import AgentState
1212
from mcp import ClientSession
1313
from mcp.client.sse import sse_client
14-
from pydantic import Field
14+
from pydantic import BaseModel, Field
1515

1616
dotenv.load_dotenv()
1717

1818

19-
class PullRequestState(AgentState):
20-
"""State for the GitHub Assistant agent."""
19+
class PullRequestInfo(BaseModel):
20+
"""Input parameters with Pull Request details"""
2121

2222
owner: str
2323
repo: str
2424
pullNumber: int
25-
in_reply_to: Optional[int]
25+
commentNumber: Optional[int]
2626
command: str = Field(default="review")
2727

2828

29+
class GraphState(AgentState):
30+
"""Graph state"""
31+
32+
owner: str
33+
repo: str
34+
pull_number: int
35+
in_reply_to: Optional[int]
36+
command: str
37+
38+
2939
@asynccontextmanager
3040
async def make_graph():
3141
async with sse_client(
@@ -39,42 +49,38 @@ async def make_graph():
3949
model = ChatAnthropic(model="claude-3-5-sonnet-latest")
4050

4151
# Create the conversation history
42-
async def hydrate_history(state: PullRequestState) -> PullRequestState:
52+
async def hydrate_history(input: PullRequestInfo) -> GraphState:
4353
"""Fetch PR context at the start of the workflow."""
44-
owner = state["owner"]
45-
repo = state["repo"]
46-
pull_number = state["pullNumber"]
47-
command = state["command"]
48-
in_reply_to = state["in_reply_to"] if "in_reply_to" in state else None
4954

5055
context_messages = []
5156

5257
# Fetch PR details
5358
tool_result = await session.call_tool(
5459
"get_pull_request",
5560
{
56-
"owner": owner,
57-
"repo": repo,
58-
"pullNumber": pull_number,
61+
"owner": input.owner,
62+
"repo": input.repo,
63+
"pullNumber": input.pullNumber,
5964
},
6065
)
6166

6267
pr_details = json.loads(tool_result.content[0].text)
68+
pr_body = pr_details["body"] if "body" in pr_details else ""
6369

6470
# Add PR details as a system message
6571
context_messages.append(
6672
{
6773
"role": "system",
68-
"content": f"Pull Request #{pull_number} by {pr_details['user']['login']}\nTitle: {pr_details['title']}\nDescription: {pr_details['body']}",
74+
"content": f"Pull Request #{input.pullNumber} by {pr_details['user']['login']}\nTitle: {pr_details['title']}\nDescription: {pr_body}",
6975
}
7076
)
7177
# Fetch PR comments
7278
tool_result = await session.call_tool(
7379
"get_pull_request_comments",
7480
{
75-
"owner": owner,
76-
"repo": repo,
77-
"pullNumber": pull_number,
81+
"owner": input.owner,
82+
"repo": input.repo,
83+
"pullNumber": input.pullNumber,
7884
},
7985
)
8086

@@ -83,12 +89,12 @@ async def hydrate_history(state: PullRequestState) -> PullRequestState:
8389
# Add each comment as a user or assistant message
8490
for comment in comments:
8591
# Bot comments
86-
if comment["body"].startswith("/ai generated"):
92+
if comment["body"].startswith("_/ai generated_\n"):
8793
context_messages.append(
8894
{
8995
"role": "assistant",
9096
"content": comment["body"]
91-
.replace("/ai generated", "")
97+
.replace("_/ai generated_\n", "")
9298
.strip(),
9399
}
94100
)
@@ -97,26 +103,27 @@ async def hydrate_history(state: PullRequestState) -> PullRequestState:
97103
context_messages.append(
98104
{"role": "user", "content": comment["body"].strip()}
99105
)
106+
100107
# Fetch PR review comments
101-
review_comments = await session.call_tool(
108+
tool_result = await session.call_tool(
102109
"get_pull_request_reviews",
103110
{
104-
"owner": owner,
105-
"repo": repo,
106-
"pullNumber": pull_number,
111+
"owner": input.owner,
112+
"repo": input.repo,
113+
"pullNumber": input.pullNumber,
107114
},
108115
)
109116

110117
review_comments = json.loads(tool_result.content[0].text)
111118

112119
# Add review comments as messages
113120
for comment in review_comments:
114-
if comment["body"].startswith("/ai generated"):
121+
if comment["body"].startswith("_/ai generated_\n"):
115122
context_messages.append(
116123
{
117124
"role": "assistant",
118125
"content": comment["body"]
119-
.replace("/ai generated", "")
126+
.replace("_/ai generated_\n", "")
120127
.strip(),
121128
}
122129
)
@@ -127,34 +134,71 @@ async def hydrate_history(state: PullRequestState) -> PullRequestState:
127134
"content": f"Comment on {comment['path']} line {comment['line']}: {comment['body']}",
128135
}
129136
)
130-
# Add the input message as the last user message
131-
if in_reply_to:
137+
138+
# Fetch PR review comments
139+
tool_result = await session.call_tool(
140+
"get_issue_comments",
141+
{
142+
"owner": input.owner,
143+
"repo": input.repo,
144+
"issue_number": input.pullNumber,
145+
"page": 1,
146+
"per_page": 100
147+
},
148+
)
149+
150+
issue_comments = json.loads(tool_result.content[0].text)
151+
152+
# Add review comments as messages
153+
for comment in issue_comments:
154+
if comment["body"].startswith("_/ai generated_\n"):
132155
context_messages.append(
133156
{
134-
"role": "user",
135-
"content": f"The command is {command} with review comment id #{in_reply_to}",
157+
"role": "assistant",
158+
"content": comment["body"]
159+
.replace("_/ai generated_\n", "")
160+
.strip(),
136161
}
137162
)
138163
else:
139164
context_messages.append(
140-
{
141-
"role": "user",
142-
"content": f"Please {command} this PR and provide detailed feedback.",
143-
}
165+
{"role": "user", "content": comment["body"].strip()}
144166
)
145167

146-
# Update the state with the hydrated conversation history
147-
return {**state, "messages": context_messages}
168+
# Add the input message as the last user message
169+
if input.commentNumber:
170+
context_messages.append(
171+
{
172+
"role": "user",
173+
"content": f"The command is {input.command} with review comment id #{input.commentNumber}",
174+
}
175+
)
176+
else:
177+
context_messages.append(
178+
{
179+
"role": "user",
180+
"content": f"Please {input.command} this PR and provide detailed feedback.",
181+
}
182+
)
148183

149-
def pr_prompt(state: PullRequestState):
184+
# Update the state with the hydrated conversation history
185+
return {
186+
"owner": input.owner,
187+
"repo": input.repo,
188+
"pull_number": input.pullNumber,
189+
"in_reply_to": input.commentNumber,
190+
"messages": context_messages,
191+
}
192+
193+
def pr_prompt(state: GraphState) -> GraphState:
150194
"""Create a prompt that incorporates PR data and the requested command."""
151195
system_message = f"""You are a professional developer with experience in code reviews and GitHub pull requests.
152-
You are working with repo: {state["owner"]}/{state["repo"]}, PR #{state["pullNumber"]}.
196+
You are working with repo: {state["owner"]}/{state["repo"]}, PR #{state["pull_number"]}.
153197
154198
IMPORTANT INSTRUCTIONS:
155199
1. ALWAYS get the contents of the changed files in the current PR
156200
2. ALWAYS use the contents of the changed files as context when replying to a user command.
157-
3. Always start your responses with "/ai generated" to properly tag your comments.
201+
3. Always start your responses with "_/ai generated_\n" to properly tag your comments.
158202
4. When you reply to a comment, make sure to address the specific request.
159203
5. When reviewing code, be thorough but constructive - point out both issues and good practices.
160204
6. You MUST use the available tools to post your response as a PR comment or perform the PR code review.
@@ -185,20 +229,20 @@ def pr_prompt(state: PullRequestState):
185229
# Create the agent node - this will handle both analysis and posting the response
186230
# using its available GitHub tools
187231
agent = create_react_agent(
188-
model, tools=tools, state_schema=PullRequestState, prompt=pr_prompt
232+
model, tools=tools, state_schema=GraphState, prompt=pr_prompt
189233
)
190234

191235
# Create a simple two-node StateGraph
192-
workflow = StateGraph(PullRequestState)
236+
workflow = StateGraph(GraphState, input=PullRequestInfo)
193237

194238
# Add nodes
195239
workflow.add_node("hydrate_history", hydrate_history)
196-
workflow.add_node("agent", agent)
240+
workflow.add_node("github_agent", agent)
197241

198-
# Add edges - simple linear flow from context hydration to agent to end
242+
# Add edges - simple linear flow from "history hydration" to "GitHub MCP tools agent" to end
199243
workflow.add_edge("__start__", "hydrate_history")
200-
workflow.add_edge("hydrate_history", "agent")
201-
workflow.add_edge("agent", END)
244+
workflow.add_edge("hydrate_history", "github_agent")
245+
workflow.add_edge("github_agent", END)
202246

203247
# Compile the graph
204248
graph = workflow.compile()

samples/github-helper-agent/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "github-helper-agent"
3-
version = "0.0.1"
3+
version = "0.0.5"
44
description = "An automated agent that reviews GitHub pull requests and provides feedback"
55
authors = [{ name = "Cristi Pufu", email = "[email protected]" }]
66
dependencies = [

0 commit comments

Comments
 (0)