Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AI Dev Team #819

Open
wants to merge 83 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
82f7c02
add chat agent
khoangothe Sep 9, 2024
c3d440e
add chat history websocket
khoangothe Sep 9, 2024
c157f41
add chat functionality to frontend
khoangothe Sep 9, 2024
0a3d1d7
cleanup
khoangothe Sep 9, 2024
57997bb
fix ui bug
khoangothe Sep 12, 2024
69964b6
decouple, refactor chat agent and add docstring
khoangothe Sep 12, 2024
f7f40b7
add document to vectorstore
khoangothe Sep 12, 2024
cc8450e
clean up
khoangothe Sep 12, 2024
85f9a10
add documentation for vector store
khoangothe Sep 13, 2024
7341e0f
dev-team setup
ElishaKay Sep 2, 2024
e6d662b
more dev-team setup - can be triggered via 'python -m dev_team.main' …
ElishaKay Sep 2, 2024
5292898
restructure
ElishaKay Sep 2, 2024
0c48f9e
utils
ElishaKay Sep 2, 2024
223d9a0
triggering run_dev_team_flow from cli
ElishaKay Sep 2, 2024
8b486c1
test_researcher.py
ElishaKay Sep 2, 2024
5dd82b3
debug github agent
ElishaKay Sep 2, 2024
89f6034
python -m dev_team.test_github_agent logs the gptr repo's directory s…
ElishaKay Sep 2, 2024
37ff0cb
combing through errors
ElishaKay Sep 2, 2024
2da26fd
less errors - reaching RubberDuckerAgent.think_aloud()
ElishaKay Sep 2, 2024
9562132
logging research state with significant data from agents
ElishaKay Sep 2, 2024
380c61d
logging state with lots of data & leveraging langgraph nodes
ElishaKay Sep 2, 2024
8e91ed7
saving vectorstore in correct format
ElishaKay Sep 2, 2024
933d366
dev team runs to completion
ElishaKay Sep 2, 2024
5281c7c
pass repo name in web search
ElishaKay Sep 2, 2024
441f2f4
pointing dev team at gptr repo
ElishaKay Sep 2, 2024
a560fa9
frontend setup for AI Dev Team
ElishaKay Sep 3, 2024
7591941
verifying that output of repo_analyzer & web_search agents reach the …
ElishaKay Sep 3, 2024
6be20c6
correct state variables of langgraph dev flow
ElishaKay Sep 4, 2024
1892c74
rubberduck agent composes his output based on output of previous steps
ElishaKay Sep 4, 2024
238b283
tech lead agent composes clear, cool & friendly response
ElishaKay Sep 4, 2024
7abf9ac
github agent can accept branch name as input & fetch it
ElishaKay Sep 4, 2024
777a65b
github agent can be initiated with vector store & github agent has ac…
ElishaKay Sep 4, 2024
36d3eb4
much better results when rubber duck & tech lead agents have access t…
ElishaKay Sep 4, 2024
c4ee467
discord bot setup
ElishaKay Sep 6, 2024
f35a2dc
nodejs debugging instructions
ElishaKay Sep 6, 2024
ecf3bac
bot replies sup
ElishaKay Sep 6, 2024
ec27c39
discord bot docs
ElishaKay Sep 6, 2024
f74dfa7
bot is triggering gptr websocket
ElishaKay Sep 6, 2024
a7d8fa4
discord bot triggers gptr's ai dev team & some websocket responses ar…
ElishaKay Sep 6, 2024
194db6b
ai tech lead response sent back from gptr service & logged as js obje…
ElishaKay Sep 6, 2024
7facbf4
removed hardcoded repo_name
ElishaKay Sep 6, 2024
493e71d
consistent naming of repo_name between discord-bot server & gptr serv…
ElishaKay Sep 6, 2024
c05e9a9
added the file_search_agent who has the ability to fetch the relevant…
ElishaKay Sep 6, 2024
5d6d5ad
handling race conditions around discord bot & websocket response
ElishaKay Sep 6, 2024
524378d
rubber duck & tech lead thoughts sent by discord bot (in chunks of up…
ElishaKay Sep 7, 2024
b71d8e0
lighter dev team backend flow & cleaner output of discord bot
ElishaKay Sep 7, 2024
b21e908
moved discord server into docker-container which can be triggered via…
ElishaKay Sep 9, 2024
9039750
moved dev_team folder into multi_agents folder & edited imports - dis…
ElishaKay Sep 9, 2024
3e08554
remove irrelevant code - runs to completion
ElishaKay Sep 9, 2024
c01ed56
/ask triggers modal with input form - user input on the form as logge…
ElishaKay Sep 9, 2024
ab23de6
/ask command launches modal where user can input query (& optionally:…
ElishaKay Sep 9, 2024
12631ac
user input from modal triggers backend dev team flow. bot awaits repl…
ElishaKay Sep 9, 2024
ac18689
cleanup
ElishaKay Sep 12, 2024
62b71e3
runDevTeamFlow function knows whether it's responding to a 1-on-1 cha…
ElishaKay Sep 12, 2024
a8003d5
setup for sending relevantFileNames to server as well
ElishaKay Sep 12, 2024
d8644ea
cleaner formatting of modal form submission in discussion thread
ElishaKay Sep 12, 2024
32adda8
branch name & file name reach file_search_agent - need to also pass t…
ElishaKay Sep 12, 2024
84ae569
removed hardcoded repo name & branch name from dev_team flow - discor…
ElishaKay Sep 12, 2024
7c77b19
better json handling for dev_team discord bot flow
ElishaKay Sep 12, 2024
080ccd6
better error handling - invalid json is logged in chat as is
ElishaKay Sep 12, 2024
8e87f3e
much better JSON parsing in discord nodejs
ElishaKay Sep 12, 2024
632355f
much better formatting & other error handling
ElishaKay Sep 12, 2024
3b03717
fixed error in 1-on-1 chat
ElishaKay Sep 12, 2024
e9272ed
server restarts independently when running prod Dockerfile - much bet…
ElishaKay Sep 12, 2024
cf1fffd
much better handling of triggering reports from threads - reports wil…
ElishaKay Sep 12, 2024
0d5ce98
rubber-duck shouldnt answer in json - just plain string. discord node…
ElishaKay Sep 15, 2024
11c01c9
restricted gptr-bot to help forum, general channel, gptr-allstars cha…
ElishaKay Sep 15, 2024
391e3a0
removed duplicate agents/utils folder - ai dev team runs to completion
ElishaKay Sep 15, 2024
08eed23
vector tests
ElishaKay Sep 15, 2024
2b81396
add document to vectorstore
khoangothe Sep 12, 2024
ab63ed9
passing vector_store into gptr - more metadata after filtering the gi…
ElishaKay Sep 16, 2024
803cf30
logging python files in __get_similar_content_by_query_with_vectorstore
ElishaKay Sep 18, 2024
2a11f1b
embedding flow running much better - looping through one file at a ti…
ElishaKay Sep 20, 2024
3dddf1a
embedding correctly - skipping errors of trying to embed images - run…
ElishaKay Sep 20, 2024
b72be91
python -m dev_team.test_github_agent logs the gptr repo's directory s…
ElishaKay Sep 2, 2024
64d4a4d
dev team runs to completion
ElishaKay Sep 2, 2024
ea9c9e6
only share the /ask guide when a new message is posted in the help fo…
ElishaKay Sep 18, 2024
bea1a81
Merge remote-tracking branch 'khoangothe/features/chat-with-history' …
ElishaKay Oct 2, 2024
7df936d
cleanup
ElishaKay Oct 2, 2024
761a216
cleanup
ElishaKay Oct 2, 2024
6c0300a
gitignore faiss index
ElishaKay Oct 2, 2024
41a4fd9
github data is saved in a langchain vector store syncronously & an as…
ElishaKay Oct 6, 2024
c1375a5
pgvector docs
ElishaKay Oct 15, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ docs/build
.langgraph-data/
.next/
package-lock.json
.github_repo_index/

#Vim swp files
*.swp
1 change: 1 addition & 0 deletions backend/chat/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .chat import ChatAgentWithMemory
74 changes: 74 additions & 0 deletions backend/chat/chat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
from fastapi import WebSocket
import uuid

from gpt_researcher.utils.llm import get_llm
from gpt_researcher.memory import Memory
from gpt_researcher.config import Config

from langgraph.prebuilt import create_react_agent
from langgraph.checkpoint.memory import MemorySaver

from langchain_community.vectorstores import InMemoryVectorStore
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.tools import Tool, tool

class ChatAgentWithMemory:
def __init__(
self,
report: str,
config_path,
headers,
vector_store = None
):
self.report = report
self.headers = headers
self.config = Config(config_path)
self.vector_store = vector_store
self.graph = self.create_agent()

def create_agent(self):
"""Create React Agent Graph"""
#If not vector store, split and talk to the report
if not self.vector_store:
documents = self._process_document(self.report)
provider = get_llm(self.config.llm_provider, model=self.config.fast_llm_model, temperature=self.config.temperature, max_tokens=self.config.fast_token_limit, **self.config.llm_kwargs).llm
self.chat_config = {"configurable": {"thread_id": str(uuid.uuid4())}}
self.embedding = Memory(self.config.embedding_provider, self.headers).get_embeddings()
self.vector_store = InMemoryVectorStore(self.embedding)
self.vector_store.add_texts(documents)
graph = create_react_agent(provider, tools=[self.vector_store_tool(self.vector_store)], checkpointer=MemorySaver())
return graph

def vector_store_tool(self, vector_store) -> Tool:
"""Create Vector Store Tool"""
@tool
def retrieve_info(query):
"""
Consult the report for relevant contexts whenever you don't know something
"""
retriever = vector_store.as_retriever(k = 4)
return retriever.invoke(query)
return retrieve_info

def _process_document(self, report):
"""Split Report into Chunks"""
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1024,
chunk_overlap=20,
length_function=len,
is_separator_regex=False,
)
documents = text_splitter.split_text(report)
return documents

async def chat(self, message, websocket):
"""Chat with React Agent"""
inputs = {"messages": [("user", message)]}
response = await self.graph.ainvoke(inputs, config=self.chat_config)
ai_message = response["messages"][-1].content
if websocket is not None:
await websocket.send_json({"type": "chat", "content": ai_message})

def get_context(self):
"""return the current context of the chat"""
return self.report
11 changes: 10 additions & 1 deletion backend/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def sanitize_filename(filename):
async def websocket_endpoint(websocket: WebSocket):
await manager.connect(websocket)
try:
report = None
while True:
data = await websocket.receive_text()
if data.startswith("start"):
Expand All @@ -80,14 +81,17 @@ async def websocket_endpoint(websocket: WebSocket):
source_urls = json_data.get("source_urls")
tone = json_data.get("tone")
headers = json_data.get("headers", {})
repo_name = json_data.get("repo_name")
print("repo_name in server.py", repo_name, flush=True)
branch_name = json_data.get("branch_name")
filename = f"task_{int(time.time())}_{task}"
sanitized_filename = sanitize_filename(
filename
) # Sanitize the filename
report_source = json_data.get("report_source")
if task and report_type:
report = await manager.start_streaming(
task, report_type, report_source, source_urls, tone, websocket, headers
task, report_type, report_source, source_urls, tone, websocket, headers, repo_name, branch_name
)
# Ensure report is a string
if not isinstance(report, str):
Expand Down Expand Up @@ -118,6 +122,11 @@ async def websocket_endpoint(websocket: WebSocket):
# You can add logic here to forward the feedback to the appropriate agent or update the research state
else:
print("Error: not enough parameters provided.")
elif data.startswith("chat") and report:
json_data = json.loads(data[4:])
headers = json_data.get("headers", {})
await manager.chat(json_data.get("message"), report, websocket, headers)

except WebSocketDisconnect:
await manager.disconnect(websocket)

Expand Down
35 changes: 31 additions & 4 deletions backend/websocket_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
from fastapi import WebSocket

from backend.report_type import BasicReport, DetailedReport
from backend.chat import ChatAgentWithMemory

from gpt_researcher.utils.enum import ReportType, Tone
from multi_agents.main import run_research_task
from gpt_researcher.master.actions import stream_output # Import stream_output

from multi_agents.dev_team.main import trigger_dev_team_flow
class WebSocketManager:
"""Manage websockets"""

Expand All @@ -17,6 +19,7 @@ def __init__(self):
self.active_connections: List[WebSocket] = []
self.sender_tasks: Dict[WebSocket, asyncio.Task] = {}
self.message_queues: Dict[WebSocket, asyncio.Queue] = {}
self.chat_agent = None

async def start_sender(self, websocket: WebSocket):
"""Start the sender task."""
Expand Down Expand Up @@ -54,15 +57,30 @@ async def disconnect(self, websocket: WebSocket):
del self.message_queues[websocket]


async def start_streaming(self, task, report_type, report_source, source_urls, tone, websocket, headers=None):
async def start_streaming(self, task, report_type, report_source, source_urls, tone, websocket, headers, repo_name, branch_name,):
"""Start streaming the output."""
tone = Tone[tone]
report = await run_agent(task, report_type, report_source, source_urls, tone, websocket, headers)
report = await run_agent(task, report_type, report_source, source_urls, tone, websocket, headers, repo_name, branch_name)
return report

async def chat(self, message, report, websocket, headers = None):
"""Chat with the agent based message diff"""
if self.chat_agent and self.chat_agent.get_context() == report:
await self.chat_agent.chat(message, websocket)
else:
#Create a new chat Agent when we have a new report
config_path = ""
self.chat_agent = ChatAgentWithMemory(report, config_path, headers)
await self.chat_agent.chat(message, websocket)

async def run_agent(task, report_type, report_source, source_urls, tone: Tone, websocket, headers=None):
async def run_agent(task, report_type, report_source, source_urls, tone: Tone, websocket, headers, repo_name, branch_name):
"""Run the agent."""

print(
f"Triggering run_agent with repo_name as: {repo_name}",
flush=True,
)

# measure time
start_time = datetime.datetime.now()
# add customized JSON config file path here
Expand All @@ -83,6 +101,15 @@ async def run_agent(task, report_type, report_source, source_urls, tone: Tone, w
headers=headers
)
report = await researcher.run()
elif report_type == "dev_team":
# Trigger the dev_team process
report = await trigger_dev_team_flow(
repo_name=repo_name,
branch_name=branch_name,
query=task,
websocket=websocket,
stream_output=stream_output
)
else:
researcher = BasicReport(
query=task,
Expand Down
15 changes: 15 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ services:
restart: always
ports:
- 8000:8000

gptr-nextjs:
pull_policy: build
image: gptresearcher/gptr-nextjs
Expand Down Expand Up @@ -40,3 +41,17 @@ services:
python -m pytest tests/report-types.py &&
python -m pytest tests/vector-store.py
"

discord-bot:
build:
context: ./docs/discord-bot
dockerfile: Dockerfile.dev
environment:
- DISCORD_BOT_TOKEN=${DISCORD_BOT_TOKEN}
- DISCORD_CLIENT_ID=${DISCORD_CLIENT_ID}
volumes:
- ./docs/discord-bot:/app
ports:
- 3001:3000
# profiles: ["discord"]
restart: always
6 changes: 6 additions & 0 deletions docs/discord-bot/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
FROM node:18.17.0-alpine
WORKDIR /app
COPY ./package.json ./
RUN npm install --legacy-peer-deps
COPY . .
CMD ["node", "index.js"]
7 changes: 7 additions & 0 deletions docs/discord-bot/Dockerfile.dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FROM node:18.17.0-alpine
WORKDIR /app
COPY ./package.json ./
RUN npm install --legacy-peer-deps
RUN npm install -g nodemon
COPY . .
CMD ["nodemon", "index.js"]
53 changes: 53 additions & 0 deletions docs/discord-bot/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
## Setting up the Discord Bot

Add a .env file in the root of the project and add the following:

```
DISCORD_BOT_TOKEN=
DISCORD_CLIENT_ID=
```

You can fetch the token from the Discord Developer Portal.
<br>
Go to: https://discord.com/developers/applications/
<br>
Click the "New Application" button and give your bot a name.
<br>
Within the Oath2 tab, you can generate a URL to invite your bot to your server.
First Select the "bot" scope.
<img src="./img/oath2-url-generator.png">
<br>
Next, give your bot the proper permissions.
<br>
<img src="./img/bot-permissions.png">
<br>
Finally you can invite your bot via the generated invite URL. In the case of the gptr-bot, here is the invite URL to open in your browser:
<br>
https://discord.com/oauth2/authorize?client_id=1281438963034361856&permissions=1689934339898432&integration_type=0&scope=bot

<br>
Finally, if you created your own custom bot, copy-paste the token into your .env file you created above.


### Running the bot

```bash
# run the bot
npm run dev
```

### Installing NodeJS and NPM on Ubuntu

```bash
#install nvm
wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.4/install.sh | bash

export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm

# install nodejs
nvm install 18.17.0

# install npm
sudo apt-get install npm
```
10 changes: 10 additions & 0 deletions docs/discord-bot/commands/ask.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const { SlashCommandBuilder } = require('discord.js');

module.exports = {
data: new SlashCommandBuilder()
.setName('ask')
.setDescription('Ask a question to the bot'),
async execute(interaction) {
await interaction.reply('Please provide your question.');
}
};
32 changes: 32 additions & 0 deletions docs/discord-bot/deploy-commands.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const { Client, GatewayIntentBits, REST, Routes } = require('discord.js');
require('dotenv').config();

// Create a new REST client and set your bot token
const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_BOT_TOKEN);

// Define commands
const commands = [
{
name: 'ping',
description: 'Replies with Pong!',
},
{
name: 'ask',
description: 'Ask a question to the bot',
},
];

// Deploy commands to Discord
(async () => {
try {
console.log('Started refreshing application (/) commands.');

await rest.put(Routes.applicationCommands(process.env.DISCORD_CLIENT_ID), {
body: commands,
});

console.log('Successfully reloaded application (/) commands.');
} catch (error) {
console.error(error);
}
})();
Loading