From a367bbd865b741607978a11fac64e73cfd0576ba Mon Sep 17 00:00:00 2001 From: Joshua Sundance Bailey Date: Wed, 13 Dec 2023 18:35:08 -0500 Subject: [PATCH 1/4] python-lint --- templates/python-lint/.gitignore | 1 + templates/python-lint/LICENSE | 21 ++ templates/python-lint/README.md | 69 +++++++ templates/python-lint/pyproject.toml | 27 +++ templates/python-lint/python_lint/__init__.py | 3 + .../python-lint/python_lint/agent_executor.py | 190 ++++++++++++++++++ templates/python-lint/tests/__init__.py | 0 7 files changed, 311 insertions(+) create mode 100644 templates/python-lint/.gitignore create mode 100644 templates/python-lint/LICENSE create mode 100644 templates/python-lint/README.md create mode 100644 templates/python-lint/pyproject.toml create mode 100644 templates/python-lint/python_lint/__init__.py create mode 100644 templates/python-lint/python_lint/agent_executor.py create mode 100644 templates/python-lint/tests/__init__.py diff --git a/templates/python-lint/.gitignore b/templates/python-lint/.gitignore new file mode 100644 index 0000000000000..bee8a64b79a99 --- /dev/null +++ b/templates/python-lint/.gitignore @@ -0,0 +1 @@ +__pycache__ diff --git a/templates/python-lint/LICENSE b/templates/python-lint/LICENSE new file mode 100644 index 0000000000000..426b65090341f --- /dev/null +++ b/templates/python-lint/LICENSE @@ -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. diff --git a/templates/python-lint/README.md b/templates/python-lint/README.md new file mode 100644 index 0000000000000..345923b05f511 --- /dev/null +++ b/templates/python-lint/README.md @@ -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= +export LANGCHAIN_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") +``` diff --git a/templates/python-lint/pyproject.toml b/templates/python-lint/pyproject.toml new file mode 100644 index 0000000000000..88ecf6f197a08 --- /dev/null +++ b/templates/python-lint/pyproject.toml @@ -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" diff --git a/templates/python-lint/python_lint/__init__.py b/templates/python-lint/python_lint/__init__.py new file mode 100644 index 0000000000000..ad54eac8c4439 --- /dev/null +++ b/templates/python-lint/python_lint/__init__.py @@ -0,0 +1,3 @@ +from python_lint.agent_executor import agent_executor + +__all__ = ["agent_executor"] diff --git a/templates/python-lint/python_lint/agent_executor.py b/templates/python-lint/python_lint/agent_executor.py new file mode 100644 index 0000000000000..f676ec72ee90d --- /dev/null +++ b/templates/python-lint/python_lint/agent_executor.py @@ -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: + 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}" + + 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.", + ) + + @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```" + 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. " + "Provide complete, end-to-end Python code to meet the user's description/requirements. " + "Always `check` your code. When you're done, you must ALWAYS use the `submit` tool.", + ), + ( + "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) +) diff --git a/templates/python-lint/tests/__init__.py b/templates/python-lint/tests/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d From 12d0acc64153919a50d979a7c9e6f2bcf27335c6 Mon Sep 17 00:00:00 2001 From: Joshua Sundance Bailey Date: Wed, 13 Dec 2023 18:45:13 -0500 Subject: [PATCH 2/4] fmt --- .../python-lint/python_lint/agent_executor.py | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/templates/python-lint/python_lint/agent_executor.py b/templates/python-lint/python_lint/agent_executor.py index f676ec72ee90d..52c692efc71c8 100644 --- a/templates/python-lint/python_lint/agent_executor.py +++ b/templates/python-lint/python_lint/agent_executor.py @@ -1,10 +1,9 @@ -"""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 @@ -68,7 +67,10 @@ def check_ruff(filepath: str): 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}" + cmd = ( + f"mypy {'--strict' if strict else ''} " + f"--follow-imports={follow_imports} {filepath}" + ) subprocess.check_output( # nosec cmd, @@ -81,7 +83,8 @@ def check_mypy(filepath: str, strict: bool = True, follow_imports: str = "skip") class PythonCode(BaseModel): code: str = Field( - description="Python code conforming to ruff, black, and *strict* mypy standards.", + description="Python code conforming to " + "ruff, black, and *strict* mypy standards.", ) @validator("code") @@ -135,7 +138,11 @@ def check_code(cls, v: str) -> str: 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```" + return ( + f"# LGTM\n" + f"# use the `submit` tool to submit this code:\n\n" + f"```python\n{code_obj.code}\n```" + ) except ValidationError as e: return e.errors()[0]["msg"] @@ -144,9 +151,12 @@ def check_code(code: str) -> str: [ ( "system", - "You are a world class Python coder who uses black, ruff, and *strict* mypy for all of your code. " - "Provide complete, end-to-end Python code to meet the user's description/requirements. " - "Always `check` your code. When you're done, you must ALWAYS use the `submit` tool.", + "You are a world class Python coder who uses " + "black, ruff, and *strict* mypy for all of your code. " + "Provide complete, end-to-end Python code " + "to meet the user's description/requirements. " + "Always `check` your code. When you're done, " + "you must ALWAYS use the `submit` tool.", ), ( "human", From 56694506a0fda03d118257623f1cd340ac933a9f Mon Sep 17 00:00:00 2001 From: Joshua Sundance Bailey Date: Thu, 14 Dec 2023 01:02:09 -0500 Subject: [PATCH 3/4] readme, metadata, configurability --- templates/python-lint/README.md | 9 ++++- templates/python-lint/pyproject.toml | 6 +++ .../python-lint/python_lint/agent_executor.py | 40 +++++++++++++------ 3 files changed, 41 insertions(+), 14 deletions(-) diff --git a/templates/python-lint/README.md b/templates/python-lint/README.md index 345923b05f511..094022b5a8f2d 100644 --- a/templates/python-lint/README.md +++ b/templates/python-lint/README.md @@ -1,8 +1,13 @@ # python-lint -This agent writes Python code that is formatted and linted using `black`, `ruff`, and `mypy`. +This agent specializes in generating high-quality Python code with a focus on proper formatting and linting. It uses `black`, `ruff`, and `mypy` to ensure the code meets standard quality checks. -It does not execute any code. +This streamlines the coding process by integrating and responding to these checks, resulting in reliable and consistent code output. + +It cannot actually execute the code it writes, as code execution may introduce additional dependencies and potential security vulnerabilities. +This makes the agent both a secure and efficient solution for code generation tasks. + +You can use it to generate Python code directly, or network it with planning and execution agents. ## Environment Setup diff --git a/templates/python-lint/pyproject.toml b/templates/python-lint/pyproject.toml index 88ecf6f197a08..61dda210af483 100644 --- a/templates/python-lint/pyproject.toml +++ b/templates/python-lint/pyproject.toml @@ -22,6 +22,12 @@ sse-starlette = "^1.6.5" export_module = "python_lint" export_attr = "agent_executor" +[tool.templates-hub] +use-case = "code-generation" +author = "Joshua Sundance Bailey" +integrations = ["OpenAI"] +tags = ["python", "agent"] + [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" diff --git a/templates/python-lint/python_lint/agent_executor.py b/templates/python-lint/python_lint/agent_executor.py index 52c692efc71c8..43bd7e2880a03 100644 --- a/templates/python-lint/python_lint/agent_executor.py +++ b/templates/python-lint/python_lint/agent_executor.py @@ -3,13 +3,13 @@ import subprocess # nosec import tempfile - -from langchain.agents import initialize_agent, AgentType +from langchain.agents import AgentType, initialize_agent 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 +from langchain.schema.runnable import ConfigurableField, Runnable def strip_python_markdown_tags(text: str) -> str: @@ -84,7 +84,7 @@ def check_mypy(filepath: str, strict: bool = True, follow_imports: str = "skip") class PythonCode(BaseModel): code: str = Field( description="Python code conforming to " - "ruff, black, and *strict* mypy standards.", + "ruff, black, and *strict* mypy standards.", ) @validator("code") @@ -172,29 +172,45 @@ def check_code(code: str) -> str: ) submit_code_tool = Tool.from_function( - lambda s: strip_python_markdown_tags(s), + strip_python_markdown_tags, 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!", + "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( +def get_agent_executor( + llm: BaseLLM, + agent_type: AgentType = AgentType.OPENAI_FUNCTIONS, +) -> Runnable: + _agent_executor = initialize_agent( tools, llm, agent=agent_type, verbose=True, handle_parsing_errors=True, prompt=prompt, - # return_intermediate_steps=True, - ) | (lambda output: output["output"]) + ) + return _agent_executor | (lambda output: output["output"]) + +class Instruction(BaseModel): + __root__: str -agent_executor = get_agent( - ChatOpenAI(model_name="gpt-4-1106-preview", temperature=0.0) + +agent_executor = ( + get_agent_executor(ChatOpenAI(model_name="gpt-4-1106-preview", temperature=0.0)) + .configurable_alternatives( + ConfigurableField("model_name"), + default_key="gpt4turbo", + gpt4=get_agent_executor(ChatOpenAI(model_name="gpt-4", temperature=0.0)), + gpt35t=get_agent_executor( + ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0.0), + ), + ) + .with_types(input_type=Instruction, output_type=str) ) From de66d4bcc814cc70814d23cdb5edcc16abea92e9 Mon Sep 17 00:00:00 2001 From: Joshua Sundance Bailey Date: Thu, 14 Dec 2023 01:06:30 -0500 Subject: [PATCH 4/4] sort imports --- templates/python-lint/python_lint/agent_executor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/python-lint/python_lint/agent_executor.py b/templates/python-lint/python_lint/agent_executor.py index 43bd7e2880a03..04b8304b6e42f 100644 --- a/templates/python-lint/python_lint/agent_executor.py +++ b/templates/python-lint/python_lint/agent_executor.py @@ -8,7 +8,7 @@ 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 +from langchain.pydantic_v1 import BaseModel, Field, ValidationError, validator from langchain.schema.runnable import ConfigurableField, Runnable