Github add "Create PR" tool + Docs update (#8235)

Added a new tool to the Github toolkit called **Create Pull Request.**
Now we can make our own langchain contributor in langchain 😁

In order to have somewhere to pull from, I also added a new env var,
"GITHUB_BASE_BRANCH." This will allow the existing env var,
"GITHUB_BRANCH," to be a working branch for the bot (so that it doesn't
have to always commit on the main/master). For example, if you want the
bot to work in a branch called `bot_dev` and your repo base is `main`,
you would set up the vars like:
```
GITHUB_BASE_BRANCH = "main"
GITHUB_BRANCH = "bot_dev"
``` 

Maintainer responsibilities:
  - Agents / Tools / Toolkits: @hinthornw
pull/8395/head
Gordon Clark 1 year ago committed by GitHub
parent ecd4aae818
commit e66759cc9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -57,7 +57,13 @@
}
],
"source": [
"loader = MWDumpLoader(\"example_data/testmw_pages_current.xml\", encoding=\"utf8\")\n",
"loader = MWDumpLoader(\n",
" file_path = \"example_data/testmw_pages_current.xml\", \n",
" encoding=\"utf8\",\n",
" #namespaces = [0,2,3] Optional list to load only specific namespaces. Loads all namespaces by default.\n",
" skip_redirects = True, #will skip over pages that just redirect to other pages (or not if False)\n",
" stop_on_error = False #will skip over pages that cause parsing errors (or not if False)\n",
" )\n",
"documents = loader.load()\n",
"print(f\"You have {len(documents)} document(s) in your data \")"
]

