Skip to content

Commit

Permalink
Added ability to call agent with response_model and tools from openai.
Browse files Browse the repository at this point in the history
  • Loading branch information
srtab committed Aug 29, 2024
1 parent d05fb6f commit 9a66d46
Show file tree
Hide file tree
Showing 16 changed files with 811 additions and 718 deletions.
40 changes: 20 additions & 20 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,45 +6,45 @@ name = "pypi"
[packages]
celery = { version = "==5.4.0", extras = ["redis"] }
chromadb = "==0.5.3"
django = "==5.0.6"
django-allauth = "==0.63.3"
django = "==5.1.0"
django-allauth = "==64.1.0"
django-appconf = "==1.0.6"
django-celery-beat = "==2.6.0"
django-celery-beat = "==2.7.0"
django-csp = "==3.8"
django-extensions = "==3.2.3"
django-ninja = "==1.2.0"
django-split-settings = "==1.3.1"
django-ninja = "==1.3.0"
django-split-settings = "==1.3.2"
django-webpack-loader = "==3.1.0"
esprima = "==4.0.1"
get-docker-secret = "==2.0.0"
instructor = "==1.3.4"
instructor = "==1.4.0"
ipython = "==8.26.0"
langchain = "==0.2.6"
langchain-chroma = "==0.1.2"
langchain-community = "==0.2.6"
langchain-openai = "==0.1.13"
langchain = "==0.2.15"
langchain-chroma = "==0.1.3"
langchain-community = "==0.2.14"
langchain-openai = "==0.1.23"
langchain-text-splitters = "==0.2.2"
litellm = "==1.41.3"
openai = "==1.35.7"
litellm = "==1.44.8"
openai = "==1.42.0"
psycopg = { version = "==3.2.1", extras = ["binary"] }
pydantic = "==2.8.0"
pydantic = "==2.8.2"
python-decouple = "==3.8"
python-gitlab = "==4.7.0"
python-gitlab = "==4.10.0"
redis = "==4.6.0"
sentence-transformers = "==3.0.1"
tantivy = "==0.22.0"
tree-sitter = "==0.21.3"
tree-sitter-languages = "==1.10.2"
unidiff = "==0.7.5"
uvicorn = "==0.30.1"
uvicorn = "==0.30.6"

[dev-packages]
coverage = "==7.5.0"
mypy = "==1.10.0"
pre-commit = "==3.7.0"
coverage = "==7.6.1"
mypy = "==1.11.2"
pre-commit = "==3.8.0"
pyproject-fmt = "==1.7.0"
pyopenssl = "==24.1.0"
ruff = "==0.4.4"
pyopenssl = "==24.2.1"
ruff = "==0.6.2"

[requires]
python_version = "3.12"
Expand Down
1,129 changes: 572 additions & 557 deletions Pipfile.lock

Large diffs are not rendered by default.

29 changes: 15 additions & 14 deletions daiv/automation/agents/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from functools import cached_property
from typing import TYPE_CHECKING

import instructor
import litellm
from decouple import config
from litellm import completion
Expand All @@ -26,7 +25,7 @@


CHEAPER_MODEL = "gpt-4o-mini"
PERFORMANT_MODEL = "gpt-4o-2024-05-13"
PERFORMANT_MODEL = "gpt-4o-2024-08-06"


class LlmAgent(ABC):
Expand All @@ -40,7 +39,7 @@ class LlmAgent(ABC):
model: str = PERFORMANT_MODEL

iterations: int = 0
max_iterations: int = 10
max_iterations: int = 20

