From 4ab300cb45ed7117f431e7119a808d2d0773438c Mon Sep 17 00:00:00 2001 From: shant01 <59899008+shant01@users.noreply.github.com> Date: Wed, 15 Nov 2023 12:26:52 -0800 Subject: [PATCH] Python langchain (#32) * fast api testing successful * hot reload with uvicorn working * read me for instructions * gitignore * organized folders * conversation file for langchain, main for api * langchain agent file defined * fastapi route for conversation * conversation function * changed import * shortened template * succesfful interacton with openai api * Ignore .pyc files * cleaned up fastapi file * added route for calling fastapi from backend * skeleteon for langchain call * added langchaincall route to server file * langchaincallroute finished * successful commincation from express to fastapi * able to send data to api from body * successfull call to api from front end * front end prints out response from api * created requirements file * Upgrades packages and makde import changes. Initial docker file for langchain. Ref #25 * Set up dockerfile for langchain Fin #25 --------- Co-authored-by: Arturo Co-authored-by: John Tran --- .gitignore | 1 + backend/routes/langchainCallRoute.js | 29 +++++ backend/server.js | 6 +- docker-compose.yml | 32 +++-- frontend/src/components/Chat.jsx | 36 ++++-- langchain/.gitignore | 176 +++++++++++++++++++++++++++ langchain/Dockerfile | 21 ++++ langchain/README.md | 1 + langchain/__init__.py | 0 langchain/app/__init__.py | 0 langchain/app/langchain_agent.py | 27 ++++ langchain/app/main.py | 44 +++++++ langchain/requirements.txt | 42 +++++++ 13 files changed, 398 insertions(+), 17 deletions(-) create mode 100644 backend/routes/langchainCallRoute.js create mode 100644 langchain/.gitignore create mode 100644 langchain/Dockerfile create mode 100644 langchain/README.md create mode 100644 langchain/__init__.py create mode 100644 langchain/app/__init__.py create mode 100644 langchain/app/langchain_agent.py create mode 100644 langchain/app/main.py create mode 100644 langchain/requirements.txt diff --git a/.gitignore b/.gitignore index 4ab5f1eb..7cabead0 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,6 @@ node_modules/ data/ ## ignore OS turds +.env .DS_Store .vscode diff --git a/backend/routes/langchainCallRoute.js b/backend/routes/langchainCallRoute.js new file mode 100644 index 00000000..2565b00b --- /dev/null +++ b/backend/routes/langchainCallRoute.js @@ -0,0 +1,29 @@ +import express from "express"; +const langchainCallRoute = express.Router(); +import axios from "axios"; + +langchainCallRoute.post("/ask", async (req, res) => { + //call fastapi from this endpoint + try { + //get user input + const userInput = req.body.data; + + //make request to fastapi server + const fastResponse = await axios.post( + "http://fastapi:8000/process-lang", + { + human_input: userInput, + } + ); + + //response from fastapi + const dataFromFastapi = fastResponse.data; + + res.json({ message: "Successful call to FastAPI", dataFromFastapi }); + } catch (error) { + console.error("Error calling FastAPI", error); + res.status(500).json({ error: "Failed to call FastAPI" }); + } +}); + +export default langchainCallRoute; diff --git a/backend/server.js b/backend/server.js index 9c43c0a7..96724139 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,8 +1,11 @@ import express from "express"; import cors from "cors"; import serverHealthRoutes from "./routes/serverHealthRoutes.js"; +import langchainCallRoute from "./routes/langchainCallRoute.js"; const app = express(); +app.use(express.json()); +app.use(express.urlencoded({ extended: true })); // Allow CORS only for specific origins (recommended for production) const corsOptions = { @@ -13,7 +16,8 @@ app.use(cors(corsOptions)); const port = process.env.PORT || 5050; const mongoURI = process.env.MONGO_URI || 'mongodb://localhost:27017/lp-toolkit' console.log("MONGO URI" , mongoURI) -app.use('/serverHealthRoutes', serverHealthRoutes); +app.use("/serverHealthRoutes", serverHealthRoutes); +app.use("/langchainCallRoute", langchainCallRoute); // This displays a message indicating that the server is running and listening to the specified port app.listen(port, () => console.log(`Listening on port ${port}`)); diff --git a/docker-compose.yml b/docker-compose.yml index 061e4f17..c8ca2da7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,10 +1,12 @@ +version: '3.8' + services: frontend: container_name: lp-toolkit-frontend build: context: frontend ports: - - 3000:3000 + - "3000:3000" volumes: - ./frontend:/usr/src/app restart: unless-stopped @@ -15,31 +17,45 @@ services: backend: container_name: lp-toolkit-backend - restart: unless-stopped build: context: backend ports: - - 5050:5050 + - "5050:5050" volumes: - ./backend:/usr/src/app - depends_on: - - mongo environment: - - MONGO_URI='mongodb://mongo:27017/lp-toolkit' + - MONGO_URI=mongodb://mongo:27017/lp-toolkit + restart: unless-stopped networks: - express-mongo - react-express + depends_on: + - mongo + + fastapi: + container_name: lp-toolkit-fastapi + build: ./langchain + ports: + - "8000:8000" + volumes: + - ./langchain/app:/usr/src/app + environment: + - OPENAI_API_KEY=${OPENAI_API_KEY} + restart: unless-stopped + networks: + - express-mongo # Include if FastAPI needs to communicate with MongoDB + - react-express # For communication with the frontend mongo: container_name: lp-toolkit-mongo - restart: unless-stopped image: mongo:4.2.0 volumes: - ./data:/data/db + restart: unless-stopped networks: - express-mongo expose: - - 27017 + - "27017" networks: react-express: diff --git a/frontend/src/components/Chat.jsx b/frontend/src/components/Chat.jsx index 8957ba9a..902680f4 100644 --- a/frontend/src/components/Chat.jsx +++ b/frontend/src/components/Chat.jsx @@ -19,17 +19,37 @@ export function Chat() { } }; + // Define function for sendingUserMessage with input data + const sendUserMessage = (message) => { + return axios.post("http://localhost:5050/langchainCallRoute/ask", { + data: message, + }); + }; + const sendMessage = async () => { if (currentMessage) { + //timestamp let timestamp = await fetchTimestamp(); - setMessages([ - ...messages, - { type: "user", content: currentMessage }, - { - type: "response", - content: timestamp, - }, - ]); + //add user message to chat + setMessages([...messages, { type: "user", content: currentMessage }]); + //try to send user message to express route + try { + const response = await sendUserMessage(currentMessage); + //add response from express route to chat + setMessages([ + ...messages, + { type: "user", content: currentMessage }, + { type: "response", content: response.data.dataFromFastapi.output }, + { + type: "response", + content: timestamp, + }, + ]); + } catch (error) { + console.log("Failed to send user message: ", error); + } + + //clear message input setCurrentMessage(""); } }; diff --git a/langchain/.gitignore b/langchain/.gitignore new file mode 100644 index 00000000..ad4a1f17 --- /dev/null +++ b/langchain/.gitignore @@ -0,0 +1,176 @@ +# Created by https://www.toptal.com/developers/gitignore/api/python +# Edit at https://www.toptal.com/developers/gitignore?templates=python + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json + +# End of https://www.toptal.com/developers/gitignore/api/python diff --git a/langchain/Dockerfile b/langchain/Dockerfile new file mode 100644 index 00000000..b2beaaab --- /dev/null +++ b/langchain/Dockerfile @@ -0,0 +1,21 @@ +# Use an official Python runtime as a parent image +FROM python:3.10 + +# Set the working directory in the container +WORKDIR /usr/src/app + +# Copy the current directory contents into the container at /usr/src/app +COPY ./app /usr/src/app + +# Install any needed packages specified in requirements.txt +COPY requirements.txt ./ +RUN pip install --no-cache-dir -r requirements.txt + +# Make port 8000 available to the world outside this container +EXPOSE 8000 + +# Define environment variable +ENV UVICORN_HOST=0.0.0.0 UVICORN_PORT=8000 + +# Run uvicorn when the container launches +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] diff --git a/langchain/README.md b/langchain/README.md new file mode 100644 index 00000000..d4ebe3d4 --- /dev/null +++ b/langchain/README.md @@ -0,0 +1 @@ +Run Server with command: "uvicorn main:app --reload" diff --git a/langchain/__init__.py b/langchain/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/langchain/app/__init__.py b/langchain/app/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/langchain/app/langchain_agent.py b/langchain/app/langchain_agent.py new file mode 100644 index 00000000..0cd58c7b --- /dev/null +++ b/langchain/app/langchain_agent.py @@ -0,0 +1,27 @@ +#Logic for langchain +#Import requirements +from langchain.llms import OpenAI +from langchain.chains import LLMChain +from langchain.prompts import PromptTemplate + +#Load in the enviroment variables +from dotenv import load_dotenv +load_dotenv() + +#create function to recieve the input from user +def conversation(human_input): + template = f"{human_input}\nAssistant:" + + prompt = PromptTemplate( + input_variables=["human_input"], + template=template + ) + + chatgpt_chain = LLMChain( + llm=OpenAI(temperature=0.5), + prompt=prompt, + verbose=True, + ) + + output = chatgpt_chain.predict(human_input=human_input) + return output \ No newline at end of file diff --git a/langchain/app/main.py b/langchain/app/main.py new file mode 100644 index 00000000..ab9d0a89 --- /dev/null +++ b/langchain/app/main.py @@ -0,0 +1,44 @@ +from fastapi import FastAPI +from pydantic import BaseModel # Data validation +from langchain_agent import conversation +from fastapi.middleware.cors import CORSMiddleware + + +# Input must be a string +class InputData(BaseModel): + human_input: str +# Output must be a string + + +class Output(BaseModel): + output: str + + +# Create FastAPI app +app = FastAPI() + +# origins for localhost + +origins = [ + "http://localhost:3000", # Correct format + "http://localhost:5050", # If you also want to allow this port +] + + +# Add the middeware so that the express server can connect with this one +app.add_middleware( + CORSMiddleware, + allow_origins=origins, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# Endpoint for text input + + +@app.post("/process-lang") +async def input(input: InputData): # Take in the Inputdata string as param + # calls conversation function from langchain_agent + output = Output(output=conversation(input.human_input)) + return output # returns outpu diff --git a/langchain/requirements.txt b/langchain/requirements.txt new file mode 100644 index 00000000..7ad866b6 --- /dev/null +++ b/langchain/requirements.txt @@ -0,0 +1,42 @@ +aiohttp==3.8.6 +aiosignal==1.3.1 +annotated-types==0.6.0 +anyio==3.7.1 +async-timeout==4.0.3 +attrs==23.1.0 +certifi==2023.7.22 +charset-normalizer==3.3.2 +click==8.1.7 +dataclasses-json==0.6.2 +distro==1.8.0 +fastapi==0.104.1 +frozenlist==1.4.0 +h11==0.14.0 +httpcore==1.0.2 +httpx==0.25.1 +idna==3.4 +jsonpatch==1.33 +jsonpointer==2.4 +langchain==0.0.335 +langsmith==0.0.64 +marshmallow==3.20.1 +multidict==6.0.4 +mypy-extensions==1.0.0 +numpy==1.26.2 +openai==1.2.4 +packaging==23.2 +pydantic==2.5.0 +pydantic_core==2.14.1 +python-dotenv==1.0.0 +PyYAML==6.0.1 +requests==2.31.0 +sniffio==1.3.0 +SQLAlchemy==2.0.23 +starlette==0.27.0 +tenacity==8.2.3 +tqdm==4.66.1 +typing-inspect==0.9.0 +typing_extensions==4.8.0 +urllib3==2.1.0 +uvicorn==0.24.0.post1 +yarl==1.9.2