@ -4,18 +4,41 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"# GitHub\n",
"\n",
"This notebook goes over how to use the GitHub tool.\n",
"The GitHub tool allows agents to interact with a given GitHub repository. It implements CRUD operations for modifying files and can read/comment on Issues. The tool wraps the [PyGitHub](https://github.com/PyGithub/PyGithub) library.\n",
"\n",
"In order to interact with the GitHub API you must create a [GitHub app](https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/about-creating-github-apps). Next, you must set the following environment variables:\n",
"```\n",
"GITHUB_APP_ID\n",
"GITHUB_APP_PRIVATE_KEY\n",
"GITHUB_REPOSITORY\n",
"GITHUB_BRANCH\n",
"```"
"# Github Toolkit\n",
"\n",
"The Github toolkit contains tools that enable an LLM agent to interact with a github repository. The tools are a wrapper for the [PyGitHub](https://github.com/PyGithub/PyGithub) library. \n",
"\n",
"## Quickstart\n",
"1. Install the pygithub library\n",
"2. Create a Github app\n",
"3. Set your environmental variables\n",
"4. Pass the tools to your agent with `toolkit.get_tools()`\n",
"\n",
"Each of these steps will be explained in greate detail below.\n",
"\n",
"1. **Get Issues**- fetches issues from the repository.\n",
"\n",
"2. **Get Issue**- feteches details about a specific issue.\n",
"\n",
"3. **Comment on Issue**- posts a comment on a specific issue.\n",
"\n",
"4. **Create Pull Request**- creates a pull request from the bot's working branch to the base branch.\n",
"\n",
"5. **Create File**- creates a new file in the repository.\n",
"\n",
"6. **Read File**- reads a file from the repository.\n",
"\n",
"7. **Update File**- updates a file in the repository.\n",
"\n",
"8. **Delete File**- deletes a file from the repository.\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 1. Install the pygithub library"
]
},
{
@ -31,9 +54,44 @@
"%pip install pygithub"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 2. Create a Github App\n",
"\n",
"[Follow the instructions here](https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/registering-a-github-app) to create and register a Github app. Make sure your app has the following [repository permissions:](https://docs.github.com/en/rest/overview/permissions-required-for-github-apps?apiVersion=2022-11-28)\n",
"* Commit statuses (read only)\n",
"* Contents (read and write)\n",
"* Issues (read and write)\n",
"* Metadata (read only)\n",
"* Pull requests (read and write)\n",
"\n",
"\n",
"\n",
"Once the app has been registered, add it to the repository you wish the bot to act upon.\n",
"\n",
"## 3. Set Environmental Variables\n",
"\n",
"Before initializing your agent, the following environmental variables need to be set:\n",
"\n",
"* **GITHUB_APP_ID**- A six digit number found in your app's general settings\n",
"* **GITHUB_APP_PRIVATE_KEY**- The location of your app's private key .pem file\n",
"* **GITHUB_REPOSITORY**- The name of the Github repository you want your bot to act upon. Must follow the format {username}/{repo-name}. Make sure the app has been added to this repository first!\n",
"* **GITHUB_BRANCH**- The branch where the bot will make its commits. Defaults to 'master.'\n",
"* **GITHUB_BASE_BRANCH**- The base branch of your repo, usually either 'main' or 'master.' This is where pull requests will base from. Defaults to 'master.'\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Example Usage- Simple Agent"
]
},
{
"cell_type": "code",
"execution_count": 2,
"execution_count": 47,
"metadata": {},
"outputs": [],
"source": [
@ -47,20 +105,25 @@
},
{
"cell_type": "code",
"execution_count": 3,
"execution_count": 53,
"metadata": {},
"outputs": [],
"source": [
"os.environ[\"GITHUB_APP_ID\"] = \"your-github-app-id\"\n",
"os.environ[\"GITHUB_APP_PRIVATE_KEY\"] = \"/path/to/your/private/key\"\n",
"os.environ[\"GITHUB_REPOSITORY\"] = \"user/repo\"\n",
"os.environ[\"GITHUB_BRANCH\"] = \"branch-name\"\n",
"os.environ[\"OPENAI_API_KEY\"] = \"your-openai-api-key\""
"# Set your environment variables using os.environ\n",
"os.environ[\"GITHUB_APP_ID\"] = \"123456\"\n",
"os.environ[\"GITHUB_APP_PRIVATE_KEY\"] = \"path/to/your/private-key.pem\"\n",
"os.environ[\"GITHUB_REPOSITORY\"] = \"username/repo-name\"\n",
"os.environ[\"GITHUB_BRANCH\"] = \"bot-branch-name\"\n",
"os.environ[\"GITHUB_BASE_BRANCH\"] = \"main\"\n",
"\n",
"# This example also requires an OpenAI API key\n",
"os.environ[\"OPENAI_API_KEY\"] = \"\"\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"execution_count": 54,
"metadata": {},
"outputs": [],
"source": [
@ -74,7 +137,7 @@
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": 55,
"metadata": {},
"outputs": [
{
@ -84,42 +147,46 @@
"\n",
"\n",
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
"\u001b[32;1m\u001b[1;3m I need to figure out what issues need to be completed and how to complete them.\n",
"\u001b[32;1m\u001b[1;3m I need to figure out what issues need to be completed.\n",
"Action: Get Issues\n",
"Action Input: N/A\u001b[0m\n",
"Observation: \u001b[36;1m\u001b[1;3mFound 1 issues:\n",
"[{'title': 'Change the main script to print Hello AI!', 'number': 1}]\u001b[0m\n",
"[{'title': 'Update README file', 'number': 9}]\u001b[0m\n",
"Thought:\u001b[32;1m\u001b[1;3m I need to get more information about this issue.\n",
"Action: Get Issue\n",
"Action Input: 1\u001b[0m\n",
"Observation: \u001b[33;1m\u001b[1;3m{'title': 'Change the main script to print Hello AI!', 'body': None, 'comments': '[]'}\u001b[0m\n",
"Thought:\u001b[32;1m\u001b[1;3m I need to update the main script to print Hello AI!\n",
"Action Input: 9\u001b[0m\n",
"Observation: \u001b[33;1m\u001b[1;3m{\"title\": \"Update README file\", \"body\": \"Find what the most popular frontend framework is right now and add a short blurb to the readme.md file about how this website will take advantage of it.\", \"comments\": \"[]\"}\u001b[0m\n",
"Thought:\u001b[32;1m\u001b[1;3m I need to update the README file.\n",
"Action: Create File\n",
"Action Input: README.md\u001b[0m\n",
"Observation: \u001b[33;1m\u001b[1;3mFile already exists at README.md. Use update_file instead\u001b[0m\n",
"Thought:\u001b[32;1m\u001b[1;3m I need to update the existing README file.\n",
"Action: Update File\n",
"Action Input: main.py\n",
"Action Input: README.md\n",
"OLD <<<<\n",
"print(\"Hello World!\")\n",
"This is a sample website\n",
">>>> OLD\n",
"NEW <<<<\n",
"print(\"Hello AI!\")\n",
"This is a sample website that uses the most popular frontend framework.\n",
">>>> NEW\u001b[0m\n",
"Observation: \u001b[38;5;200m\u001b[1;3mFile content was not updated because the old content was not found. It may be helpful to use the read_file action to get the current file contents.\u001b[0m\n",
"Thought:\u001b[32;1m\u001b[1;3m I need to read the current file contents.\n",
"Observation: \u001b[36;1m\u001b[1;3mFile content was not updated because old content was not found.It may be helpful to use the read_file action to get the current file contents.\u001b[0m\n",
"Thought:\u001b[32;1m\u001b[1;3m I need to get the current file contents.\n",
"Action: Read File\n",
"Action Input: main.py\u001b[0m\n",
"Observation: \u001b[33;1m\u001b[1;3mprint(\"Hello world!\")\n",
"Action Input: README.md\u001b[0m\n",
"Observation: \u001b[38;5;200m\u001b[1;3mThis is my awesome website!\n",
"\u001b[0m\n",
"Thought:\u001b[32;1m\u001b[1;3m I need to update the main script to print Hello AI!\n",
"Thought:\u001b[32;1m\u001b[1;3m I need to update the README file with the new content.\n",
"Action: Update File\n",
"Action Input: main.py\n",
"Action Input: README.md\n",
"OLD <<<<\n",
"print(\"Hello world!\")\n",
"This is my awesome website!\n",
">>>> OLD\n",
"NEW <<<<\n",
"print(\"Hello AI!\")\n",
"This is my awesome website that uses the most popular frontend framework.\n",
">>>> NEW\u001b[0m\n",
"Observation: \u001b[38;5;200m\u001b[1;3mUpdated file main.py\u001b[0m\n",
"Thought:\u001b[32;1m\u001b[1;3m I now know the final answer\n",
"Final Answer: The main script has been updated to print \"Hello AI!\"\u001b[0m\n",
"Observation: \u001b[36;1m\u001b[1;3mUpdated file README.md\u001b[0m\n",
"Thought:\u001b[32;1m\u001b[1;3m I now know the final answer.\n",
"Final Answer: The README.md file has been updated with the new content.\u001b[0m\n",
"\n",
"\u001b[1m> Finished chain.\u001b[0m\n"
]
@ -127,10 +194,10 @@
{
"data": {
"text/plain": [
"'The main script has been updated to print \"Hello AI!\"'"
"'The README.md file has been updated with the new content.'"
]
},
"execution_count": 5,
"execution_count": 55,
"metadata": {},
"output_type": "execute_result"
}
@ -140,11 +207,161 @@
" \"You have the software engineering capabilities of a Google Principle engineer. You are tasked with completing issues on a github repository. Please look at the existing issues and complete them.\"\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Example Usage- Advanced Agent\n",
"\n",
"If your agent does not need to use all 8 tools, you can build tools individually to use. For this example, we'll make an agent that does not use the create_file, delete_file or create_pull_request tools, but can also use duckduckgo-search."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"%pip install duckduckgo-search"
]
},
{
"cell_type": "code",
"execution_count": 72,
"metadata": {},
"outputs": [],
"source": [
"from langchain.tools.github.tool import GitHubAction\n",
"from langchain.tools import DuckDuckGoSearchRun\n",
"from langchain.agents import Tool\n",
"from langchain.chat_models import ChatOpenAI\n",
"\n",
"tools = []\n",
"unwanted_tools = ['Get Issue','Delete File', 'Create File', 'Create Pull Request']\n",
"\n",
"for tool in toolkit.get_tools():\n",
" if tool.name not in unwanted_tools:\n",
" tools.append(tool)\n",
"tools+= [\n",
" Tool(\n",
" name = \"Search\",\n",
" func = DuckDuckGoSearchRun().run,\n",
" description = \"useful for when you need to search the web\"\n",
" )]\n",
" \n",
"agent = initialize_agent(\n",
" tools = tools,\n",
" llm = ChatOpenAI(temperature=0.1),\n",
" agent = AgentType.ZERO_SHOT_REACT_DESCRIPTION,\n",
" verbose = True\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finally let's build a prompt and test it out!"
]
},
{
"cell_type": "code",
"execution_count": 73,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
"\u001b[32;1m\u001b[1;3mTo complete this issue, I need to find the most popular frontend framework and add a blurb about how this website will utilize it to the readme.md file. I should start by researching the most popular frontend frameworks and then update the readme file accordingly. I will use the \"Search\" tool to research the most popular frontend framework.\n",
"\n",
"Action: Search\n",
"Action Input: \"most popular frontend framework\"\u001b[0m\n",
"Observation: \u001b[33;1m\u001b[1;3mAlex Ivanovs February 25, 2023 Table of Contents What are the current Front-end trends? Top Front-end Frameworks for 2023 #1 - React #2 - Angular #3 - Vue #4 - Svelte #5 - Preact #6 - Ember #7 - Solid #8 - Lit #9 - Alpine #10 - Stencil #11 - Qwik Front-end Frameworks: A Summary Top 6 Frontend Frameworks To Use in 2022 by Nwose Lotanna Victor August 26, 2022 Web 0 Comments This post reveals the top six frontend libraries to use in 2022. The list is fresh and very different from the previous years. State of JS Though React is the most popular framework for frontend development, it also has some shortcomings. Due to its limitations, the idea was to design a small-size framework that will offer the same features as React. This is how a tiny version of React — Preact — appeared. Top 10 Popular Frontend Frameworks to Use in 2023 Sep 26, 2022 10 min Сontents 1. What is a framework? 2. Front-end frameworks vs backend frameworks 3. The best front-end frameworks in 2023 React Vue.js Angular Svelte JQuery Ember Backbone Semantic UI 4. Final words Technostacks Jan 11 2023 Top Frontend Frameworks of 2023 for Web Development Developing what the users see on their screens is the role of a front-end web developer. Unarguably, front-end developers worldwide are trying to use the best front-end frameworks to provide the best user experience.\u001b[0m\n",
"Thought:\u001b[32;1m\u001b[1;3mBased on my research, the most popular frontend framework right now is React. I will now update the readme.md file to include a blurb about how this website will take advantage of React.\n",
"\n",
"Action: Update File\n",
"Action Input:\n",
"README.md\n",
"OLD <<<<\n",
"This is the readme file for the website.\n",
">>>> OLD\n",
"NEW <<<<\n",
"This is the readme file for the website.\n",
"\n",
"This website takes advantage of the React framework, which allows for efficient and reusable UI components. With React, we can easily manage the state of our application and create interactive user interfaces. It provides a smooth and seamless user experience, making this website highly responsive and dynamic.\n",
">>>> NEW\n",
"\u001b[0m\n",
"Observation: \u001b[36;1m\u001b[1;3mFile content was not updated because old content was not found.It may be helpful to use the read_file action to get the current file contents.\u001b[0m\n",
"Thought:\u001b[32;1m\u001b[1;3mI need to first read the contents of the README.md file to get the current content. Then I can update the file with the new content.\n",
"\n",
"Action: Read File\n",
"Action Input: README.md\u001b[0m\n",
"Observation: \u001b[38;5;200m\u001b[1;3mThis is my awesome website that uses the most popular frontend framework.\n",
"\u001b[0m\n",
"Thought:\u001b[32;1m\u001b[1;3mThe current content of the README.md file is \"This is my awesome website that uses the most popular frontend framework.\" I can now update the file with the new content.\n",
"\n",
"Action: Update File\n",
"Action Input:\n",
"README.md\n",
"OLD <<<<\n",
"This is my awesome website that uses the most popular frontend framework.\n",
">>>> OLD\n",
"NEW <<<<\n",
"This is my awesome website that uses the most popular frontend framework.\n",
"\n",
"This website takes advantage of the React framework, which allows for efficient and reusable UI components. With React, we can easily manage the state of our application and create interactive user interfaces. It provides a smooth and seamless user experience, making this website highly responsive and dynamic.\n",
">>>> NEW\n",
"\u001b[0m\n",
"Observation: \u001b[36;1m\u001b[1;3mUpdated file README.md\u001b[0m\n",
"Thought:\u001b[32;1m\u001b[1;3mI have successfully updated the README.md file with the blurb about how this website will take advantage of the React framework.\n",
"\n",
"Final Answer: The most popular frontend framework right now is React. This website takes advantage of React to create efficient and reusable UI components, manage application state, and provide a smooth and seamless user experience.\u001b[0m\n",
"\n",
"\u001b[1m> Finished chain.\u001b[0m\n"
]
},
{
"data": {
"text/plain": [
"'The most popular frontend framework right now is React. This website takes advantage of React to create efficient and reusable UI components, manage application state, and provide a smooth and seamless user experience.'"
]
},
"execution_count": 73,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# The GitHubAPIWrapper can be used outside of an agent, too\n",
"# This gets the info about issue number 9, since we want to\n",
"# force the agent to address this specific issue.\n",
"\n",
"issue = github.get_issue(9)\n",
"\n",
"prompt = f\"\"\"\n",
"You are a seinor frontend developer who is experienced in HTML, CSS, and JS- especially React.\n",
"You have been assigned the below issue. Complete it to the best of your ability.\n",
"Remember to first make a plan and pay attention to details like file names and commonsense.\n",
"Then execute the plan and use tools appropriately.\n",
"Finally, make a pull request to merge your changes.\n",
"Issue: {issue[\"title\"]}\n",
"Issue Description: {issue['body']}\n",
"Comments: {issue['comments']}\"\"\"\n",
"\n",
"agent.run(prompt)\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
@ -158,9 +375,8 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.4"
},
"orig_nbformat": 4
"version": "3.8.16"
}
},
"nbformat": 4,
"nbformat_minor": 2

