├── .gitignore ├── README.md ├── __pycache__ └── app.cpython-311.pyc ├── app.py ├── multivoice_agents.ipynb ├── static └── index.html └── test.db /.gitignore: -------------------------------------------------------------------------------- 1 | .venv 2 | .env 3 | __pycache__ 4 | test.db -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | "# LangGraph-Udemy-Course" 2 | -------------------------------------------------------------------------------- /__pycache__/app.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Coding-Crashkurse/LangGraph-Fullstack---Human-in-the-loop/cf3ae709b1d2ac0b63cc9586891e5ee6b652c2ee/__pycache__/app.cpython-311.pyc -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI, Depends, HTTPException, WebSocket, WebSocketDisconnect 2 | from sqlalchemy import Column, Integer, String, ForeignKey, DateTime, select, Boolean 3 | from sqlalchemy.ext.declarative import declarative_base 4 | from sqlalchemy.orm import relationship, sessionmaker 5 | from sqlalchemy import create_engine 6 | from sqlalchemy.orm import Session, selectinload 7 | from pydantic import BaseModel, validator 8 | from datetime import datetime, timedelta 9 | from typing import Optional, List 10 | from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm 11 | from passlib.context import CryptContext 12 | from fastapi.staticfiles import StaticFiles 13 | from fastapi.responses import HTMLResponse, JSONResponse 14 | import uvicorn 15 | from fastapi.middleware.cors import CORSMiddleware 16 | 17 | DATABASE_URL = "sqlite:///./test.db" 18 | 19 | Base = declarative_base() 20 | engine = create_engine(DATABASE_URL, echo=True) 21 | SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) 22 | 23 | app = FastAPI() 24 | app.add_middleware( 25 | CORSMiddleware, 26 | allow_origins=["*"], # Allows all origins, modify this as needed 27 | allow_credentials=True, 28 | allow_methods=["*"], 29 | allow_headers=["*"], 30 | ) 31 | 32 | pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") 33 | oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") 34 | 35 | app.mount("/static", StaticFiles(directory="static"), name="static") 36 | 37 | 38 | @app.get("/", response_class=HTMLResponse) 39 | def serve_index(): 40 | with open("static/index.html") as f: 41 | return HTMLResponse(content=f.read(), status_code=200) 42 | 43 | 44 | class User(Base): 45 | __tablename__ = "users" 46 | 47 | id = Column(Integer, primary_key=True, index=True) 48 | name = Column(String, unique=True, index=True) 49 | hashed_password = Column(String) 50 | is_admin = Column(Boolean, default=False) 51 | admin_decision = Column(String, nullable=True) 52 | user_contract = relationship( 53 | "UserContract", back_populates="user", uselist=False, lazy="selectin" 54 | ) 55 | 56 | 57 | class Contract(Base): 58 | __tablename__ = "contracts" 59 | 60 | id = Column(Integer, primary_key=True, index=True) 61 | category = Column(String, unique=True, index=True) 62 | 63 | 64 | class UserContract(Base): 65 | __tablename__ = "user_contracts" 66 | 67 | id = Column(Integer, primary_key=True, index=True) 68 | user_id = Column(Integer, ForeignKey("users.id")) 69 | contract_id = Column(Integer, ForeignKey("contracts.id")) 70 | contract_time = Column(DateTime, default=datetime.utcnow) 71 | 72 | user = relationship("User", back_populates="user_contract", lazy="selectin") 73 | contract = relationship("Contract", lazy="selectin") 74 | 75 | 76 | # Schemas 77 | class UserCreate(BaseModel): 78 | name: str 79 | password: str 80 | 81 | 82 | class ContractCreate(BaseModel): 83 | category: str 84 | user_id: int 85 | 86 | @validator("category") 87 | def category_must_be_valid(cls, v): 88 | if v not in ["basic", "normal", "premium"]: 89 | raise ValueError("Category must be one of: basic, normal, premium") 90 | return v 91 | 92 | 93 | class ContractUpdate(BaseModel): 94 | category: Optional[str] = None 95 | 96 | @validator("category") 97 | def category_must_be_valid(cls, v): 98 | if v and v not in ["basic", "normal", "premium"]: 99 | raise ValueError("Category must be one of: basic, normal, premium") 100 | return v 101 | 102 | 103 | class AskAdmin(BaseModel): 104 | action: str 105 | username: str 106 | category: Optional[str] = None 107 | 108 | 109 | class ConfirmAction(BaseModel): 110 | action: str 111 | username: str 112 | category: Optional[str] = None 113 | confirmed: bool 114 | 115 | 116 | # Dependencies 117 | def get_db(): 118 | db = SessionLocal() 119 | try: 120 | yield db 121 | finally: 122 | db.close() 123 | 124 | 125 | def get_user_by_name(db: Session, name: str): 126 | return db.query(User).filter(User.name == name).first() 127 | 128 | 129 | def get_user_by_id(db: Session, user_id: int): 130 | return db.query(User).filter(User.id == user_id).first() 131 | 132 | 133 | def get_current_user( 134 | db: Session = Depends(get_db), token: str = Depends(oauth2_scheme) 135 | ): 136 | user = get_user_by_name(db, token) 137 | if user is None: 138 | raise HTTPException( 139 | status_code=401, detail="Invalid authentication credentials" 140 | ) 141 | return user 142 | 143 | 144 | def get_current_admin_user( 145 | db: Session = Depends(get_db), token: str = Depends(oauth2_scheme) 146 | ): 147 | user = get_current_user(db, token) 148 | if not user.is_admin: 149 | raise HTTPException(status_code=403, detail="Not authorized") 150 | return user 151 | 152 | 153 | # Password Hashing 154 | def get_password_hash(password): 155 | return pwd_context.hash(password) 156 | 157 | 158 | def verify_password(plain_password, hashed_password): 159 | return pwd_context.verify(plain_password, hashed_password) 160 | 161 | 162 | clients = [] 163 | 164 | 165 | @app.websocket("/ws") 166 | async def websocket_endpoint(websocket: WebSocket): 167 | await websocket.accept() 168 | clients.append(websocket) 169 | try: 170 | while True: 171 | data = await websocket.receive_text() 172 | # Process incoming WebSocket messages if needed 173 | except WebSocketDisconnect: 174 | clients.remove(websocket) 175 | 176 | 177 | async def notify_clients(message: dict): 178 | for client in clients: 179 | await client.send_json(message) 180 | 181 | 182 | # Routes 183 | @app.post("/register/") 184 | def register(user: UserCreate, db: Session = Depends(get_db)): 185 | db_user = get_user_by_name(db, user.name) 186 | if db_user: 187 | raise HTTPException(status_code=400, detail="Username already registered") 188 | hashed_password = get_password_hash(user.password) 189 | new_user = User(name=user.name, hashed_password=hashed_password) 190 | db.add(new_user) 191 | db.commit() 192 | db.refresh(new_user) 193 | return new_user 194 | 195 | 196 | @app.post("/token/") 197 | def login( 198 | form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db) 199 | ): 200 | user = get_user_by_name(db, form_data.username) 201 | if not user or not verify_password(form_data.password, user.hashed_password): 202 | raise HTTPException(status_code=400, detail="Incorrect username or password") 203 | return {"access_token": user.name, "token_type": "bearer"} 204 | 205 | 206 | @app.get("/contracts/user/{username}") 207 | def get_contract_by_username(username: str, db: Session = Depends(get_db)): 208 | user = get_user_by_name(db, username) 209 | if not user: 210 | raise HTTPException(status_code=404, detail="User not found") 211 | if not user.user_contract: 212 | raise HTTPException(status_code=404, detail="No contract found for this user") 213 | 214 | contract_details = { 215 | "id": user.user_contract.id, 216 | "category": user.user_contract.contract.category, 217 | "contract_time": user.user_contract.contract_time, 218 | "user_id": user.id, 219 | } 220 | return contract_details 221 | 222 | 223 | @app.post("/contracts/") 224 | def create_contract( 225 | contract: ContractCreate, 226 | db: Session = Depends(get_db), 227 | current_admin_user: User = Depends(get_current_admin_user), 228 | ): 229 | user = get_user_by_id(db, contract.user_id) 230 | if not user: 231 | raise HTTPException(status_code=404, detail="User not found") 232 | 233 | existing_contract = ( 234 | db.query(UserContract).filter(UserContract.user_id == contract.user_id).first() 235 | ) 236 | if existing_contract: 237 | raise HTTPException(status_code=400, detail="User already has a contract") 238 | 239 | contract_category = db.query(Contract).filter_by(category=contract.category).first() 240 | if not contract_category: 241 | raise HTTPException(status_code=404, detail="Contract category not found") 242 | 243 | new_user_contract = UserContract( 244 | user_id=contract.user_id, 245 | contract_id=contract_category.id, 246 | contract_time=datetime.utcnow(), 247 | ) 248 | db.add(new_user_contract) 249 | db.commit() 250 | db.refresh(new_user_contract) 251 | 252 | return { 253 | "id": new_user_contract.id, 254 | "category": contract_category.category, 255 | "contract_time": new_user_contract.contract_time, 256 | "user_id": new_user_contract.user_id, 257 | } 258 | 259 | 260 | @app.put("/contracts/{contract_id}/") 261 | def update_contract( 262 | contract_id: int, 263 | contract: ContractUpdate, 264 | db: Session = Depends(get_db), 265 | current_admin_user: User = Depends(get_current_admin_user), 266 | ): 267 | db_contract = db.query(UserContract).get(contract_id) 268 | if not db_contract: 269 | raise HTTPException(status_code=404, detail="Contract not found") 270 | if db_contract.user_id != current_admin_user.id: 271 | raise HTTPException( 272 | status_code=403, detail="Not authorized to update this contract" 273 | ) 274 | for key, value in contract.dict(exclude_unset=True).items(): 275 | setattr(db_contract, key, value) 276 | db.commit() 277 | db.refresh(db_contract) 278 | return db_contract 279 | 280 | 281 | @app.delete("/contracts/{contract_id}/") 282 | def delete_contract( 283 | contract_id: int, 284 | db: Session = Depends(get_db), 285 | current_admin_user: User = Depends(get_current_admin_user), 286 | ): 287 | db_contract = db.query(UserContract).get(contract_id) 288 | if not db_contract: 289 | raise HTTPException(status_code=404, detail="Contract not found") 290 | if db_contract.user_id != current_admin_user.id: 291 | raise HTTPException( 292 | status_code=403, detail="Not authorized to delete this contract" 293 | ) 294 | db_contract.contract_time = datetime.utcnow() + timedelta(days=90) 295 | db.commit() 296 | 297 | return {"detail": "Contract will be cancelled in 3 months"} 298 | 299 | 300 | @app.post("/ask_admin/") 301 | async def ask_admin(request: AskAdmin): 302 | message = request.dict() 303 | await notify_clients(message) 304 | return {"message": "Admin approval requested"} 305 | 306 | 307 | @app.post("/confirm_action/") 308 | def confirm_action(request: ConfirmAction, db: Session = Depends(get_db)): 309 | user = get_user_by_name(db, request.username) 310 | if not user: 311 | raise HTTPException(status_code=404, detail="User not found") 312 | 313 | print( 314 | f"confirm_action: Received request to {request.action} for user {request.username} with confirmation {request.confirmed}" 315 | ) 316 | 317 | if not request.confirmed: 318 | user.admin_decision = "denied" 319 | db.commit() 320 | db.refresh(user) 321 | return {"message": "Admin denied the request"} 322 | 323 | user.admin_decision = "confirmed" 324 | db.commit() 325 | db.refresh(user) 326 | 327 | if request.action == "create": 328 | contract_category = ( 329 | db.query(Contract).filter_by(category=request.category).first() 330 | ) 331 | if not contract_category: 332 | raise HTTPException(status_code=404, detail="Contract category not found") 333 | 334 | new_user_contract = UserContract( 335 | user_id=user.id, 336 | contract_id=contract_category.id, 337 | contract_time=datetime.utcnow(), 338 | ) 339 | db.add(new_user_contract) 340 | db.commit() 341 | db.refresh(new_user_contract) 342 | 343 | return { 344 | "message": "Contract created", 345 | "id": new_user_contract.id, 346 | "category": contract_category.category, 347 | "contract_time": new_user_contract.contract_time, 348 | "user_id": new_user_contract.user_id, 349 | } 350 | 351 | 352 | @app.get("/users/") 353 | def get_all_users( 354 | db: Session = Depends(get_db), 355 | current_admin_user: User = Depends(get_current_admin_user), 356 | ): 357 | users = ( 358 | db.query(User) 359 | .options(selectinload(User.user_contract).selectinload(UserContract.contract)) 360 | .all() 361 | ) 362 | return users 363 | 364 | 365 | @app.get("/users/{username}") 366 | def get_user_by_username(username: str, db: Session = Depends(get_db)): 367 | user = get_user_by_name(db, username) 368 | if not user: 369 | raise HTTPException(status_code=404, detail="User not found") 370 | return user 371 | 372 | 373 | @app.get("/check_confirmation/{username}") 374 | def check_confirmation(username: str, db: Session = Depends(get_db)): 375 | user = ( 376 | db.query(User) 377 | .options(selectinload(User.user_contract).selectinload(UserContract.contract)) 378 | .filter(User.name == username) 379 | .first() 380 | ) 381 | 382 | if not user: 383 | print(f"check_confirmation: User '{username}' not found.") 384 | return {"message": "User not found"} 385 | 386 | # Capture the current admin decision 387 | user_decision = user.admin_decision 388 | # Reset admin decision to prevent stale state 389 | user.admin_decision = None 390 | db.commit() 391 | db.refresh(user) 392 | 393 | print( 394 | f"check_confirmation: Admin decision for user '{username}' is '{user_decision}'." 395 | ) 396 | 397 | # If the admin denied the request 398 | if user_decision == "denied": 399 | print(f"check_confirmation: Admin denied the request for user '{username}'.") 400 | return {"message": "Admin denied the request"} 401 | 402 | if not user.user_contract: 403 | if user_decision == "confirmed": 404 | print( 405 | f"check_confirmation: No contract found for user '{username}', approved to create." 406 | ) 407 | return { 408 | "message": "Contract created" 409 | } # Indicate a contract should be created 410 | print( 411 | f"check_confirmation: No contract found for user '{username}', awaiting approval." 412 | ) 413 | return {"message": "No Contract, awaiting approval"} 414 | 415 | if user.user_contract: 416 | contract = user.user_contract 417 | 418 | # Determine if the contract is newly created or set for deletion 419 | contract_age = datetime.utcnow() - contract.contract_time 420 | 421 | # Consider a contract "new" if it was just created within a reasonable timeframe, e.g., a minute 422 | if user_decision == "confirmed" and contract_age.total_seconds() < 60: 423 | print( 424 | f"check_confirmation: Contract for user '{username}' confirmed as created." 425 | ) 426 | return { 427 | "message": "Contract created", 428 | "id": contract.id, 429 | "category": contract.contract.category, 430 | "contract_time": contract.contract_time, 431 | "user_id": contract.user_id, 432 | } 433 | 434 | # If the contract is marked for deletion (i.e., contract_time + 90 days is in the future) 435 | if contract.contract_time + timedelta(days=90) >= datetime.utcnow(): 436 | print( 437 | f"check_confirmation: Contract for user '{username}' confirmed for deletion." 438 | ) 439 | return { 440 | "message": "Contract will be cancelled in 3 months", 441 | "id": contract.id, 442 | "category": contract.contract.category, 443 | "contract_time": contract.contract_time, 444 | "user_id": contract.user_id, 445 | } 446 | 447 | print(f"check_confirmation: Waiting for admin confirmation for user '{username}'.") 448 | return {"message": "Waiting for admin confirmation"} 449 | 450 | 451 | @app.on_event("startup") 452 | def startup(): 453 | Base.metadata.create_all(bind=engine) 454 | db = SessionLocal() 455 | categories = ["basic", "normal", "premium"] 456 | for category in categories: 457 | existing_contract = db.query(Contract).filter_by(category=category).first() 458 | if not existing_contract: 459 | new_contract = Contract(category=category) 460 | db.add(new_contract) 461 | db.commit() 462 | 463 | admin1 = get_user_by_name(db, "admin1") 464 | if not admin1: 465 | hashed_password = get_password_hash("admin1password") 466 | admin1 = User(name="admin1", hashed_password=hashed_password, is_admin=True) 467 | db.add(admin1) 468 | 469 | admin2 = get_user_by_name(db, "admin2") 470 | if not admin2: 471 | hashed_password = get_password_hash("admin2password") 472 | admin2 = User(name="admin2", hashed_password=hashed_password, is_admin=True) 473 | db.add(admin2) 474 | 475 | db.commit() 476 | db.close() 477 | 478 | 479 | if __name__ == "__main__": 480 | uvicorn.run("app:app", host="127.0.0.1", port=8000, log_level="info") 481 | -------------------------------------------------------------------------------- /multivoice_agents.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "### LangGraph Agent - Customer Support multivoice Agent" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": null, 13 | "metadata": {}, 14 | "outputs": [], 15 | "source": [ 16 | "from dotenv import load_dotenv\n", 17 | "\n", 18 | "load_dotenv()" 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": null, 24 | "metadata": {}, 25 | "outputs": [], 26 | "source": [ 27 | "from langchain_core.prompts import PromptTemplate\n", 28 | "from langchain_openai import ChatOpenAI\n", 29 | "\n", 30 | "system = \"\"\"You are Andrea, a knowledgeable and friendly assistant in a telecommunications company. Your expertise lies in various mobile plans and upgrades. Your role is to help users understand their options and assist them with their queries about mobile plans and upgrades. Always respond in a helpful and professional manner.\n", 31 | "Always speak to the user with his name.\n", 32 | "\n", 33 | "Username: {username}\n", 34 | "\n", 35 | "Remember, your goal is to make the user feel supported and informed. Always be courteous and clear in your responses.\n", 36 | "\"\"\"\n", 37 | "prompt_template = PromptTemplate.from_template(system)" 38 | ] 39 | }, 40 | { 41 | "cell_type": "code", 42 | "execution_count": null, 43 | "metadata": {}, 44 | "outputs": [], 45 | "source": [ 46 | "import requests\n", 47 | "from langchain_core.tools import tool\n", 48 | "from typing import Optional\n", 49 | "import time\n", 50 | "\n", 51 | "API_URL = \"http://127.0.0.1:8000\"\n", 52 | "ADMIN_USERNAME = \"admin1\"\n", 53 | "ADMIN_PASSWORD = \"admin1password\"\n", 54 | "\n", 55 | "categories = [\"basic\", \"normal\", \"premium\"]\n", 56 | "\n", 57 | "def login():\n", 58 | " login_response = requests.post(f\"{API_URL}/token/\", data={\"username\": ADMIN_USERNAME, \"password\": ADMIN_PASSWORD})\n", 59 | " if login_response.status_code != 200:\n", 60 | " print(f\"Login failed: {login_response.json().get('detail')}\")\n", 61 | " return None, f\"Login failed: {login_response.json().get('detail')}\"\n", 62 | " access_token = login_response.json().get(\"access_token\")\n", 63 | " headers = {\"Authorization\": f\"Bearer {access_token}\"}\n", 64 | " print(\"Login successful, headers obtained\")\n", 65 | " return headers, None\n", 66 | "\n", 67 | "def ask_admin(action: str, username: str, category: Optional[str] = None):\n", 68 | " try:\n", 69 | " headers, error = login()\n", 70 | " if error:\n", 71 | " return None, error\n", 72 | "\n", 73 | " ask_data = {\"action\": action, \"username\": username}\n", 74 | " if category:\n", 75 | " ask_data[\"category\"] = category\n", 76 | "\n", 77 | " print(f\"Requesting admin approval with data: {ask_data}\")\n", 78 | " response = requests.post(f\"{API_URL}/ask_admin/\", json=ask_data, headers=headers)\n", 79 | " if response.status_code != 200:\n", 80 | " print(f\"Failed to request admin approval: {response.json().get('detail')}\")\n", 81 | " return None, f\"Failed to request admin approval: {response.json().get('detail')}\"\n", 82 | "\n", 83 | " print(\"Admin approval requested\")\n", 84 | " return \"Admin approval requested\", None\n", 85 | " except Exception as e:\n", 86 | " print(f\"Failed to execute ask_admin. Error: {repr(e)}\")\n", 87 | " return None, f\"Failed to execute. Error: {repr(e)}\"\n", 88 | "\n", 89 | "def wait_for_admin_approval(action: str, username: str, category: str = None):\n", 90 | " print(\"Waiting for admin approval...\")\n", 91 | " while True:\n", 92 | " response = requests.get(f\"{API_URL}/check_confirmation/{username}\")\n", 93 | " if response.status_code == 200:\n", 94 | " result = response.json()\n", 95 | " print(f\"Received admin approval response: {result}\")\n", 96 | " message = result.get(\"message\")\n", 97 | " # Exit loop if a final decision has been made\n", 98 | " if message in [\"Admin denied the request\", \"Contract created\", \"Contract will be cancelled in 3 months\"]:\n", 99 | " return result\n", 100 | " time.sleep(2) # Add a delay before retrying to avoid spamming the server\n", 101 | "\n", 102 | "\n", 103 | "@tool\n", 104 | "def create_contract_tool(username: str, category: str):\n", 105 | " \"\"\"\n", 106 | " Create a new contract for a user with a specific category.\n", 107 | "\n", 108 | " Args:\n", 109 | " username (str): Username of the user for whom the contract is being created.\n", 110 | " category (str): Category of the contract. Must be one of \"basic\", \"normal\", or \"premium\".\n", 111 | "\n", 112 | " Returns:\n", 113 | " str: A string indicating the result of the admin approval process and contract creation.\n", 114 | " \"\"\"\n", 115 | " print(f\"Starting contract creation for user: {username}, category: {category}\")\n", 116 | "\n", 117 | " # Step 0: Check if the user already has a contract\n", 118 | " headers, error = login()\n", 119 | " if error:\n", 120 | " print(f\"Error during login: {error}\")\n", 121 | " return error\n", 122 | "\n", 123 | " print(f\"Fetching contract details for username: {username}\")\n", 124 | " user_contract_response = requests.get(f\"{API_URL}/contracts/user/{username}\", headers=headers)\n", 125 | " if user_contract_response.status_code == 200:\n", 126 | " user_contract = user_contract_response.json()\n", 127 | " print(f\"User contract details: {user_contract}\")\n", 128 | " # Check if the user has a valid contract category\n", 129 | " if user_contract.get('category') in categories:\n", 130 | " return f\"User already has a contract: {user_contract}\"\n", 131 | " else:\n", 132 | " print(\"No valid contract found for the user.\")\n", 133 | " elif user_contract_response.status_code == 404:\n", 134 | " print(\"No contract found for the user.\")\n", 135 | " else:\n", 136 | " print(f\"Failed to fetch user contract details: {user_contract_response.json().get('detail')}\")\n", 137 | " return f\"Failed to fetch user contract details: {user_contract_response.json().get('detail')}\"\n", 138 | "\n", 139 | " # Step 1: Request admin approval\n", 140 | " admin_request, error = ask_admin(\"create\", username, category)\n", 141 | " if error:\n", 142 | " print(f\"Error during admin approval request: {error}\")\n", 143 | " return error\n", 144 | "\n", 145 | " # Inform that admin approval is requested\n", 146 | " if admin_request == \"Admin approval requested\":\n", 147 | " # Wait for admin approval\n", 148 | " approval_result = wait_for_admin_approval(\"create\", username, category)\n", 149 | " print(\"APPROVAL RESULT: \", approval_result)\n", 150 | "\n", 151 | " if approval_result.get(\"message\") == \"Admin denied the request\":\n", 152 | " print(f\"Admin denied the request: {approval_result.get('message')}\")\n", 153 | " return approval_result.get('message')\n", 154 | " elif approval_result.get(\"message\") == \"Contract created\":\n", 155 | " print(f\"Admin created the contract: {approval_result}\")\n", 156 | " return f\"Contract successfully created: ID {approval_result['id']}, Category {approval_result['category']}, Contract Time {approval_result['contract_time']}, User ID {approval_result['user_id']}\"\n", 157 | "\n", 158 | " return \"Unexpected flow reached\"\n", 159 | "\n", 160 | "tools = [create_contract_tool]" 161 | ] 162 | }, 163 | { 164 | "cell_type": "code", 165 | "execution_count": null, 166 | "metadata": {}, 167 | "outputs": [], 168 | "source": [ 169 | "from langchain_core.messages.human import HumanMessage\n", 170 | "from langchain_core.messages.system import SystemMessage\n", 171 | "\n", 172 | "sys_msg = [SystemMessage(content=prompt_template.format(username=\"hans\"))]\n", 173 | "hu_msg = [HumanMessage(content=\"Please create a my premium contract for me\")]\n", 174 | "\n", 175 | "chat_history = []\n", 176 | "\n", 177 | "messages = sys_msg + chat_history + hu_msg\n", 178 | "model = ChatOpenAI(model=\"gpt-4o-mini\")\n", 179 | "model_with_tools = model.bind_tools(tools=tools)\n", 180 | "\n", 181 | "result = model_with_tools.invoke(messages)" 182 | ] 183 | }, 184 | { 185 | "cell_type": "code", 186 | "execution_count": null, 187 | "metadata": {}, 188 | "outputs": [], 189 | "source": [ 190 | "messages.append(result)" 191 | ] 192 | }, 193 | { 194 | "cell_type": "code", 195 | "execution_count": null, 196 | "metadata": {}, 197 | "outputs": [], 198 | "source": [ 199 | "result.tool_calls" 200 | ] 201 | }, 202 | { 203 | "cell_type": "code", 204 | "execution_count": null, 205 | "metadata": {}, 206 | "outputs": [], 207 | "source": [ 208 | "from langchain_core.messages import ToolMessage\n", 209 | "\n", 210 | "for tool_call in result.tool_calls:\n", 211 | " print(\"Use Tool:\", tool_call)\n", 212 | " selected_tool = {tool.name.lower(): tool for tool in tools}[tool_call[\"name\"].lower()]\n", 213 | " tool_output = selected_tool.invoke(tool_call[\"args\"])\n", 214 | " print(tool_output)\n", 215 | " messages.append(ToolMessage(tool_output, tool_call_id=tool_call[\"id\"]))" 216 | ] 217 | }, 218 | { 219 | "cell_type": "code", 220 | "execution_count": null, 221 | "metadata": {}, 222 | "outputs": [], 223 | "source": [ 224 | "messages" 225 | ] 226 | }, 227 | { 228 | "cell_type": "code", 229 | "execution_count": null, 230 | "metadata": {}, 231 | "outputs": [], 232 | "source": [ 233 | "model_with_tools.invoke(messages)" 234 | ] 235 | } 236 | ], 237 | "metadata": { 238 | "kernelspec": { 239 | "display_name": ".venv", 240 | "language": "python", 241 | "name": "python3" 242 | }, 243 | "language_info": { 244 | "codemirror_mode": { 245 | "name": "ipython", 246 | "version": 3 247 | }, 248 | "file_extension": ".py", 249 | "mimetype": "text/x-python", 250 | "name": "python", 251 | "nbconvert_exporter": "python", 252 | "pygments_lexer": "ipython3", 253 | "version": "3.11.0" 254 | } 255 | }, 256 | "nbformat": 4, 257 | "nbformat_minor": 2 258 | } 259 | -------------------------------------------------------------------------------- /static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 |ID | 60 |Name | 61 |Is Admin | 62 |Contract Category | 63 |Contract Time | 64 |
---|