2024-01-02 00:25:03 +00:00
|
|
|
import os
|
|
|
|
import re
|
|
|
|
import subprocess # nosec
|
|
|
|
import tempfile
|
|
|
|
|
|
|
|
from langchain.agents import AgentType, initialize_agent
|
|
|
|
from langchain.agents.tools import Tool
|
|
|
|
from langchain.pydantic_v1 import BaseModel, Field, ValidationError, validator
|
2024-01-02 20:32:16 +00:00
|
|
|
from langchain_community.chat_models import ChatOpenAI
|
|
|
|
from langchain_core.language_models import BaseLLM
|
2024-01-03 21:28:05 +00:00
|
|
|
from langchain_core.prompts import ChatPromptTemplate
|
2024-01-02 20:09:45 +00:00
|
|
|
from langchain_core.runnables import ConfigurableField, Runnable
|
2024-01-02 00:25:03 +00:00
|
|
|
|
|
|
|
|
|
|
|
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 ''} "
|
|
|
|
f"--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"
|
|
|
|
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"]
|
|
|
|
|
|
|
|
|
|
|
|
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(
|
|
|
|
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!",
|
|
|
|
return_direct=True,
|
|
|
|
)
|
|
|
|
|
|
|
|
tools = [check_code_tool, submit_code_tool]
|
|
|
|
|
|
|
|
|
|
|
|
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 _agent_executor | (lambda output: output["output"])
|
|
|
|
|
|
|
|
|
|
|
|
class Instruction(BaseModel):
|
|
|
|
__root__: str
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
)
|