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

[MRG] the local web server to display the MLE suggestions #177

Merged
merged 8 commits into from
Sep 6, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
23 changes: 15 additions & 8 deletions mle/agents/reporter.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import json
from rich.console import Console
from mle.utils import dict_to_markdown
from time import gmtime, strftime


Expand Down Expand Up @@ -52,7 +51,7 @@ def __init__(self, model, console=None):
"business_goal": ["The project aims to build an image classification model...", ...],
"dev_progress": ["implemented the data collection Python function...", ...],
"communicate_progress": ["Meeting with the design team to discuss the new feature...", ...],
"dev_todo": [{"task": "fix ...", "priority": "high"}, {"task": "support ...", "priority": "medium"}, ...],
"dev_todo": [{"task": "fix ...", "description": ..., "priority": "high"}, {"task": "support ..."," description": ..., "priority": "medium"}, ...],
"communicate_todo": [{"task": "seek helps from ...", "priority": "high"},
{"task": "meet with ...", "priority": "low"} ...],
"hard_parts": ["The project may face the challenge of ...", ...],
Expand All @@ -65,13 +64,14 @@ def __init__(self, model, console=None):
self.sys_prompt += self.json_mode_prompt
self.chat_history.append({"role": 'system', "content": self.sys_prompt})

def process_knowledge(self, github_summary: dict, calendar_events: list = None):
def process_knowledge(self, github_summary: dict, calendar_events: list = None, okr: str = None):
"""
Process the knowledge to generate the report.

Args:
github_summary: the summary of the GitHub project.
calendar_events: the Google Calendar events.
okr: the OKR of the project.
"""
info_prompt = f"""
# Project Overview
Expand All @@ -81,6 +81,9 @@ def process_knowledge(self, github_summary: dict, calendar_events: list = None):
## Technology stack: {github_summary.get('tech_stack')}\n
## The project summary: {github_summary.get('summary')}\n
"""
if okr:
info_prompt += f"\n## The project's OKR: \n"
info_prompt += f"{okr}\n"

info_prompt += f"\n## The project's business goal: \n"
for goal in github_summary.get("business_goal", []):
Expand Down Expand Up @@ -139,16 +142,19 @@ def process_knowledge(self, github_summary: dict, calendar_events: list = None):
self.knowledge = info_prompt
return info_prompt

def gen_report(self, github_summary: dict, calendar_events: list = None):
def gen_report(self, github_summary: dict, calendar_events: list = None, okr: str = None):
"""
Handle the query from the model query response.
Args: None
Args:
github_summary: the summary of the GitHub project.
calendar_events: the Google Calendar
okr: the OKR of the project.
"""
with self.console.status("MLE reporter is writing the progress report..."):
self.chat_history.append(
{
"role": "user",
"content": self.process_knowledge(github_summary, calendar_events)
"content": self.process_knowledge(github_summary, calendar_events, okr)
}
)
text = self.model.query(
Expand All @@ -157,8 +163,9 @@ def gen_report(self, github_summary: dict, calendar_events: list = None):
)

self.chat_history.append({"role": "assistant", "content": text})
# save the dict into a local markdown file
# save the dict into a local files
today = strftime("%Y_%m_%d", gmtime())
result_dict = json.loads(text)
dict_to_markdown(result_dict, f"progress_report_{today}.md")
with open(f'progress_report_{today}.json', 'w') as f:
json.dump(result_dict, f)
return result_dict
11 changes: 11 additions & 0 deletions mle/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import yaml
import click
import pickle
import uvicorn
import questionary
from pathlib import Path
from rich.live import Live
Expand All @@ -11,6 +12,7 @@
from rich.markdown import Markdown

import mle
from mle.server import app
from mle.model import load_model
from mle.agents import CodeAgent
import mle.workflow as workflow
Expand Down Expand Up @@ -123,6 +125,15 @@ def chat():
exit()


@cli.command()
@click.option('--host', default='0.0.0.0', help='Host to bind the server to')
@click.option('--port', default=8000, help='Port to bind the server to')
def serve(host, port):
"""Start the FastAPI server"""
click.echo(f"Starting server on {host}:{port}")
uvicorn.run(app, host=host, port=port)


@cli.command()
@click.argument('name')
def new(name):
Expand Down
1 change: 1 addition & 0 deletions mle/server/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .app import app
105 changes: 105 additions & 0 deletions mle/server/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import os
import json
from typing import Optional
from datetime import datetime
from pydantic import BaseModel
from fastapi import FastAPI, HTTPException, BackgroundTasks
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware

