Skip to content

Commit

Permalink
Merge pull request #35 from Grigorij-Dudnik/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
Grigorij-Dudnik authored Jan 10, 2025
2 parents 932b166 + 8c55793 commit dc3595f
Show file tree
Hide file tree
Showing 26 changed files with 288 additions and 183 deletions.
File renamed without changes.
4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ services:
- LOG_FILE=${LOG_FILE:-}
volumes:
- .:/Clean_Coder
- ${WORK_DIR}:/work_dir
- ${WORK_DIR:-.}:/work_dir
# Uncomment (linux) or adjust (other systems) the following line to use microphone
# devices:
# - "/dev/snd:/dev/snd"
Expand All @@ -33,7 +33,7 @@ services:
- LOG_FILE=${LOG_FILE:-}
volumes:
- .:/Clean_Coder
- ${WORK_DIR}:/work_dir
- ${WORK_DIR:-.}:/work_dir
# Uncomment (linux) or adjust (other systems) the following line to use microphone
# devices:
# - "/dev/snd:/dev/snd"
Expand Down
12 changes: 4 additions & 8 deletions manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,12 @@
add_todoist_envs()

from typing import TypedDict, Sequence
from langchain_core.messages import BaseMessage, HumanMessage
from langchain_core.messages import BaseMessage, HumanMessage, ToolMessage
from langchain_core.load import dumps
from langgraph.graph import StateGraph
from dotenv import load_dotenv, find_dotenv
from src.tools.tools_project_manager import add_task, modify_task, finish_project_planning, reorder_tasks
from src.tools.tools_coder_pipeline import prepare_list_dir_tool, prepare_see_file_tool, ask_human_tool
from src.utilities.manager_utils import actualize_tasks_list_and_progress_description, create_todoist_project_if_needed, get_manager_messages
from src.utilities.manager_utils import actualize_tasks_list_and_progress_description, setup_todoist_project_if_needed, get_manager_messages
from src.utilities.langgraph_common_functions import call_model, call_tool, multiple_tools_msg, no_tools_msg, empty_message_msg
from src.utilities.start_project_functions import set_up_dot_clean_coder_dir
from src.utilities.util_functions import join_paths
Expand All @@ -41,15 +40,14 @@ def __init__(self):
self.manager = self.setup_workflow()
self.saved_messages_path = join_paths(self.work_dir, ".clean_coder/manager_messages.json")

# node functions
def call_model_manager(self, state):
self.save_messages_to_disk(state)
state = call_model(state, self.llms)
state = self.cut_off_context(state)
state = call_tool(state, self.tools)
messages = [msg for msg in state["messages"] if msg.type == "ai"]
last_ai_message = messages[-1]
if not last_ai_message.content:
if not last_ai_message.content and not last_ai_message.tool_calls:
state["messages"].pop()
state["messages"].append(HumanMessage(content=empty_message_msg))
elif len(last_ai_message.tool_calls) == 0:
Expand Down Expand Up @@ -111,13 +109,11 @@ def setup_workflow(self):
manager_workflow.add_node("agent", self.call_model_manager)
manager_workflow.set_entry_point("agent")
manager_workflow.add_edge("agent", "agent")
#manager_workflow.add_conditional_edges("agent", self.after_agent_condition)
return manager_workflow.compile()


def run(self):
print_formatted("😀 Hello! I'm Manager agent. Let's plan your project together!", color="green")
create_todoist_project_if_needed()
setup_todoist_project_if_needed()