@ -6,6 +6,7 @@ from langchain.tools import BaseTool
from langchain.tools.github.prompt import (
COMMENT_ON_ISSUE_PROMPT,
CREATE_FILE_PROMPT,
CREATE_PULL_REQUEST_PROMPT,
DELETE_FILE_PROMPT,
GET_ISSUE_PROMPT,
GET_ISSUES_PROMPT,
@ -41,6 +42,11 @@ class GitHubToolkit(BaseToolkit):
"name": "Comment on Issue",
"description": COMMENT_ON_ISSUE_PROMPT,
},
{
"mode": "create_pull_request",
"name": "Create Pull Request",
"description": CREATE_PULL_REQUEST_PROMPT,
},
{
"mode": "create_file",
"name": "Create File",

@ -14,7 +14,20 @@ This tool is useful when you need to comment on a GitHub issue. Simply pass in t
- Then you must place two newlines
- Then you must specify your comment
"""
CREATE_PULL_REQUEST_PROMPT = """
This tool is useful when you need to create a new pull request in a GitHub repository. **VERY IMPORTANT**: Your input to this tool MUST strictly follow these rules:
- First you must specify the title of the pull request
- Then you must place two newlines
- Then you must write the body or description of the pull request
To reference an issue in the body, put its issue number directly after a #.
For example, if you would like to create a pull request called "README updates" with contents "added contributors' names, closes issue #3", you would pass in the following string:
README updates
added contributors' names, closes issue #3
"""
CREATE_FILE_PROMPT = """
This tool is a wrapper for the GitHub API, useful when you need to create a file in a GitHub repository. **VERY IMPORTANT**: Your input to this tool MUST strictly follow these rules:
@ -43,6 +56,7 @@ For example, if you would like to replace the contents of the file /test/test.tx
test/test.txt
This is text that will not be changed
OLD <<<<
old contents
>>>> OLD

@ -21,6 +21,7 @@ class GitHubAPIWrapper(BaseModel):
github_app_id: Optional[str] = None
github_app_private_key: Optional[str] = None
github_branch: Optional[str] = None
github_base_branch: Optional[str] = None
class Config:
"""Configuration for this pydantic object."""
@ -43,6 +44,9 @@ class GitHubAPIWrapper(BaseModel):
github_branch = get_from_dict_or_env(
values, "github_branch", "GITHUB_BRANCH", default="master"
)
github_base_branch = get_from_dict_or_env(
values, "github_base_branch", "GITHUB_BASE_BRANCH", default="master"
)
try:
from github import Auth, GithubIntegration
@ -72,6 +76,7 @@ class GitHubAPIWrapper(BaseModel):
values["github_app_id"] = github_app_id
values["github_app_private_key"] = github_app_private_key
values["github_branch"] = github_branch
values["github_base_branch"] = github_base_branch
return values
@ -134,6 +139,34 @@ class GitHubAPIWrapper(BaseModel):
"comments": str(comments),
}
def create_pull_request(self, pr_query: str) -> str:
"""
Makes a pull request from the bot's branch to the base branch
Parameters:
pr_query(str): a string which contains the PR title
and the PR body. The title is the first line
in the string, and the body are the rest of the string.
For example, "Updated README\nmade changes to add info"
Returns:
str: A success or failure message
"""
if self.github_base_branch == self.github_branch:
return """Cannot make a pull request because
commits are already in the master branch"""
else:
try:
title = pr_query.split("\n")[0]
body = pr_query[len(title) + 2 :]
pr = self.github_repo_instance.create_pull(
title=title,
body=body,
head=self.github_branch,
base=self.github_base_branch,
)
return f"Successfully created PR number {pr.number}"
except Exception as e:
return "Unable to make pull request due to error:\n" + str(e)
def comment_on_issue(self, comment_query: str) -> str:
"""
Adds a comment to a github issue
@ -272,6 +305,8 @@ class GitHubAPIWrapper(BaseModel):
return self.comment_on_issue(query)
elif mode == "create_file":
return self.create_file(query)
elif mode == "create_pull_request":
return self.create_pull_request(query)
elif mode == "read_file":
return self.read_file(query)
elif mode == "update_file":

@ -4450,6 +4450,7 @@ optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*"
files = [
{file = "jsonpointer-2.4-py2.py3-none-any.whl", hash = "sha256:15d51bba20eea3165644553647711d150376234112651b4f1811022aecad7d7a"},
{file = "jsonpointer-2.4.tar.gz", hash = "sha256:585cee82b70211fa9e6043b7bb89db6e1aa49524340dde8ad6b63206ea689d88"},
]
[[package]]

Loading…
Cancel
Save