* updated local APIs to return usage info (#585) * updated APIs to return usage info * tested all endpoints * added autogen as an extra (#616) * added autogen as an extra * updated docs Co-authored-by: hemanthsavasere <hemanth.savasere@gmail.com> * Update LICENSE * Add safeguard on tokens returned by functions (#576) * swapping out hardcoded str for prefix (forgot to include in #569) * add extra failout when the summarizer tries to run on a single message * added function response validation code, currently will truncate responses based on character count * added return type hints (functions/tools should either return strings or None) * discuss function output length in custom function section * made the truncation more informative * patch bug where None.copy() throws runtime error (#617) * allow passing custom host to uvicorn (#618) * feat: initial poc for socket server * feat: initial poc for frontend based on react Set up an nx workspace which maks it easy to manage dependencies and added shadcn components that allow us to build good-looking ui in a fairly simple way. UI is a very simple and basic chat that starts with a message of the user and then simply displays the answer string that is sent back from the fastapi ws endpoint * feat: mapp arguments to json and return new messages Except for the previous user message we return all newly generated messages and let the frontend figure out how to display them. * feat: display messages based on role and show inner thoughts and connection status * chore: build newest frontend * feat(frontend): show loader while waiting for first message and disable send button until connection is open * feat: make agent send the first message and loop similar to CLI currently the CLI loops until the correct function call sends a message to the user. this is an initial try to achieve a similar behavior in the socket server * chore: build new version of frontend * fix: rename lib directory so it is not excluded as part of python gitignore * chore: rebuild frontend app * fix: save agent at end of each response to allow the conversation to carry on over multiple sessions * feat: restructure server to support multiple endpoints and add agents and sources endpoint * feat: setup frontend routing and settings page * chore: build frontend * feat: another iteration of web interface changes include: websocket for chat. switching between different agents. introduction of zustand state management * feat: adjust frontend to work with memgpt rest-api * feat: adjust existing rest_api to serve and interact with frontend * feat: build latest frontend * chore: build latest frontend * fix: cleanup workspace --------- Co-authored-by: Charles Packer <packercharles@gmail.com> Co-authored-by: hemanthsavasere <hemanth.savasere@gmail.com>
202 lines
5.6 KiB
Python
202 lines
5.6 KiB
Python
import asyncio
|
|
import os
|
|
from contextlib import asynccontextmanager
|
|
import json
|
|
|
|
from fastapi import FastAPI, HTTPException
|
|
from fastapi.responses import StreamingResponse
|
|
from pydantic import BaseModel
|
|
|
|
from starlette.exceptions import HTTPException as StarletteHTTPException
|
|
from starlette.middleware.cors import CORSMiddleware
|
|
from starlette.staticfiles import StaticFiles
|
|
|
|
from memgpt.server.server import SyncServer
|
|
from memgpt.server.rest_api.interface import QueuingInterface
|
|
|
|
"""
|
|
Basic REST API sitting on top of the internal MemGPT python server (SyncServer)
|
|
|
|
Start the server with:
|
|
cd memgpt/server/rest_api
|
|
poetry run uvicorn server:app --reload
|
|
"""
|
|
|
|
|
|
class CreateAgentConfig(BaseModel):
|
|
user_id: str
|
|
config: dict
|
|
|
|
|
|
class UserMessage(BaseModel):
|
|
user_id: str
|
|
agent_id: str
|
|
message: str
|
|
stream: bool = False
|
|
|
|
|
|
class Command(BaseModel):
|
|
user_id: str
|
|
agent_id: str
|
|
command: str
|
|
|
|
|
|
class CoreMemory(BaseModel):
|
|
user_id: str
|
|
agent_id: str
|
|
human: str | None = None
|
|
persona: str | None = None
|
|
|
|
|
|
class SPAStaticFiles(StaticFiles):
|
|
async def get_response(self, path: str, scope):
|
|
try:
|
|
return await super().get_response(path, scope)
|
|
except (HTTPException, StarletteHTTPException) as ex:
|
|
if ex.status_code == 404:
|
|
return await super().get_response("index.html", scope)
|
|
else:
|
|
raise ex
|
|
|
|
|
|
server: SyncServer | None = None
|
|
interface: QueuingInterface | None = None
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(application: FastAPI):
|
|
global server
|
|
global interface
|
|
interface = QueuingInterface()
|
|
server = SyncServer(default_interface=interface)
|
|
yield
|
|
server.save_agents()
|
|
server = None
|
|
|
|
|
|
CORS_ORIGINS = [
|
|
"http://localhost:4200",
|
|
"http://localhost:4201",
|
|
"http://localhost:8283",
|
|
"http://127.0.0.1:4200",
|
|
"http://127.0.0.1:4201",
|
|
"http://127.0.0.1:8283",
|
|
]
|
|
|
|
app = FastAPI(lifespan=lifespan)
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=CORS_ORIGINS,
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
|
|
@app.get("/agents")
|
|
def list_agents(user_id: str):
|
|
interface.clear()
|
|
return server.list_agents(user_id=user_id)
|
|
|
|
|
|
@app.get("/agents/memory")
|
|
def get_agent_memory(user_id: str, agent_id: str):
|
|
interface.clear()
|
|
return server.get_agent_memory(user_id=user_id, agent_id=agent_id)
|
|
|
|
|
|
@app.put("/agents/memory")
|
|
def put_agent_memory(body: CoreMemory):
|
|
interface.clear()
|
|
new_memory_contents = {"persona": body.persona, "human": body.human}
|
|
return server.update_agent_core_memory(user_id=body.user_id, agent_id=body.agent_id, new_memory_contents=new_memory_contents)
|
|
|
|
|
|
@app.get("/agents/config")
|
|
def get_agent_config(user_id: str, agent_id: str):
|
|
interface.clear()
|
|
return server.get_agent_config(user_id=user_id, agent_id=agent_id)
|
|
|
|
|
|
@app.get("/config")
|
|
def get_server_config(user_id: str):
|
|
interface.clear()
|
|
return server.get_server_config(user_id=user_id)
|
|
|
|
|
|
# server.create_agent
|
|
@app.post("/agents")
|
|
def create_agents(body: CreateAgentConfig):
|
|
interface.clear()
|
|
try:
|
|
agent_id = server.create_agent(user_id=body.user_id, agent_config=body.config)
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"{e}")
|
|
return {"agent_id": agent_id}
|
|
|
|
|
|
# server.user_message
|
|
@app.post("/agents/message")
|
|
async def user_message(body: UserMessage):
|
|
if body.stream:
|
|
# For streaming response
|
|
try:
|
|
# Start the generation process (similar to the non-streaming case)
|
|
# This should be a non-blocking call or run in a background task
|
|
|
|
# Check if server.user_message is an async function
|
|
if asyncio.iscoroutinefunction(server.user_message):
|
|
# Start the async task
|
|
await asyncio.create_task(server.user_message(user_id=body.user_id, agent_id=body.agent_id, message=body.message))
|
|
else:
|
|
# Run the synchronous function in a thread pool
|
|
loop = asyncio.get_event_loop()
|
|
loop.run_in_executor(None, server.user_message, body.user_id, body.agent_id, body.message)
|
|
|
|
async def formatted_message_generator():
|
|
async for message in interface.message_generator():
|
|
formatted_message = f"data: {json.dumps(message)}\n\n"
|
|
yield formatted_message
|
|
await asyncio.sleep(1)
|
|
|
|
# Return the streaming response using the generator
|
|
return StreamingResponse(formatted_message_generator(), media_type="text/event-stream")
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"{e}")
|
|
|
|
else:
|
|
interface.clear()
|
|
try:
|
|
server.user_message(user_id=body.user_id, agent_id=body.agent_id, message=body.message)
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"{e}")
|
|
return {"messages": interface.to_list()}
|
|
|
|
|
|
# server.run_command
|
|
@app.post("/agents/command")
|
|
def run_command(body: Command):
|
|
interface.clear()
|
|
try:
|
|
response = server.run_command(user_id=body.user_id, agent_id=body.agent_id, command=body.command)
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"{e}")
|
|
return {"response": response}
|
|
|
|
|
|
app.mount(
|
|
"/",
|
|
SPAStaticFiles(
|
|
directory=os.path.join(os.getcwd(), "..", "static_files"),
|
|
html=True,
|
|
),
|
|
name="spa-static-files",
|
|
)
|