messages = get_manager_messages(self.saved_messages_path)
inputs = {"messages": messages}
Expand Down
24 changes: 13 additions & 11 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,26 +34,28 @@ or (recommended) check detailed instructions [how to start in documentation](htt
You can also [deploy with Docker](https://clean-coder.dev/quick_start/run_with_docker/).


## 📺 Demo videos

Create an entire web app ~~with~~ by Clean Coder:

<div align="center">
<a href="https://youtu.be/aNpB-Tw-YPw" title="Greg's Tech video">
<img src="https://img.youtube.com/vi/aNpB-Tw-YPw/maxresdefault.jpg" width="600" alt="Demo video">
</a>
</div>


## ✨ Key advantages:

- Get project supervised by [Manager agent](https://clean-coder.dev/quick_start/manager/) with thoroughly-described tasks organized in Todoist, just like with a human scrum master.
- Watch tasks executed one by one by [Programming agents](https://clean-coder.dev/quick_start/programmer_pipeline/), with a well-designed context pipeline and advanced techniques for enhanced intelligence.
- Allow AI to see fronted it creates using [frontend feedback feature](https://clean-coder.dev/features/frontend_feedback/). At the day of writing no other AI coder has that.
- Allow AI to see frontend it creates with [frontend feedback feature](https://clean-coder.dev/features/frontend_feedback/). At the day of writing no other AI coder has that feature.
- Create a [frontend based on images](https://clean-coder.dev/features/working_with_images/) with designs.
- [Speak to Clean Coder](https://clean-coder.dev/features/talk_to_cc/) instead of writing.
- Automatic file linting prevents from introducing incorrect changes and [log check for self-debug](https://clean-coder.dev/advanced_features_installation/logs_check/).
- File Researcher agent with (but not only) [RAG tool](https://clean-coder.dev/advanced_features_installation/similarity_search_for_researcher/) for effective searching of project files.
- [Sensitive files protection](https://clean-coder.dev/features/sensitive_file_protection/) from being watched by AI.

## 📺 Demo videos

Create an entire web app ~~with~~ by Clean Coder:

<div align="center">
<a href="https://youtu.be/aNpB-Tw-YPw" title="Greg's Tech video">
<img src="https://img.youtube.com/vi/aNpB-Tw-YPw/maxresdefault.jpg" width="600" alt="Demo video">
</a>
</div>

## ⛓️‍💥 Something got broken?

Expand All @@ -63,7 +65,7 @@ Report bugs or propose new features for Clean Coder on our [Discord](https://dis
<br>
<div align="center">
<a href="https://github.com/Grigorij-Dudnik/Clean-Coder-AI/graphs/contributors">
<img src="https://contrib.rocks/image?repo=Grigorij-Dudnik/Clean-Coder-AI" />
<img src="https://contrib.rocks/image?repo=Grigorij-Dudnik/Clean-Coder-AI&1" />
</a>
</div>
<br>
Expand Down
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ langchain-core==0.3.15
langchain_community==0.3.1
langgraph==0.2.48
langgraph-checkpoint==2.0.4
langgraph-checkpoint-sqlite==2.0.1
python-dotenv==1.0.1
langchain-anthropic==0.2.1
langchain-openai==0.2.1
Expand All @@ -29,4 +28,5 @@ pytest==8.3.4
pytest-cov==6.0.0
pyright==1.1.390
ruff==0.8.2
httpx==0.27.2
httpx==0.27.2
questionary==2.1.0
14 changes: 4 additions & 10 deletions single_task_coder.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from src.agents.planner_agent import planning
from src.agents.executor_agent import Executor
from src.agents.debugger_agent import Debugger
from src.agents.frontend_feedback import write_screenshot_codes, execute_screenshot_codes
from src.agents.frontend_feedback import write_screenshot_codes
import os
from src.utilities.user_input import user_input
from src.utilities.print_formatters import print_formatted
Expand All @@ -37,25 +37,19 @@ def run_clean_coder_pipeline(task, work_dir):
future = executor_thread.submit(write_screenshot_codes, task, plan, work_dir)
file_paths = executor.do_task(task, plan)
playwright_codes = future.result()
if playwright_codes:
print_formatted("Making screenshots, please wait a while...", color="light_blue")
first_vfeedback_screenshots_msg = execute_screenshot_codes(playwright_codes)
else:
first_vfeedback_screenshots_msg = None
else:
file_paths = executor.do_task(task, plan)
first_vfeedback_screenshots_msg = None

human_message = user_input("Please test app and provide commentary if debugging/additional refinement is needed.")
human_message = user_input("Please test app and provide commentary if debugging/additional refinement is needed. ")
if human_message in ['o', 'ok']:
return
debugger = Debugger(
file_paths, work_dir, human_message,image_paths, first_vfeedback_screenshots_msg, playwright_codes)
file_paths, work_dir, human_message,image_paths, playwright_codes)
debugger.do_task(task, plan)


if __name__ == "__main__":
work_dir = os.getenv("WORK_DIR")
set_up_dot_clean_coder_dir(work_dir)
task = user_input("Provide task to be executed.")
task = user_input("Provide task to be executed. ")
run_clean_coder_pipeline(task, work_dir)
18 changes: 10 additions & 8 deletions src/agents/debugger_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class AgentState(TypedDict):


class Debugger():
def __init__(self, files, work_dir, human_feedback, image_paths, vfeedback_screenshots_msg=None, playwright_code=None):
def __init__(self, files, work_dir, human_feedback, image_paths, playwright_code=None):
self.work_dir = work_dir
self.tools = prepare_tools(work_dir)
self.llms = init_llms(self.tools, "Debugger")
Expand All @@ -57,7 +57,6 @@ def __init__(self, files, work_dir, human_feedback, image_paths, vfeedback_scree
self.files = files
self.images = convert_images(image_paths)
self.human_feedback = human_feedback
self.visual_feedback = vfeedback_screenshots_msg
self.playwright_code = playwright_code

# workflow definition
Expand All @@ -82,11 +81,8 @@ def __init__(self, files, work_dir, human_feedback, image_paths, vfeedback_scree
# node functions
def call_model_debugger(self, state):
state = call_model(state, self.llms)
state = self.call_tool_debugger(state)
return state

def call_tool_debugger(self, state):
state = call_tool(state, self.tools)

messages = [msg for msg in state["messages"] if msg.type == "ai"]
last_ai_message = messages[-1]
if len(last_ai_message.tool_calls) > 1:
Expand All @@ -95,6 +91,10 @@ def call_tool_debugger(self, state):
state["messages"].append(HumanMessage(content=multiple_tools_msg))
elif len(last_ai_message.tool_calls) == 0:
state["messages"].append(HumanMessage(content=no_tools_msg))

for tool_call in last_ai_message.tool_calls:
if tool_call["name"] == "create_file_with_code":
self.files.add(tool_call["args"]["filename"])
state = exchange_file_contents(state, self.files, self.work_dir)
return state

Expand Down Expand Up @@ -154,8 +154,10 @@ def do_task(self, task, plan):
HumanMessage(content=self.images),
HumanMessage(content=f"Human feedback: {self.human_feedback}"),
]}
if self.visual_feedback:
inputs["messages"].append(self.visual_feedback)
if self.playwright_code:
print_formatted("Making screenshots, please wait a while...", color="light_blue")
screenshot_msg = execute_screenshot_codes(self.playwright_code)
inputs["messages"].append(screenshot_msg)
self.debugger.invoke(inputs, {"recursion_limit": 150})


Expand Down
20 changes: 6 additions & 14 deletions src/agents/executor_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def final_response_executor(test_instruction):
tool input:
:param test_instruction: write detailed instruction for human what actions he need to do in order to check if
implemented changes work correctly."""
print_formatted(content=test_instruction, color="blue")
pass


class AgentState(TypedDict):
Expand All @@ -53,13 +53,10 @@ def __init__(self, files, work_dir):
executor_workflow = StateGraph(AgentState)

executor_workflow.add_node("agent", self.call_model_executor)
executor_workflow.add_node("tool", self.call_tool_executor)
executor_workflow.add_node("human_help", agent_looped_human_help)

executor_workflow.set_entry_point("agent")

# executor_workflow.add_edge("agent", "checker")
executor_workflow.add_edge("tool", "agent")
executor_workflow.add_edge("human_help", "agent")
executor_workflow.add_conditional_edges("agent", self.after_agent_condition)

Expand All @@ -68,18 +65,15 @@ def __init__(self, files, work_dir):
# node functions
def call_model_executor(self, state):
state = call_model(state, self.llms)
state = call_tool(state, self.tools)
messages = [msg for msg in state["messages"] if msg.type == "ai"]
last_ai_message = messages[-1]
if len(last_ai_message.tool_calls) > 1:
for tool_call in last_ai_message.tool_calls:
state["messages"].append(ToolMessage(content="too much tool calls", tool_call_id=tool_call["id"]))
state["messages"].append(HumanMessage(content=multiple_tools_msg))
return state

def call_tool_executor(self, state):
state = call_tool(state, self.tools)
messages = [msg for msg in state["messages"] if msg.type == "ai"]
last_ai_message = messages[-1]
elif len(last_ai_message.tool_calls) == 0:
state["messages"].append(HumanMessage(content=no_tools_msg))
for tool_call in last_ai_message.tool_calls:
if tool_call["name"] == "create_file_with_code":
self.files.add(tool_call["args"]["filename"])
Expand All @@ -93,12 +87,10 @@ def after_agent_condition(self, state):

if bad_tool_call_looped(state):
return "human_help"
elif last_message.content in (multiple_tools_msg, no_tools_msg):
return "agent"
elif last_message.tool_calls[0]["name"] == "final_response_executor":
elif len(last_message.tool_calls) > 0 and last_message.tool_calls[0]["name"] == "final_response_executor":
return END
else:
return "tool"
return "agent"

# just functions
def do_task(self, task, plan):
Expand Down
2 changes: 1 addition & 1 deletion src/agents/frontend_feedback.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class ScreenshotCodingStructure(BaseModel):
description="If you have missing info about some endpoint/selector/other important element name, ask about it here. If everything clear, questions are not needed."
)
screenshot_code: str = Field(description="""
Provide here your playwright code for a screenshot or write "No screenshot needed".
Provide here your playwright code for a screenshot. If screenshot is not needed for that task write exactly "No screenshot needed".
""")


Expand Down
4 changes: 2 additions & 2 deletions src/agents/planner_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def call_planer(state):


def ask_human_planner(state):
human_message = user_input("Type (o)k if you accept or provide commentary.")
human_message = user_input("Type (o)k if you accept or provide commentary. ")
if human_message in ['o', 'ok']:
state["messages"].append(HumanMessage(content="Approved by human"))
else:
Expand Down Expand Up @@ -135,7 +135,7 @@ def planning(task, text_files, image_paths, work_dir):
print_formatted("📈 Planner here! Create plan of changes with me!", color="light_blue")
file_contents = check_file_contents(text_files, work_dir, line_numbers=False)
images = convert_images(image_paths)
message_content_without_imgs = f"Task: {task},\n\nFiles:\n{file_contents}"
message_content_without_imgs = f"Task: {task},\n\n###\n\nFiles:\n{file_contents}"
message_without_imgs = HumanMessage(content=message_content_without_imgs)
message_images = HumanMessage(content=images)

Expand Down
9 changes: 6 additions & 3 deletions src/prompts/manager_system.prompt
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,19 @@ Do not create/modify tasks without watching project files first. Do not add new
Tasks you are creating are always very concrete, showing programmer how exactly and with which tools he can implement the change. If you unsure which technologies/resources to use, ask human before creating/modifying task.
Avoid creating flaky tasks, where it's unclear how to do task and if it is needed at all.

Make good file research before adding task to understand, how to implement task the best and to describe it in all details. In case if something doesn't work, make file research to undestand why it doesn't work and be able to provide exact guidelnes in the task.
Never make up information you don't know in task description. Ask human or make file research to get info.

Make good file research before adding task to understand, how to implement task the best and to describe it in all details.

When you need to decide about introducing new technology into the project, consult it with human first.

Always absolutely execute orders of a human.

Here is description of the project you work on:
Here is description of changes in the project you work on:
'''
{project_description}
{project_plan}
'''
Some of the changes may be already implemented, some not.

Some important project informations and rules:
'''
Expand Down
18 changes: 12 additions & 6 deletions src/prompts/planer_system.prompt
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,22 @@ Plan should not include library installation or tests or anything else unrelated
At every your message, you providing proposition of all changes, not just some. Never imagine files you have
not provided in context.

If advanced logic is required, plan logic algorithm before proposing code chages.

Readability is a priority - write your code to be readable and minimalistic.
Follow DRY (Don't Repeat Yourself) rule.
Give variables names that makes sense.

If you not sure how to implement given task - do not improvise, just say that you don't know. If you feel you miss some important files needed to implement plan - just say it.
If you not sure how to implement given task - do not improvise, just say that you don't know. If you feel you miss some important files or information needed to implement plan - just say it.

Do not rewrite full code, instead only write changes and point places where they need to be inserted.
Show with pluses (+) and minuses (-), where you want to add/remove/replace code. Like:
```diff
- self.id_number = str(ObjectId())
+ self.user_id = str(ObjectId())
+ self.email = email
Show with pluses (+) and minuses (-), where you want to add/remove/replace code. provide code snippets in your response looking like:

```filename.extension
- self.line_to_remove = str(ObjectId())
+ self.line_to_add_on_its_place = str(ObjectId())
self.line_to_stay_as_it_is = ObjectId(var)
+ self.more_lines_to_add = email
```

When adjusting your plan with user feedback, always provide complete version of plan. Do not provide unnecesary elements. Do not reference to previous version of plan - write it from zero instead.
Expand Down
6 changes: 3 additions & 3 deletions src/tools/rag/retrieval.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,13 @@ def get_collection():
return False


collection = get_collection()


def vdb_available():
return True if get_collection() else False


def retrieve(question):
# collection should be initialized once, in the class init
collection = get_collection()
retrieval = collection.query(query_texts=[question], n_results=8)
reranked_docs = cohere_client.rerank(
query=question,
Expand All @@ -50,6 +49,7 @@ def retrieve(question):

return response


if __name__ == "__main__":
question = "Common styles, used in the main page"
results = retrieve(question)
Expand Down
Loading

0 comments on commit dc3595f

Please sign in to comment.