-
Notifications
You must be signed in to change notification settings - Fork 15.8k
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
__pycache__ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2023 LangChain, Inc. | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
# python-lint | ||
|
||
This agent writes Python code that is formatted and linted using `black`, `ruff`, and `mypy`. | ||
|
||
It does not execute any code. | ||
|
||
## Environment Setup | ||
|
||
- Install `black`, `ruff`, and `mypy`: `pip install -U black ruff mypy` | ||
- Set `OPENAI_API_KEY` environment variable. | ||
|
||
## Usage | ||
|
||
To use this package, you should first have the LangChain CLI installed: | ||
|
||
```shell | ||
pip install -U langchain-cli | ||
``` | ||
|
||
To create a new LangChain project and install this as the only package, you can do: | ||
|
||
```shell | ||
langchain app new my-app --package python-lint | ||
``` | ||
|
||
If you want to add this to an existing project, you can just run: | ||
|
||
```shell | ||
langchain app add python-lint | ||
``` | ||
|
||
And add the following code to your `server.py` file: | ||
```python | ||
from python_lint import agent_executor as python_lint_agent | ||
|
||
add_routes(app, python_lint_agent, path="/python-lint") | ||
``` | ||
|
||
(Optional) Let's now configure LangSmith. | ||
LangSmith will help us trace, monitor and debug LangChain applications. | ||
LangSmith is currently in private beta, you can sign up [here](https://smith.langchain.com/). | ||
If you don't have access, you can skip this section | ||
|
||
|
||
```shell | ||
export LANGCHAIN_TRACING_V2=true | ||
export LANGCHAIN_API_KEY=<your-api-key> | ||
export LANGCHAIN_PROJECT=<your-project> # if not specified, defaults to "default" | ||
``` | ||
|
||
If you are inside this directory, then you can spin up a LangServe instance directly by: | ||
|
||
```shell | ||
langchain serve | ||
``` | ||
|
||
This will start the FastAPI app with a server is running locally at | ||
[http://localhost:8000](http://localhost:8000) | ||
|
||
We can see all templates at [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs) | ||
We can access the playground at [http://127.0.0.1:8000/python-lint/playground](http://127.0.0.1:8000/python-lint/playground) | ||
|
||
We can access the template from code with: | ||
|
||
```python | ||
from langserve.client import RemoteRunnable | ||
|
||
runnable = RemoteRunnable("http://localhost:8000/python-lint") | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
[tool.poetry] | ||
name = "python-lint" | ||
version = "0.0.1" | ||
description = "Python code-writing agent whose work is checked by black, ruff, and mypy." | ||
authors = ["Joshua Sundance Bailey"] | ||
readme = "README.md" | ||
|
||
[tool.poetry.dependencies] | ||
ruff = ">=0.1.8" | ||
black = ">=23.12.0" | ||
mypy = ">=1.7.1" | ||
python = ">=3.8.1,<4.0" | ||
langchain = ">=0.0.313, <0.1" | ||
openai = ">=1.3.9" | ||
|
||
[tool.poetry.group.dev.dependencies] | ||
langchain-cli = ">=0.0.4" | ||
fastapi = "^0.104.0" | ||
sse-starlette = "^1.6.5" | ||
|
||
[tool.langserve] | ||
export_module = "python_lint" | ||
export_attr = "agent_executor" | ||
|
||
[build-system] | ||
requires = ["poetry-core"] | ||
build-backend = "poetry.core.masonry.api" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from python_lint.agent_executor import agent_executor | ||
|
||
__all__ = ["agent_executor"] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
"""langchain python coder-- requires black, ruff, and mypy.""" | ||
|
||
import os | ||
import re | ||
import subprocess # nosec | ||
import tempfile | ||
|
||
from langchain.agents import initialize_agent, AgentType | ||
from langchain.agents.tools import Tool | ||
from langchain.chat_models import ChatOpenAI | ||
from langchain.llms.base import BaseLLM | ||
from langchain.prompts import ChatPromptTemplate | ||
from langchain.pydantic_v1 import BaseModel, validator, Field, ValidationError | ||
|
||
|
||
def strip_python_markdown_tags(text: str) -> str: | ||
Check failure on line 16 in templates/python-lint/python_lint/agent_executor.py GitHub Actions / lint / build (3.11)Ruff (I001)
|
||
pat = re.compile(r"```python\n(.*)```", re.DOTALL) | ||
code = pat.match(text) | ||
if code: | ||
return code.group(1) | ||
else: | ||
return text | ||
|
||
|
||
def format_black(filepath: str): | ||
"""Format a file with black.""" | ||
subprocess.run( # nosec | ||
f"black {filepath}", | ||
stderr=subprocess.STDOUT, | ||
text=True, | ||
shell=True, | ||
timeout=3, | ||
check=False, | ||
) | ||
|
||
|
||
def format_ruff(filepath: str): | ||
"""Run ruff format on a file.""" | ||
subprocess.run( # nosec | ||
f"ruff check --fix {filepath}", | ||
shell=True, | ||
text=True, | ||
timeout=3, | ||
universal_newlines=True, | ||
check=False, | ||
) | ||
|
||
subprocess.run( # nosec | ||
f"ruff format {filepath}", | ||
stderr=subprocess.STDOUT, | ||
shell=True, | ||
timeout=3, | ||
text=True, | ||
check=False, | ||
) | ||
|
||
|
||
def check_ruff(filepath: str): | ||
"""Run ruff check on a file.""" | ||
subprocess.check_output( # nosec | ||
f"ruff check {filepath}", | ||
stderr=subprocess.STDOUT, | ||
shell=True, | ||
timeout=3, | ||
text=True, | ||
) | ||
|
||
|
||
def check_mypy(filepath: str, strict: bool = True, follow_imports: str = "skip"): | ||
"""Run mypy on a file.""" | ||
cmd = f"mypy {'--strict' if strict else ''} --follow-imports={follow_imports} {filepath}" | ||
Check failure on line 71 in templates/python-lint/python_lint/agent_executor.py GitHub Actions / lint / build (3.11)Ruff (E501)
|
||
|
||
subprocess.check_output( # nosec | ||
cmd, | ||
stderr=subprocess.STDOUT, | ||
shell=True, | ||
text=True, | ||
timeout=3, | ||
) | ||
|
||
|
||
class PythonCode(BaseModel): | ||
code: str = Field( | ||
description="Python code conforming to ruff, black, and *strict* mypy standards.", | ||
Check failure on line 84 in templates/python-lint/python_lint/agent_executor.py GitHub Actions / lint / build (3.11)Ruff (E501)
|
||
) | ||
|
||
@validator("code") | ||
@classmethod | ||
def check_code(cls, v: str) -> str: | ||
v = strip_python_markdown_tags(v).strip() | ||
try: | ||
with tempfile.NamedTemporaryFile(mode="w", delete=False) as temp_file: | ||
temp_file.write(v) | ||
temp_file_path = temp_file.name | ||
|
||
try: | ||
# format with black and ruff | ||
format_black(temp_file_path) | ||
format_ruff(temp_file_path) | ||
except subprocess.CalledProcessError: | ||
pass | ||
|
||
# update `v` with formatted code | ||
with open(temp_file_path, "r") as temp_file: | ||
v = temp_file.read() | ||
|
||
# check | ||
complaints = dict(ruff=None, mypy=None) | ||
|
||
try: | ||
check_ruff(temp_file_path) | ||
except subprocess.CalledProcessError as e: | ||
complaints["ruff"] = e.output | ||
|
||
try: | ||
check_mypy(temp_file_path) | ||
except subprocess.CalledProcessError as e: | ||
complaints["mypy"] = e.output | ||
|
||
# raise ValueError if ruff or mypy had complaints | ||
if any(complaints.values()): | ||
code_str = f"```{temp_file_path}\n{v}```" | ||
error_messages = [ | ||
f"```{key}\n{value}```" | ||
for key, value in complaints.items() | ||
if value | ||
] | ||
raise ValueError("\n\n".join([code_str] + error_messages)) | ||
|
||
finally: | ||
os.remove(temp_file_path) | ||
return v | ||
|
||
|
||
def check_code(code: str) -> str: | ||
try: | ||
code_obj = PythonCode(code=code) | ||
return f"# LGTM\n# use the `submit` tool to submit this code:\n\n```python\n{code_obj.code}\n```" | ||
Check failure on line 138 in templates/python-lint/python_lint/agent_executor.py GitHub Actions / lint / build (3.11)Ruff (E501)
|
||
except ValidationError as e: | ||
return e.errors()[0]["msg"] | ||
|
||
|
||
prompt = ChatPromptTemplate.from_messages( | ||
[ | ||
( | ||
"system", | ||
"You are a world class Python coder who uses black, ruff, and *strict* mypy for all of your code. " | ||
Check failure on line 147 in templates/python-lint/python_lint/agent_executor.py GitHub Actions / lint / build (3.11)Ruff (E501)
|
||
"Provide complete, end-to-end Python code to meet the user's description/requirements. " | ||
Check failure on line 148 in templates/python-lint/python_lint/agent_executor.py GitHub Actions / lint / build (3.11)Ruff (E501)
|
||
"Always `check` your code. When you're done, you must ALWAYS use the `submit` tool.", | ||
Check failure on line 149 in templates/python-lint/python_lint/agent_executor.py GitHub Actions / lint / build (3.11)Ruff (E501)
|
||
), | ||
( | ||
"human", | ||
": {input}", | ||
), | ||
], | ||
) | ||
|
||
check_code_tool = Tool.from_function( | ||
check_code, | ||
name="check-code", | ||
description="Always check your code before submitting it!", | ||
) | ||
|
||
submit_code_tool = Tool.from_function( | ||
lambda s: strip_python_markdown_tags(s), | ||
name="submit-code", | ||
description="THIS TOOL is the most important. " | ||
"use it to submit your code to the user who requested it... " | ||
"but be sure to `check` it first!", | ||
return_direct=True, | ||
) | ||
|
||
tools = [check_code_tool, submit_code_tool] | ||
|
||
|
||
def get_agent(llm: BaseLLM, agent_type: AgentType = AgentType.OPENAI_FUNCTIONS): | ||
return initialize_agent( | ||
tools, | ||
llm, | ||
agent=agent_type, | ||
verbose=True, | ||
handle_parsing_errors=True, | ||
prompt=prompt, | ||
# return_intermediate_steps=True, | ||
) | (lambda output: output["output"]) | ||
|
||
|
||
agent_executor = get_agent( | ||
ChatOpenAI(model_name="gpt-4-1106-preview", temperature=0.0) | ||
) |