def __init__(
self,
Expand Down Expand Up @@ -94,23 +93,22 @@ def run_iteration(self, response_model: type[BaseModel] | None = None):

completion_kwargs = {"model": self.model, "messages": messages, "temperature": 0, "api_key": self.api_key}

if response_model is None:
response: ModelResponse = completion(
tools=([tool.to_schema() for tool in self.tools] if self.tools else NotGiven()), **completion_kwargs
)
response: ModelResponse = completion(
tools=([tool.to_schema() for tool in self.tools] if self.tools else NotGiven()),
response_format=response_model,
**completion_kwargs,
)

message = Message(**response.choices[0].message.model_dump())
else:
model_instance, response = instructor.from_litellm(completion).chat.completions.create_with_completion(
response_model=response_model, **completion_kwargs
)
message = Message(model_instance=model_instance, content=model_instance.model_dump_json(), role="assistant")
message = Message(**response.choices[0].message.json())

self.memory.append(message)

logger.debug("%s: %s", message.role, message.content)

if message.tool_calls:
# If the response has a model instance, avoid call the tools, it means the model has already done the job.
if response_model and message.content:
message.model_instance = response_model.model_validate_json(message.content)
elif message.tool_calls:
for tool_call in message.tool_calls:
tool_response = self.call_tool(
ToolCall(
Expand Down Expand Up @@ -141,6 +139,9 @@ def should_continue_iteration(self, single_iteration: bool = False) -> bool:
if single_iteration:
return False

if self.memory[-1].model_instance is not None:
return False

if (
self.stop_messages
and self.memory[-1].content
Expand Down
2 changes: 1 addition & 1 deletion daiv/automation/coders/refactor/coder_patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def invoke(self, *args, **kwargs: Unpack[MergerRequestRefactorInvoke]) -> list[F
)
if (
not (
filepath_to_change := self.codebase_index.search_most_similar_filepath(
filepath_to_change := self.codebase_index.search_most_similar_file(
kwargs["target_repo_id"], repository_file
)
)
Expand Down
35 changes: 0 additions & 35 deletions daiv/automation/coders/refactor/prompts.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,41 +46,6 @@ def format_task_prompt(prompt: str) -> str:
"""
).format(prompt=prompt)

@staticmethod
def format_file_review_feedback_prompt(file_path: str, comments: list[str]) -> str:
"""
Format the review feedback for the task.
"""
return textwrap.dedent(
"""\
### Tasks ###
A developer has reviewed the file {file_path} and left comments that you need to analyze and apply changes.
### Developer Comments ###
{comments}
"""
).format(file_path=file_path, comments="\n".join(comments))

@staticmethod
def format_diff_review_feedback_prompt(file_path: str, comments: list[tuple[str, str]]) -> str:
"""
Format the review feedback for the task.
"""
diff_content = ""
for comment in comments:
diff_content += f"\n{comment[0]}Hunk:\n{comment[1]}\n\n"

return textwrap.dedent(
"""\
### Tasks ###
A developer has reviewed the file {file_path} and left notes that you need to analyse and address one by one.
The review below contain a hunk of a unified diff, which includes the line number of the code where the developer left the review to locate you, and the corresponding notes to you address.
### Developer Review ###
{diff_content}
""" # noqa: E501
).format(file_path=file_path, diff_content=diff_content)


class PatchRefactorPrompts:
@staticmethod
Expand Down
30 changes: 21 additions & 9 deletions daiv/automation/coders/review_addressor/coder.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,33 @@
from automation.agents.agent import LlmAgent
from automation.agents.models import Message
from automation.coders.base import STOP_MESSAGE, CodebaseCoder
from automation.coders.tools import CodeActionTools, CodeInspectTools
from automation.coders.typings import ReviewAddressorInvoke
from codebase.base import FileChange

from .models import RequestFeedback
from .prompts import ReviewAddressorPrompts, ReviewCommentorPrompts
from .tools import ReviewAddressorTools


class ReviewCommentorCoder(CodebaseCoder[ReviewAddressorInvoke, RequestFeedback]):
class ReviewCommentorCoder(CodebaseCoder[ReviewAddressorInvoke, RequestFeedback | None]):
"""
Coder to review the comments in the pull request.
Coder to review the comments left in a diff extracted from a pull request.
The coder will review the comments and ask for more information if needed.
"""

def invoke(self, *args, **kwargs: Unpack[ReviewAddressorInvoke]) -> RequestFeedback:
def invoke(self, *args, **kwargs: Unpack[ReviewAddressorInvoke]) -> RequestFeedback | None:
"""
Invoke the coder to review the comments in the pull request.
"""
code_inspect = CodeInspectTools(
self.repo_client,
self.codebase_index,
self.usage,
repo_id=kwargs["source_repo_id"],
ref=kwargs["source_ref"],
)

memory = [Message(role="system", content=ReviewCommentorPrompts.format_system(kwargs["diff"]))]

for note in kwargs["notes"]:
Expand All @@ -29,27 +39,29 @@ def invoke(self, *args, **kwargs: Unpack[ReviewAddressorInvoke]) -> RequestFeedb
else:
memory.append(Message(role="user", content=note.body))

self.agent = LlmAgent(memory=memory)
response = self.agent.run(response_model=RequestFeedback, single_iteration=True)
self.agent = LlmAgent(memory=memory, tools=code_inspect.get_tools())
response = self.agent.run(response_model=RequestFeedback)

self.usage += self.agent.usage

if response is None:
return []
return None

return cast(RequestFeedback, response)


class ReviewAddressorCoder(CodebaseCoder[ReviewAddressorInvoke, list[FileChange]]):
"""
Coder to address the review comments in the codebase.
Coder to address the review comments left on a pull request.
The coder will address the comments and make the necessary changes in the codebase.
"""

def invoke(self, *args, **kwargs: Unpack[ReviewAddressorInvoke]) -> list[FileChange]:
"""
Invoke the coder to address the review comments in the codebase.
"""
code_actions = ReviewAddressorTools(
code_actions = CodeActionTools(
self.repo_client,
self.codebase_index,
self.usage,
Expand Down
7 changes: 2 additions & 5 deletions daiv/automation/coders/review_addressor/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,10 @@ class RequestFeedback(BaseModel):
"""

questions: list[str] = Field(
default_factory=list,
description=dedent(
"""\
Questions for the user to answer to help you complete the task. Leave empty if there are no questions.
"""
),
)
code_changes_needed: bool = Field(
default=False, description="Whether code changes are needed to complete the task."
)
)
code_changes_needed: bool = Field(description="Whether code changes are needed to complete the task.")
9 changes: 5 additions & 4 deletions daiv/automation/coders/review_addressor/prompts.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ def format_system(diff: str):
### Instructions ###
Act as an exceptional senior software engineer that is responsible for addressing code review left on a pull request you worked on.
It's absolutely vital that you completely and correctly execute your task.
The user will interact with the comments left on the code review. The unified diff has been extracted from the file where the comments were made, and shows only the specific lines of code where they were made.
It's absolutely vital that you completely and correctly execute your task.
### Guidelines ###
- Think out loud step-by-step before you start asking questions;
- Before you start asking questions:
* Think out loud step-by-step;
* Use the supplied tools to obtain more detail about the codebase and to help you think about the problem and avoid asking unnecessary questions;
- Be straightforward on the context you need;
- To ask for feedback, use the provided functions;
- Your task is completed when there's no feedback to request.
### Examples ###
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ class ReplaceSnippetWith(OpenAISchema):
class CreateFile(OpenAISchema):
"""
Use this as primary tool to create a new file with the provided content.
If the file already exists, it will raise an error. Only use this to create inexistent files.
"""

file_path: str = Field(description="The file path to create.")
Expand Down
Loading

0 comments on commit 9a66d46

Please sign in to comment.