from mle.workflow import report
from mle.utils import check_config

app = FastAPI()

# Add CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)


class ReportRequest(BaseModel):
"""
ReportRequest: the request body for generating a report
"""
repo: str
username: str
okr: Optional[str] = None


@app.get("/")
def root():
"""
read_root: read the root.
:return: the root.
"""
return {"Welcome to": "MLE-Agent!"}


@app.get("/latest_report", response_class=JSONResponse)
def read_latest_report():
"""
read_latest_report: read the latest progress report.
:return: the content of the latest progress report as plain text.
"""
if not check_config():
raise HTTPException(
status_code=400,
detail="`project.yml` not found. Please start the MLE server under an MLE-Agent project directory."
)

reports_dir = os.getcwd()
report_files = [f for f in os.listdir(reports_dir) if f.startswith("progress_report_") and f.endswith(".json")]

if not report_files:
raise HTTPException(status_code=404, detail="No progress reports found.")

latest_report = max(report_files, key=lambda f: datetime.strptime(f, "progress_report_%Y_%m_%d.json"))
try:
with open(os.path.join(reports_dir, latest_report), 'r') as file:
report_dict = json.load(file)
report_dict.update({"file": latest_report})
return JSONResponse(content=report_dict)
except IOError:
raise HTTPException(status_code=500, detail="Error reading the latest report file.")


@app.post("/gen_report")
async def gen_report(report_request: ReportRequest, background_tasks: BackgroundTasks):
"""
Generate a report based on the provided GitHub repository and username.
Optionally includes OKR text.

Example payload:

curl -X POST http://localhost:8000/gen_report \
-H "Content-Type: application/json" \
-d '{
"repo": "MLSysOps/MLE-agent",
"username": "huangyz0918",
"okr": "Improve system efficiency by 20% this quarter"
}'
"""
try:
# Trigger report generation in the background
background_tasks.add_task(
report,
os.getcwd(),
report_request.repo,
report_request.username,
okr_str=report_request.okr,
model="gpt-4o",
)

return {
"message": "Report generation started",
"repo": report_request.repo,
"username": report_request.username,
"okr_provided": report_request.okr is not None
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Error in report generation process: {e}")
8 changes: 7 additions & 1 deletion mle/workflow/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,16 @@ def report(
work_dir: str,
github_repo: str,
github_username: str,
okr_str: str = None,
model=None
):
"""
The workflow of the baseline mode.
:param work_dir: the working directory.
:param github_repo: the GitHub repository.
:param github_username: the GitHub username.
:param okr_str: the OKR string.
:param model: the model to use.
:return:
"""
console = Console()
Expand All @@ -74,6 +80,6 @@ def report(
reporter = ReportAgent(model, console)

github_summary = summarizer.summarize()
proj_report = reporter.gen_report(github_summary, events)
proj_report = reporter.gen_report(github_summary, events, okr=okr_str)
# print_in_box(proj_report, console, title="Github Summarizer", color="green")
print(proj_report)
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ rich
click
openai~=1.34.0
pyyaml
fastapi
uvicorn
requests
chromadb
onnxruntime
Expand Down
3 changes: 3 additions & 0 deletions web/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": ["next/core-web-vitals", "next/typescript"]
}
36 changes: 36 additions & 0 deletions web/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
36 changes: 36 additions & 0 deletions web/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
Binary file added web/app/favicon.ico
Binary file not shown.
Binary file added web/app/fonts/GeistMonoVF.woff
Binary file not shown.
Binary file added web/app/fonts/GeistVF.woff
Binary file not shown.
27 changes: 27 additions & 0 deletions web/app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
--background: #ffffff;
--foreground: #171717;
}

@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
}

body {
color: var(--foreground);
background: var(--background);
font-family: Arial, Helvetica, sans-serif;
}

@layer utilities {
.text-balance {
text-wrap: balance;
}
}
35 changes: 35 additions & 0 deletions web/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { Metadata } from "next";
import localFont from "next/font/local";
import "./globals.css";

const geistSans = localFont({
src: "./fonts/GeistVF.woff",
variable: "--font-geist-sans",
weight: "100 900",
});
const geistMono = localFont({
src: "./fonts/GeistMonoVF.woff",
variable: "--font-geist-mono",
weight: "100 900",
});

export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children}
</body>
</html>
);
}
Loading
Loading