From 95d357de60944d7fdfe6db8a1293bc849fb15a50 Mon Sep 17 00:00:00 2001 From: OwusuBlessing Date: Thu, 24 Jul 2025 14:27:56 +0100 Subject: [PATCH] setup assisant bot --- .env | 2 + .env.example | 2 + .gitignore | 0 .python-version | 0 Dockerfile | 0 README.md | 0 __pycache__/config.cpython-311.pyc | Bin 0 -> 745 bytes __pycache__/test.cpython-311.pyc | Bin 0 -> 21712 bytes api/__init__.py | 0 api/__pycache__/__init__.cpython-311.pyc | Bin 0 -> 173 bytes api/__pycache__/config.cpython-311.pyc | Bin 0 -> 1366 bytes api/config.py | 19 + .../__pycache__/auth.cpython-311.pyc | Bin 0 -> 1367 bytes api/dependencies/auth.py | 25 + .../__pycache__/requests.cpython-311.pyc | Bin 0 -> 1123 bytes .../__pycache__/responses.cpython-311.pyc | Bin 0 -> 550 bytes api/models/requests.py | 15 + api/models/responses.py | 8 + api/routes/__init__.py | 0 .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 180 bytes api/routes/__pycache__/chat.cpython-311.pyc | Bin 0 -> 1982 bytes api/routes/chat.py | 38 ++ config.py | 15 + main.py | 40 ++ pyproject.toml | 0 requirements.txt | 21 + scripts/evaluate_prompts.py | 0 src/__init__.py | 0 src/__pycache__/__init__.cpython-311.pyc | Bin 0 -> 173 bytes src/chains/__init__.py | 0 src/chains/base_chain.py | 0 src/config/__init__.py | 0 .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 180 bytes .../__pycache__/llm_config.cpython-311.pyc | Bin 0 -> 2999 bytes src/config/llm_config.py | 60 +++ src/llm/__init__.py | 0 src/llm/__pycache__/__init__.cpython-311.pyc | Bin 0 -> 177 bytes .../__pycache__/orchestrator.cpython-311.pyc | Bin 0 -> 21728 bytes src/llm/clients/__init__.py | 0 src/llm/clients/openai_client.py | 0 src/llm/orchestrator.py | 446 ++++++++++++++++++ src/llm/tools/__init__.py | 5 + .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 509 bytes .../booking_calendar.cpython-311.pyc | Bin 0 -> 1369 bytes src/llm/tools/booking_calendar.py | 32 ++ src/prompts/__init__.py | 0 .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 181 bytes .../__pycache__/manager.cpython-311.pyc | Bin 0 -> 1702 bytes .../__pycache__/setup_prompt.cpython-311.pyc | Bin 0 -> 626 bytes src/prompts/manager.py | 24 + src/prompts/setup_prompt.py | 8 + src/prompts/templates/__init__.py | 0 .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 191 bytes .../chat_templates.cpython-311.pyc | Bin 0 -> 8516 bytes src/prompts/templates/chat_templates.py | 226 +++++++++ src/prompts/validation/__init__.py | 0 src/prompts/validation/prompt_validator.py | 0 template.py | 44 ++ test.py | 38 ++ tests/__init__.py | 0 tests/integration/bot_chat.py | 38 ++ tests/unit/test_prompt_manager.py | 0 62 files changed, 1106 insertions(+) create mode 100644 .env create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 .python-version create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 __pycache__/config.cpython-311.pyc create mode 100644 __pycache__/test.cpython-311.pyc create mode 100644 api/__init__.py create mode 100644 api/__pycache__/__init__.cpython-311.pyc create mode 100644 api/__pycache__/config.cpython-311.pyc create mode 100644 api/config.py create mode 100644 api/dependencies/__pycache__/auth.cpython-311.pyc create mode 100644 api/dependencies/auth.py create mode 100644 api/models/__pycache__/requests.cpython-311.pyc create mode 100644 api/models/__pycache__/responses.cpython-311.pyc create mode 100644 api/models/requests.py create mode 100644 api/models/responses.py create mode 100644 api/routes/__init__.py create mode 100644 api/routes/__pycache__/__init__.cpython-311.pyc create mode 100644 api/routes/__pycache__/chat.cpython-311.pyc create mode 100644 api/routes/chat.py create mode 100644 config.py create mode 100644 main.py create mode 100644 pyproject.toml create mode 100644 requirements.txt create mode 100644 scripts/evaluate_prompts.py create mode 100644 src/__init__.py create mode 100644 src/__pycache__/__init__.cpython-311.pyc create mode 100644 src/chains/__init__.py create mode 100644 src/chains/base_chain.py create mode 100644 src/config/__init__.py create mode 100644 src/config/__pycache__/__init__.cpython-311.pyc create mode 100644 src/config/__pycache__/llm_config.cpython-311.pyc create mode 100644 src/config/llm_config.py create mode 100644 src/llm/__init__.py create mode 100644 src/llm/__pycache__/__init__.cpython-311.pyc create mode 100644 src/llm/__pycache__/orchestrator.cpython-311.pyc create mode 100644 src/llm/clients/__init__.py create mode 100644 src/llm/clients/openai_client.py create mode 100644 src/llm/orchestrator.py create mode 100644 src/llm/tools/__init__.py create mode 100644 src/llm/tools/__pycache__/__init__.cpython-311.pyc create mode 100644 src/llm/tools/__pycache__/booking_calendar.cpython-311.pyc create mode 100644 src/llm/tools/booking_calendar.py create mode 100644 src/prompts/__init__.py create mode 100644 src/prompts/__pycache__/__init__.cpython-311.pyc create mode 100644 src/prompts/__pycache__/manager.cpython-311.pyc create mode 100644 src/prompts/__pycache__/setup_prompt.cpython-311.pyc create mode 100644 src/prompts/manager.py create mode 100644 src/prompts/setup_prompt.py create mode 100644 src/prompts/templates/__init__.py create mode 100644 src/prompts/templates/__pycache__/__init__.cpython-311.pyc create mode 100644 src/prompts/templates/__pycache__/chat_templates.cpython-311.pyc create mode 100644 src/prompts/templates/chat_templates.py create mode 100644 src/prompts/validation/__init__.py create mode 100644 src/prompts/validation/prompt_validator.py create mode 100644 template.py create mode 100644 test.py create mode 100644 tests/__init__.py create mode 100644 tests/integration/bot_chat.py create mode 100644 tests/unit/test_prompt_manager.py diff --git a/.env b/.env new file mode 100644 index 0000000..8f981a5 --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +OPENAI_API_KEY="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" +API_KEY="drbot-nhLybL86VBiUTtyngshsdj9efmc" diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..6693e7a --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +OPENAI_API_KEY="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" +API_KEY="drbot-nhLybL86VBiUTtyngshsdj9efmc" \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..e69de29 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/__pycache__/config.cpython-311.pyc b/__pycache__/config.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..62915ce31e6f1cd22d84637bedd69c3f85d7542d GIT binary patch literal 745 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 literal 0 HcmV?d00001 diff --git a/api/__pycache__/config.cpython-311.pyc b/api/__pycache__/config.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dd7d02093aaa3410c7e2f1ae39327d8c7aee0f28 GIT binary patch literal 1366 zcmZuw&2Q646rZst&UXu2=#my_5r>jJB=m|9QV9_%ETL*zBzzgU@wCl0vAbgjiHd`f zdMMH!A}+mfE?V(d?0?`$dx(5p?SWgWaO#Qo#tBh?@ta@X@4fNlefeWPpCPdRefD<$ zA4QO4nPgoiG!WXyh@m*)g#bf}ZJwe1aV> z)6j^}&wzXyn}}@)V^_12$86eeChb(66w{yaWRXU>hn6Sm!uOlbo(DYLk{#Z#8v6o> zQE{E!=3e95t)Q?G86JUhN(3>fO-yYYGB9<)M8ekYgU8lQLoudU!b~b=D3&x+ilvZ= z^dG8Dc(0jN_B7g6z8T0jd!|8Vu;XQ>?Hpu~k5c2XkCJ8Uuvvb!8fDft*7=K7Ufy^#*jV+`z zc+b=7OTJdARO|Jr0zfO(8vX)5lLBD+wg!Re7qjEQN^$+RYDE^MICreJ;5^E3-gfOy z3-ui5e|D_a$TQP`W^hk9Uej;B5`U8+{?o;S^0p_WS8iL49oIc5KN8-7?;e(STEg?7 zZ`^8@+Xr@8CCnjVRgQ-0UFq<(#9@=O05hB_ zmNEqp>7Fm6SaA$Z!I3t0iA#|i_3 zEZ+K13ToX-$nJpj*qs5RiJJrRBb=k{%TF9}xEN_{oZvvbWe>@Vv zroT2A2U;uAIOpj)M7cfTL$Bj6PhN+2vECk5F(ChE=ic4>>t4vNs!(dhRCM|9wQV_m zvoU!IV~Enb9jD>DZp)Jlxe+%$?z$>A-u+z{E>id78GIm@!HWra9{>hArL<3e3#X+| zmcnW2lR`Kx1D2ss Settings: + return Settings() \ No newline at end of file diff --git a/api/dependencies/__pycache__/auth.cpython-311.pyc b/api/dependencies/__pycache__/auth.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d2128790aa51c4d959f8e4c56127bac3600a514c GIT binary patch literal 1367 zcmZ`(&2Jk;6rb7kdhPW()N&Doq|ruIC032RmmW%mKrJy!l?s)9pwXqOP>MiC)-XxwHnPoU zGHk19dE?HVoApmT+7*5rrTRMU(unZ1aGQEP=8GL0r{>k0*Ei|T26YK#V3)4BoZg}z z_9z!%=HH5=mcN~rul7VcW`5t5sou7gG#9xcO%1mv;wEJ*X6b_4^_v~K(`;wS(mbQ0 z$0F~fUZ^S$<4YJvzk}FB7_iz!?8RL)0Q-3&c>cuvtN`R^L=>Wu9MOJI9_8op`T|47 z6ZB^D<(KI58A)OBB>XAf#X?p%r4Mp5t&{D_p5MKA%I+BM!RRl)=LSA;1+@TW3!t%D zEDo&+K7IWfbs1%r{|z9Nns+0>GQ*gX{>p5D&#lns+>f@c7^X%a$&59?Uz>oghm>=7 zn}Vmrg)4fz>BWSmc|wKj2glfYCh_mTG)qWg%B?6CR_F?^U9+{+fOSmpkA2Z*@|>y0 zyMZrQLFziJYi`!>+X^$Yn20+x;u2fSI==4Q<&<$JbiJ)O?l|j|cSPKEwgRXMYHqr| z6LyG0SRB#jRxBLo5r<@bNF&du+>wW_b$8OzHWkfj2eBncFSqAEg7_1Dd+4yVI7T_m z7_2;5y>hU6&G&*-NxqL6F-Ul9Q>I=qS2*lq~jFDP2#s(@c zJu$5V(;8Iw%?m^G!oVGxukO_kb@RLYw+jdQN}{j)C)vA6^%n3c$;`^$jUy-!y str: + """Validate API key from header""" + if not api_key_header or not api_key_header.startswith('Bearer '): + raise HTTPException( + status_code=401, + detail={"error": "Unauthorized", "message": "API key is missing or invalid."} + ) + + token = api_key_header.split(' ')[1] + + if token != Config.API_KEY: + raise HTTPException( + status_code=401, + detail={"error": "Unauthorized", "message": "API key does not match."} + ) + + return token + diff --git a/api/models/__pycache__/requests.cpython-311.pyc b/api/models/__pycache__/requests.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7adad53e4d12df38e2d210d13278c7a6a595f667 GIT binary patch literal 1123 zcmZuvF>ezw6t?d!$t7u8iVz41Rf1-Tka`S^6$4a?5~V=`x@55~v6V!<+@lWBB8#^kYszkK za#03)rRX|?w73m=9bJ6W^W#%U(C-TE$S4px*NMVdgdjg7b9j)8kO$DV zilyFT%n!pT_TzpOD#j$%M4HkLT6nJpaG!bxuu4B6vqCAUCACRyR=SrolEwr^(nz=C z&bt-81mktjIkEkFf-p&#Q`a-OagWowd?oP?u)NiGfVoX6{Y4I@>wQKZP1pO}p)@}y n+i+R=i>ucwDr?dGIoSsEgw^X6Rj_CoJvSlsETiWpW$xo2t4{Hi literal 0 HcmV?d00001 diff --git a/api/models/__pycache__/responses.cpython-311.pyc b/api/models/__pycache__/responses.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..59b035a7459ed49313705c67743f607443456ad3 GIT binary patch literal 550 zcmZuty-or_5T3mQMZ$@^fQ7NShO148g_>vrQfO$d*(~e^F5KOow?{$ck@x_1yn)8} zCRUc@DibR^3|3Um9*}5sc4y|BpPkv6N6RV$(o1_XxJCW0jSUsoU{Oaf1rD6}5D=FT zFyI`xaR=NK`sRZa&ixe_Xpp%U4aN?0;d*>)h5VtvuJi&=@%kOhL$tW0Sj`N-F} zLg~$j`RjuN&Q6g4Oz%H* z^Qdi`St%NG7A9UV*2`KIv^2`Is)&}Q#%jppxP~S{R!tOhlMwO-`>((6e9IuUIs6s( NN1xjJ^-m~o_6;{DiM;>- literal 0 HcmV?d00001 diff --git a/api/models/requests.py b/api/models/requests.py new file mode 100644 index 0000000..67c5450 --- /dev/null +++ b/api/models/requests.py @@ -0,0 +1,15 @@ +# api/models/requests.py +from pydantic import BaseModel +from typing import List + +class BaseRequest(BaseModel): + pass + +class ChatMessage(BaseModel): + role: str # "human" or "ai" + content: str + + +class ChatRequest(BaseModel): + query: str + history: List[ChatMessage] = [] diff --git a/api/models/responses.py b/api/models/responses.py new file mode 100644 index 0000000..5edff3a --- /dev/null +++ b/api/models/responses.py @@ -0,0 +1,8 @@ +# api/models/responses.py +from pydantic import BaseModel + + +class ChatResponse(BaseModel): + status: str + message: str + diff --git a/api/routes/__init__.py b/api/routes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/routes/__pycache__/__init__.cpython-311.pyc b/api/routes/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6a08c8218d1f5f1d2f1842bada2230dcf9ae4975 GIT binary patch literal 180 zcmZ3^%ge<81hc#vGeGoX5CH>>P{wCAAY(d13PUi1CZpd7%Q6rR~%d;QngNt5DIxhkSHRcu5>;ZUVYYa|e&s@fvNB4llLC(bszUUz1! zG*yB^se}(4S_HHQ4mqGA)JSmPz>(fKAmm6sSSwYkgplCoDlLk7;*HlfF)Fh&Z{K_K z-u!&;&HFZ$G7z+{&V9U8QxN)H2*aign4RB%xs4p;U=R7&#J*%ozHG`^=p;|^Ra5mf zQ|rsJr~5H8*5?&3?i;4j=T$G^C(WdvGE*2yh+s!^^cvoZ-IWl!2P0}`nxr=~Luv2d ziIdotF>Vd(NW{2{VFegbTf|O+s7{jXTU4CX4fQUl?+quBIpL%)qGIMRp()B?eE$4f z7lI}yG}J4kLFx_*Q*U0nbpDO2HfeA-s28O$adye#7s!<+VLTGvA&glyQp900nq!Ti z&V-z(5ni<#ZgrWgf@`cogE~1A@K7K4T#J=$SZ>yGLq#xPq%|m6bgzaAw`wdzuu7Ve zXvL}@IK&HeI@ldo?GaXR10BXEFm}>#|F;o_IK?4OH_#fo4C<&=&^QL`5|@W_2(3wu zv?Y(@x-PHDn|t>enZvpx-*Md66&@d%AxBwL9QD9Rv89dga1k9ryq|lK$qR@cSyy># z~YF2?R$Fsh@+fQvq@t)d0>9pI^s(kIfpsJd%JPfwhIkFM3P*><}az zhA=BM@JOiJL7kI2FUm9ltJ5h#_5%S6sq}Iv`>e*qa)rXvv*mXgp{(p%_CgRWmn(!V z^Po{)@F4%-9#zXN`^!$*5lL2E2zVJXuS`WgvwoJ98mmu@Qy$#W4*bU4wY(=VZm!ODJ3*KB5 zMezn898n2Y8UakcD7F*me}`yZ@FFfxW2fL)O^?jc6CjGW$NmP=lQG7CG(Oozul`@} zqSxEc>RmM39`Dc6ywXO`=nv!B)`4zfs%z|P%|JX13F2W$5D!Dz(0T@vVxQ)(=UW#x zlN%E&FY^M%)Qipr$G=|Ut>856SGW;Lv#=pw`%p(~|CE+R!ZTspz GF8=_{>GpB} literal 0 HcmV?d00001 diff --git a/api/routes/chat.py b/api/routes/chat.py new file mode 100644 index 0000000..0fe5dfd --- /dev/null +++ b/api/routes/chat.py @@ -0,0 +1,38 @@ +# api/routes/chat_ai.py + +from fastapi import APIRouter, Depends, HTTPException +from api.models.requests import ChatRequest, ChatMessage +from api.models.responses import ChatResponse +from api.dependencies.auth import get_api_key +from src.llm.orchestrator import DroneBot, Message # Adjust import as needed + +router = APIRouter( + prefix="/chat-ai", + tags=["chat"] +) + +@router.post("", response_model=ChatResponse) +async def chat_ai( + request: ChatRequest, + _: str = Depends(get_api_key) +): + """Chat with DroneBot using query and history.""" + try: + # Convert to internal Message format + history = [Message(role=msg.role, content=msg.content) for msg in request.history] + + # Initialize DroneBot with history + bot = DroneBot(history=history, use_openai_as_fallback=True) + + # Get response + result = bot.chat(request.query) + + return ChatResponse( + status="success", + message=result["final_message"] + ) + except Exception as e: + raise HTTPException( + status_code=500, + detail=str(e) + ) diff --git a/config.py b/config.py new file mode 100644 index 0000000..fb13146 --- /dev/null +++ b/config.py @@ -0,0 +1,15 @@ + +from dotenv import load_dotenv +import os +import json +import asyncio +import random + + +load_dotenv(override=True) + + +class Config: + OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") + API_KEY = os.getenv("API_KEY") + \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..81a1a76 --- /dev/null +++ b/main.py @@ -0,0 +1,40 @@ +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from api.routes import chat +from api.config import Settings + + +settings = Settings() + +app = FastAPI( + title=settings.PROJECT_NAME, + description=settings.DESCRIPTION, + version=settings.VERSION, + openapi_url=f"{settings.API_V1_STR}/openapi.json", + docs_url=f"{settings.API_V1_STR}/docs", + redoc_url=f"{settings.API_V1_STR}/redoc", +) + +# Add CORS middleware +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], # Modify this in production + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# Include routers +app.include_router(chat.router, prefix=settings.API_V1_STR) + +@app.get("/") +async def root(): + return { + "message": "Welcome to DroneBot AI API", + "version": settings.VERSION, + "docs_url": f"{settings.API_V1_STR}/docs" + } + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=5120) \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..e69de29 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..c39cffa --- /dev/null +++ b/requirements.txt @@ -0,0 +1,21 @@ +numpy==1.26.4 +openai==1.58.1 +pandas==2.2.3 +python-dotenv==1.0.1 +reportlab==4.2.5 +requests==2.32.3 +fastapi +Jinja2==3.1.5 +matplotlib==3.8.2 +slack_sdk==3.34.0 +tabulate==0.9.0 +python-multipart==0.0.20 +langgraph==0.3.2 +langchain==0.3.19 +langchain-openai== 0.3.7 +langchain-anthropic==0.3.9 +gunicorn==23.0.0 +tqdm +uvicorn[standard] +cryptography +pydantic-settings \ No newline at end of file diff --git a/scripts/evaluate_prompts.py b/scripts/evaluate_prompts.py new file mode 100644 index 0000000..e69de29 diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/__pycache__/__init__.cpython-311.pyc b/src/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0cf334cff35f00051d288c88337dedd716495316 GIT binary patch literal 173 zcmZ3^%ge<81e#in86f&Gh=2h`DC095kTIPhg&~+hlhJP_LlF~@{~09t%RxW1IJKx) zKQ}QsDL+43-zBv;yClCrKPe}*xHvN}Jw7p0KQ}u?Kcy%?FEu_XzeK;dC|N%~J~J<~ rBtBlRpz;@oO>TZlX-=wL5i8I@kd?*!K;i>4BO~Jn1{hJq3={(Z=Vd90 literal 0 HcmV?d00001 diff --git a/src/chains/__init__.py b/src/chains/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/chains/base_chain.py b/src/chains/base_chain.py new file mode 100644 index 0000000..e69de29 diff --git a/src/config/__init__.py b/src/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/config/__pycache__/__init__.cpython-311.pyc b/src/config/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f8f888262b763b334da052c5c63e8beebfa42707 GIT binary patch literal 180 zcmZ3^%ge<81e#in86f&Gh=2h`DC095kTIPhg&~+hlhJP_LlF~@{~09t%UwUTIJKx) zKQ}QsDL+43-zBv;yClCrKPe}*xHvN}Jw7p0KQ}u?Kcy%?FEu_XzeK;dC|N%_KQApa yT|YiPGcU6wK3=b&@)w6qZhlH>PO4oIE6`k!&Bgpc;sY}yBjX1K7*WIw6axTK8Z0#c literal 0 HcmV?d00001 diff --git a/src/config/__pycache__/llm_config.cpython-311.pyc b/src/config/__pycache__/llm_config.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..667360d0b6cfa826c289c94afda95b326fc4d30b GIT binary patch literal 2999 zcma)8&1)M+6ra^HDhUONvqDT zj3X3COAb2r7Pn2nr!;Ax_)qAug%%{LgSrq1loSd*$k@I3)Hk#GRxlaOym|B9yf<%l ze)C2@3=DV?l(#N?mHN<)&|hrOEY1eA7Xjuja-b^+qdN!-4;%lt7&2wS~jVuT0FCk+@Ohgf`7$$I-OB$HkP|-x$F=rKn97> zj4>d$Tff?Hxb$9av#zp?V~LR7Ten5?P!v}wt$ z2rN&664lf;h=x1}X#hg>4}w#Siu5Qrw0uTBlTqnS7Oo|e&0Jd5vV<-^a#5I%sLTk1 zIA7oLntP1fDcF>f6KDf#P1&{(K8IaM1EgLs6AN4zEevtyWm)w-YrTjpi& zB6KfupZbA&&jGOY0_1!L$J&rnvu29wIW5t(+>0T1;yG@b;2lV$)jTFMb2EA_ZxjNp zp`mB9!~`t&9FWzpQ05e7VKppZXj}B05I;{k1z%$bS}*=Wua<7qrsAdd*{=hC{2QXD z3(4=kW#!K%^DE$w1BwP=isb=x5S1oU8Uol-wnEfl>xY3~q@zH404eM)781V`o*dOTG)yP}oMjT8F)W!u-)8+B z^Gc5G(zINZz;(zb7}T4kd9U|=1SH%=W)lyfFQQ`417I98Sq^9vEEI+BA;I4lAAr^4 zmgU!i*|9>ums?VoUczGcLvYL06@{zV^Mn{9T~RoT4v0eHvAFI_hsM(9>z2DH7Nsv* zyV?+Fw|CGV=Dp|y>*ke3eb%0$^H6J{=G!F_{40(7AcT?F)mMw++*tz$Y?82Y%KEJ4S>0-E3rbu+>N z)RIM=^dv-RNgQsLuOrD)79zY`?v!e%rU{VzR88l}yOin_9w=fmA!V=TgqZKP5?k}qi2DU zZ;Y+0mB6WL;8Yz6@>{}_V5l6v^zaN2yH=(rP}aBZF0IgIluE+b#kFLak@G&yK}ZW zu~3(#LC7&Sk-a>HE;e%@%Q8{9Cx27+W>J=fEFUC$!^jXbrDIDp;MAR1OPF+)&FFq; zDZ68wutZ_}1f(-x1|$fgpX@T(ep_+k2%FbWcA0Fyt%xAk(Yj-dO$M@kl6O0Ey!+Yi z%BzGA%NqsWx5=&u)_0cM_FQE@E^HTzQNi9#_YMy{Zvs@|f7(TQ9%k`vz79-X6a=B} Scte0-`pGVn?LSAs*ZUu{ozArY literal 0 HcmV?d00001 diff --git a/src/config/llm_config.py b/src/config/llm_config.py new file mode 100644 index 0000000..f57c7dc --- /dev/null +++ b/src/config/llm_config.py @@ -0,0 +1,60 @@ +class LlmConfig: + class openai: + class models: + gpt_4o = "gpt-4o" + gpt_4_1 = "gpt-4.1" + + temperatures = { + "default": 0.7, + "drone_bot": 0.3, + "creative": 0.9 + } + + max_tokens = { + "default": 2048, + "summary_bot": 512, + "explainer": 1024 + } + + class anthropic: + class models: + claude_3_opus = "claude-3-opus" + claude_3_sonnet = "claude-3-sonnet" + + temperatures = { + "default": 0.5, + "drone_bot": 0.4, + "research": 0.2 + } + + max_tokens = { + "default": 4096, + "summary_bot": 1024 + } + + @classmethod + def get_config(cls, provider: str, model_name: str, temp_name: str = "default", token_preset: str = "default") -> dict: + if not hasattr(cls, provider): + raise ValueError(f"Provider '{provider}' not found.") + + provider_cls = getattr(cls, provider) + + # Get model value from class (e.g., LlmConfig.openai.models.gpt_4o) + model_cls = getattr(provider_cls, "models") + if not hasattr(model_cls, model_name): + raise ValueError(f"Model '{model_name}' not found under provider '{provider}'.") + + model = getattr(model_cls, model_name) + + if temp_name not in provider_cls.temperatures: + raise ValueError(f"Temperature preset '{temp_name}' not found under provider '{provider}'.") + + if token_preset not in provider_cls.max_tokens: + raise ValueError(f"Max token preset '{token_preset}' not found under provider '{provider}'.") + + return { + "provider": provider, + "model": model, + "temperature": provider_cls.temperatures[temp_name], + "max_tokens": provider_cls.max_tokens[token_preset] + } diff --git a/src/llm/__init__.py b/src/llm/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/llm/__pycache__/__init__.cpython-311.pyc b/src/llm/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..be7375e6ff8dc494f1fd760011f49156464aa928 GIT binary patch literal 177 zcmZ3^%ge<81e#in86f&Gh=2h`DC095kTIPhg&~+hlhJP_LlF~@{~09t%SAu5IJKx) zKQ}QsDL+43-zBv;yClCrKPe}*xHvN}Jw7p0KQ}u?Kcy%?FEu_XzeK;dC|N%zCs#i{ vJ~J<~BtBlRpz;@oO>TZlX-=wL5i8J8khR79K;i>4BO~Jn1{hJq3={(Zu=Ohe literal 0 HcmV?d00001 diff --git a/src/llm/__pycache__/orchestrator.cpython-311.pyc b/src/llm/__pycache__/orchestrator.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d4fcc8decbdd55c8f25802814ffe8bc6caf49bfd GIT binary patch literal 21728 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
  • (@(z*GN|n%I#QUF zC)-?1FAAInH3^QyL_qZ=)#o_cxbAKuQMsC-Q+eU1;KaB@3goOEW>P{d!j9vtKA(o4QK2rzs=wpCeI?$TL+T@Ik{nzev(%i0(*ry&zEagxpuFrt+&`6Dn0!|%o& zll~`CX+_8X16C@2NXBH!aOI2aFT?GSjV{9t$VQjp9Ele8 zRXs}84z+5>B9R{E@=deZR0X-~7VtU6{hGk%7UX-UQOoHJm`rqm_{Fc!wX|Owvy54+ zrVfZVwt%GxNWM)S5XI$_DuY%N&dTlK=Pdr$`qW7WXLT(l*DZ~*r4fUwYnA str: + """Extract the final message content from an AIMessage response""" + try: + # Case 1: Response has direct string content + if hasattr(response, 'content') and isinstance(response.content, str) and response.content.strip(): + return response.content.strip() + + # Case 2: Response has list content (complex format) + elif hasattr(response, 'content') and isinstance(response.content, list): + for item in response.content: + if isinstance(item, dict) and item.get('type') == 'text' and item.get('text'): + return item['text'].strip() + + # Case 3: Response only has tool calls, no text content + elif hasattr(response, 'tool_calls') and response.tool_calls: + return "Generating your visualization..." + + # Case 4: Empty or None content + else: + return "Processing your request..." + + except Exception as e: + print(f"Error extracting message content: {str(e)}") + return "I encountered an issue processing your request." + + + def create_workflow(self) -> StateGraph: + """Create the DroneBot workflow""" + print("Creating DroneBot workflow") + + # Create state graph + workflow = StateGraph(State) + + def drone_bot(state: State): + """Main chatbot that handles plotting requests""" + current_model_config = self.model_manager.get_current_model() + + # Add retry logic with model rotation + max_retries = 3 + last_error = None + + for attempt in range(max_retries): + try: + llm = self.model_manager.create_llm_instance(current_model_config) + + print(f"Using model: {current_model_config['name']} ({current_model_config['provider']}) - Attempt {attempt + 1}") + + # Bind tools to the LLM + llm_with_tools = llm.bind_tools(self.tools) + + # Get messages from state + messages = state["messages"] + + # Add system message if not present + if not messages or not isinstance(messages[0], SystemMessage): + system_prompt = prompt_manager.get_prompt("booking") + messages = [SystemMessage(content=system_prompt)] + messages + + print(f"DroneBot input messages: {len(messages)}") + + # Get response from LLM + response = llm_with_tools.invoke(messages) + + print(f"DroneBot response: {type(response).__name__}") + if hasattr(response, 'tool_calls'): + print(f" Tool calls: {len(response.tool_calls) if response.tool_calls else 0}") + + # Extract and store the final message content + final_message_content = self._extract_final_message_content(response) + self.final_message = final_message_content + + # Update state + updated_state = {"messages": state["messages"] + [response]} + updated_state["current_model"] = current_model_config["name"] + self.final_model_used = current_model_config["name"] + + return updated_state + + except Exception as e: + last_error = e + print(f"Attempt {attempt + 1} failed with model {current_model_config['name']}: {str(e)}") + + if attempt < max_retries - 1: + current_model_config = self.model_manager.rotate_on_failure(current_model_config["name"]) + time.sleep(1) + + # If all retries failed, raise the last error + raise last_error + + def route_tools(state: State): + """Route to tools if the last message has tool calls""" + messages = state.get("messages", []) + if not messages: + return END + + ai_message = messages[-1] + + if hasattr(ai_message, "tool_calls") and ai_message.tool_calls: + return "tools" + return END + + # Tool execution node + class ToolNode: + """A node that runs the plotting tools""" + + def __init__(self, tools: list, dronebot_instance) -> None: + self.tools_by_name = {tool.name: tool for tool in tools} + self.dronebot = dronebot_instance + + def __call__(self, inputs: dict): + messages = inputs.get("messages", []) + if not messages: + raise ValueError("No message found in input") + + message = messages[-1] + outputs = [] + + if not hasattr(message, "tool_calls") or not message.tool_calls: + print("No tool calls found in message") + return {"messages": messages} + + print(f"Processing {len(message.tool_calls)} tool calls") + + for tool_call in message.tool_calls: + tool_name = tool_call["name"] + tool_args = tool_call["args"] + tool_call_id = tool_call["id"] + + print(f"Executing tool: {tool_name}") + + try: + if tool_name in self.tools_by_name: + tool_result = self.tools_by_name[tool_name].invoke(tool_args) + else: + raise ValueError(f"Tool {tool_name} not found") + + print(f"Tool {tool_name} executed successfully") + + + + # Create tool message + tool_message = ToolMessage( + content=json.dumps(tool_result), + name=tool_name, + tool_call_id=tool_call_id, + ) + outputs.append(tool_message) + + except Exception as e: + print(f"Error executing tool {tool_name}: {str(e)}") + error_result = { + "status": "error", + "error": str(e), + "tool_name": tool_name + } + error_message = ToolMessage( + content=json.dumps(error_result), + name=tool_name, + tool_call_id=tool_call_id, + ) + outputs.append(error_message) + + result = {"messages": messages + outputs} + print(f"ToolNode returning {len(outputs)} tool messages") + return result + + tool_node = ToolNode(tools=self.tools, dronebot_instance=self) + + # Add nodes + workflow.add_node("chatbot", drone_bot) + workflow.add_node("tools", tool_node) + + # Add edges + workflow.add_edge(START, "chatbot") + workflow.add_conditional_edges( + "chatbot", + route_tools, + {"tools": "tools", END: END}, + ) + workflow.add_edge("tools", "chatbot") + + # Compile with memory + memory = MemorySaver() + app = workflow.compile(checkpointer=memory) + + print("DroneBot workflow created successfully") + return app + + def convert_to_langchain_messages(self, messages: List[Message]) -> List[HumanMessage | AIMessage]: + """Convert Message objects to LangChain message objects""" + langchain_messages = [] + for msg in messages: + if msg.role == "human" or msg.role == "user": + langchain_messages.append(HumanMessage(content=msg.content)) + elif msg.role == "ai" or msg.role == "assistant": + langchain_messages.append(AIMessage(content=msg.content)) + return langchain_messages + + def chat(self, user_query: str) -> Dict[str, Any]: + """Main method to interact with DroneBot""" + print(f"DroneBot processing query: {user_query}") + + conversation_id = f"dronebot_{int(time.time())}" + config = {"configurable": {"thread_id": conversation_id}} + app = self.create_workflow() + + # Prepare input messages + input_messages = [] + + # Add history if available + if self.history: + input_messages.extend(self.convert_to_langchain_messages(self.history)) + + # Add current query + input_messages.append(HumanMessage(content=user_query)) + + # Initialize state + initial_state = { + "messages": input_messages, + "final_response": None, + "user_question": user_query, + "current_model": self.model_manager.get_current_model()["name"] + } + + try: + output = app.invoke(initial_state, config) + print("DroneBot workflow completed successfully") + + # Ensure final_message is properly set + if not self.final_message: + messages = output.get("messages", []) + for message in reversed(messages): + if isinstance(message, AIMessage): + self.final_message = self._extract_final_message_content(message) + break + + if not self.final_message: + self.final_message = "I've processed your visualization request." + + final_response = { + "messages": output.get("messages", []), + "final_message": self.final_message, + "final_model_used": self.final_model_used or output.get("current_model", "unknown"), + "user_question": user_query + } + + print(f"Final message: {self.final_message[:100]}...") + return final_response + + except Exception as e: + print(f"Error in DroneBot workflow execution: {str(e)}") + return { + "messages": [], + "final_message": "Sorry, I encountered an error while processing your visualization request. Please try again.", + "final_model_used": "error", + "user_question": user_query, + "error": str(e) + } + + +# Example usage +if __name__ == "__main__": + # Example conversation history + history_list = [ + {"role": "human", "content": "Hello DroneBot!"}, + {"role": "ai", "content": "Hello! How can I help you today?"} + ] + + history = [Message(role=msg["role"], content=msg["content"]) for msg in history_list] + + + # Initialize DroneBot + bot = DroneBot(history=history, use_openai_as_fallback=True) + + query = "Can we start" + # Chat with DroneBot + response = bot.chat(query) + + print("Response:", response["final_message"]) + \ No newline at end of file diff --git a/src/llm/tools/__init__.py b/src/llm/tools/__init__.py new file mode 100644 index 0000000..d2124b6 --- /dev/null +++ b/src/llm/tools/__init__.py @@ -0,0 +1,5 @@ +from .booking_calendar import get_booking_calendar + + +class AgenTools: + tools = [get_booking_calendar] \ No newline at end of file diff --git a/src/llm/tools/__pycache__/__init__.cpython-311.pyc b/src/llm/tools/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e9e9c5e34f315985624fb36337d4451226058dac GIT binary patch literal 509 zcmZuuy-ve05WY(qR7!&wDt2Isgs{YnkU$^=LlEyhGmrgm{^b zRaPdpqB3>DPAC#TXZyQ5-}&?T{N(#BFybHI_xnb_T4yPZFJr!Ij4?1^C?LZS5ga%J z<~#s%A02=fvuX()*0_Ygdqd*~SauR#(4LfoG*4(837)e^#mj7~ZK9QV$G{jkgvcOt zxSL?uU}(ska`hGZ$();-3!+s_gvdxmLnux2w4iivc&j`^ zSb=Sk-ZXIcJ*NKFq`iH2YKK}|CUyh2-1yyys?3_q{z9GIHrAqfP(SQYtsB?&NS_;+ bxrjK0{mIWcTS45J!C!c{dmsMM)XoyzVfVD2G`C=O5?*D+SU7HDm~uBW~pWZIc}1|z1^ z48u1yYTV5{gsMj`PXMeN)LcfT?B8mqgyYNw-;>J9CqC&*8#nwurN$>v>FwzFnmjdWv4>0u!q;>Ns$HA%N_L>pSgL>QD|Z&{&D2Be>@TLEBMZv~c!j(iom! z?%ausO(r2V9o8toUx$Y3lM*`sU!laFHtfViM+m^NcONefiswMLXG@Py4~k2m2izo^ znR_L%(d^s~ng6M z{XaYhNenn0tat<7@SC$Aq2KJ9nV^iMg0KHuO?r|}2!=~4A z{jlLhoRw2*c?Lr1q*LmCLA$UVI>vY#9T|-4HhOz7 kuG?sKFs?8J%}lfzY~QB|0<#bQ=n%SFOoRKo{1`O+4-|%HPyhe` literal 0 HcmV?d00001 diff --git a/src/llm/tools/booking_calendar.py b/src/llm/tools/booking_calendar.py new file mode 100644 index 0000000..8ca9ef2 --- /dev/null +++ b/src/llm/tools/booking_calendar.py @@ -0,0 +1,32 @@ +from typing import Dict +from langchain_core.tools import tool +from datetime import datetime + + +@tool +def get_booking_calendar() -> Dict: + """ + Retrieve the current booking calendar data. This could be integrated with a real calendar API. + """ + try: + # Simulated booking calendar data + calendar_data = { + "status": "success", + "data": { + "available_slots": [ + {"date": "2025-07-24", "time": "10:00 AM"}, + {"date": "2025-07-24", "time": "2:00 PM"}, + {"date": "2025-07-25", "time": "9:30 AM"} + ], + "timezone": "UTC+1", + "generated_at": datetime.utcnow().isoformat() + "Z" + } + } + return calendar_data + + except Exception as e: + print(f"General Exception: {e}") + return { + "status": "error", + "error": str(e), + } diff --git a/src/prompts/__init__.py b/src/prompts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/prompts/__pycache__/__init__.cpython-311.pyc b/src/prompts/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ddfcd7e70b0e7b4b6081470d51660624de0981e8 GIT binary patch literal 181 zcmZ3^%ge<81e#in86f&Gh=2h`DC095kTIPhg&~+hlhJP_LlF~@{~09t%R@i3IJKx) zKQ}QsDL+43-zBv;yClCrKPe}*xHvN}Jw7p0KQ}u?Kcy%?FEu_XzeK;dC|SRtC_lHL zq*y;bJ~J<~BtBlRpz;@oO>TZlX-=wL5i8JOkk!TfK;i>4BO~Jn1{hJq3={(ZmH8~y literal 0 HcmV?d00001 diff --git a/src/prompts/__pycache__/manager.cpython-311.pyc b/src/prompts/__pycache__/manager.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..72fc8e459592c74e42d8c30b9e2ac232f8011639 GIT binary patch literal 1702 zcmbVM&1)M+6rb5$$%<@igRIm|smT)Can}vj#p$6o#n6`IQW0>QgD#u(&RShd+Er&( zDGoBY#Rs2?4+RHG!H43;_}V`rN2v%B77T`-dQ(si#izbED=C(pK*-yf-@ci*^X7f- zpX1|c0`%X?Pv8Hg5b_T$`lR)k?kB+P6Gj-VkRnmYUBZ+d!qh`JaSSu2B4u=ynA*R% zEI7Sg`J%l=_ZBew#3k@8DJqQakfMsZiW*Y^6HEh4vIOW!M&tK+P_2dE*uGtIxzksI zy6{pJW;X}0Pdw5f7H}~!vPzC<;fO|8Yf^)?UiY2Q3w&YHNE2>lU7#}K>~8)$;c}6$ z+Rj=Kl=EM?q8tXb{945o!t+a(?d7XwmS;ThU281}^MX71SSg;8hAq@Kqm*TNz86~7 z;}3?6Ug+;9;68B`V4J)!ZtiOB)KpWBF52v%FMUE@`t}hp`=p^X$VOjA8gvM*2mcQI}yD91|Vs+%0(`bh#CO_IPU8-V5RsoDtUm!0cOhq}fkc zD>dDegO7p485964(9Kbp-G=~RJ3VjvBg!YjbDlEVpua&W{jMHDI*teV{h2C|&+|TC z2W>Gi6>_coL4T2@Rya$=<{NWoV{?WdgvNSM_u0Y`F%^!+N9hN4rS5*sdBA&d;u+je zmVdN)Nr<87q)Tqt56p+dfq5-K|HM52h{>qF7q%yFH%D=tWIt|Beg0Gfh({~C)Xpw7 z$Ch5`^SfbNpE>xpt7aa#vmmPWjGOA zYKVo^qlUUMICX%JDBGbdMwxSWw7d6)jqbtHI`F2?6 zZlqP9Orvzo=1|m7uOiaQ?xrv`S?h5QHSFt1g+l`WRZ!&pDu10V3$uK&$}fnqFNFi} z?ioGRC;Iu^)lM?k+L;>%S}QZ#&dlzrC;G(Rm%kRC=jQ&-&9!o7J7>1^c}#t3UM9O> zj^Hl?`_;0dv}IKTR<9sGW?4Vf?Mf`+SZW-tJ0*U`TtH@g8Tpg literal 0 HcmV?d00001 diff --git a/src/prompts/__pycache__/setup_prompt.cpython-311.pyc b/src/prompts/__pycache__/setup_prompt.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..872407e66a9663aa6e7fd949da84e7ca83b875e0 GIT binary patch literal 626 zcmZWnze@u#6n?o&t-q=w(p3>bx-^U3M6kLDDuM{orJTJ`OE0}6Nr!?$1sB1=t*etF z{vXa&6w84iIJp(-$PK*{Jenh6W=HxW&|6N!sdD*lfUr7U<+^;}`}JC* zS`MNU71K(LVqoJp*hkj^ZNOr?!u>W$;24hJ@>ln#fg@^z65AC@xJ_gL!CN?_wH>D8 zA#%1feBq?z)tb^=##6}@R&ih}5X#WZeQvTn#ie4xshGQLovXU`19sqXrHmY>#zfs^ zuJjumHEO10g?(#OOmW=|V(fg-3TJ*7%HsQ#Oq%vZ? x0XdF1()zE literal 0 HcmV?d00001 diff --git a/src/prompts/templates/__pycache__/chat_templates.cpython-311.pyc b/src/prompts/templates/__pycache__/chat_templates.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b8ed67a975ef94580c4e3167de2339a117e34905 GIT binary patch literal 8516 zcma)C-EJGl6_(@l2Sn0~zCgwa5ZQuA+RjfCqNqX3l0`&+Fqx{ZU>L*QA-UmlhuPT` z&0MuW-=U9C^p$$s7ciQ;T=o0T%yO5Og4VEQa(DL3ne+3V@0`^?KK!t?gFoYgzs^2V zJ3Ig8m+H@*@ZrY~@Zs+}@y_3N{=5_48SXrIFRIq?f3CZWe?R^bKd??#oaz63w6oJ{ zee#JKu3cdkYLK}iFQX!{nNy#t3zIvW=~nBdEtSrVdY0SFJhjEX(rTI8iE$1~b*gk0 zt9fQusfnjXk5i*!E>*6~ugzMGt(_;?RB5bth0Y4KvbkKD*c`G_qhsFBO=ec|I6mal zwMsI#G@_4+>|&|2bw^Q|nZ7p4yeUlP68W?=d1A82;0aaC%uPuSVz?I+EwdO0gBr0c zjq;>OA{-)FENza8xLe4*VrFw&PG{aEg`OKM zPhzpb%%n?NbfTj~1TS=^r`Tv;xun1Wv8jeZg1QTxWH6?jy)b&=l#6C2F5$>oGM%-* zFTHx+tF>1gET{`LH)cs&Cbt|}?B~LU5(GGv(Q_UfmV{oB@h8~w3#7(&l@-at!1bZ z$_+&2W&zXU0wNu^p&vx+IL;B~CidQvo3SZ$lC~`!RhU>Lc(LsWnG+?pq+QdMUb}j8 zv6L-y6`8z9CJ7$&<}}$9fdk+8utt1HTqc>AhoQpnYlSrkGYfNMF_KhHA~C@XK^8RJ zA_m>XIdlqp;Q-h-GLte@ZP>(QDw<&%Y;1Vk#F>3D>V$!e&@Md!Fpzaxm^?$kUSlaX zUlX4_VA4YLWI#>D)jaN#Y75VEPCa z-^XpJ2q^>(*d#N&OyOIWv3024jLub3%)GW6n{TY`gKmON>dXyBDd0STAw@w(0b z83){zXOJi|DuV+m#PEUOBW96=C;^-fGC&Ft<#`TkQU6bIsmLwe26wjh_5h;2y*>5u zvBCED)L?f(2ju?)Hal1dOE9^`Uz;S=;lVlt07^VMFc?_29d!*0jAro9Dq+MCKE5km z0aHxON-dJCEa39C-%`7|-_ar;zb}mo5i+(Vz=-k%Rv0nOT(=9rx@4S>JYXx;`mCeQZ^vudpw${o{NY6&l5kg+@9wK* z*hpe$eVcL#YKQCK%zJxB5^C*osgj9gjfo`&4s4mmvH_y)nu)ToNZn0viH)IOI4%$W zM2Y7Z2o7l_g>@P#AMhI3K#hRe@Mq<2DzRnw$2~PM1e2%vKt+7&N4D2X zt{}%Vx-d1@q<^AjC9{fjaVJs@r2}b|i$CQlk{m_IS-=HCkx(yzOcG5#2evFkChBvM zWqrx2P5>W!4uI63;eKj?%M6FWQ5TXoguxq$@;S7Rqzt@h@`Qt#iRKM&HSz?zHQ0=Y zMlWFt9f2&)J4u#IaXe7R96Z3WXcu`B3BQm>pH%jFc{rqPS|3X>5LTeQ@BkU~>vNil z`J?V5DD`rg0#Xry4{+}rkcpX1!Gf_OD;TfgN!+BxH?=UxWe8P$0hT;v;KQgW#Y+p~ zNR?yICn;{OQuFNGYZ*`kClzZGHh|6vxs_<0uw59h)wP)=aJ=9I-5JPHxfvm zelcUM&3qaWAV!`JTb$^=U9wyvncB*9rk%jS10~;A+w411Cu*--y7U!+wm4m zL@JKf2iZ{Oj;S#;5sZiaKp;r^AtsoiaLEjY+f4tUb5IV1eRHoW@H0e$fY->^L$GS1 zRWtTbv3+;oF-e+Hh^(5E4Q$6b6049IIVaF61nNkaDPSbg)6AmD?j6}pZwqjd|6Sn``eW3og^VQA%tdu`(bYZp|;xF zJB3X`k1O8U20Bn3ICMflL{Mb(E!z{gk}V*q2*U7$fnyPlbQY8wXp z7$g)Iz+M0pS_RPoHYH{ji%TKgja8bU5ozWMjsJh4#0qTHPhB=K<>0FeA&xnFXQBgq z45Vi|i_0NgU)x0TyFO_QXEV0NW;OJDn@@#HKu*d;X?piY_MchuSQNJfl7D5`xZ!)m zCSH28%0m?ak(vebP6tTFhP6)Im|K3B4I2nCStEztz)UKR16LePKRd-zcGPx0x|V}86imDcIf#F&$AFRkvk)favx zN0c%a5y54BS4Py`@BwDe=1MwRQZ$eC-C@5AUq1c9yaKy!fJYh9giB-gm6L_EUt zQVKYo?`td`Wo;FTbfdVV-q3DR9sHmd@ffDTv2u zChMQ`Url80DUXES+zrklq)jwp+&&3mS1w5-{QQ-DQ^W0SCPL1KAog?E06xu3_sxS( zrKSY73r%W=bJy-48EK<|0t2$=ZC7L14hM;y@G64ihcJnNQ!U&%_6U75RsP-Zvf@_BOzfz zV+gW+-@4ifX=WpoLF*<8g%1H90**Zaa*A;*f}rN6FjA6-c#JIZ260`@ponlrBhME4 zI~<3vVdUY^_aCtjYp7usON`4X!@q*tBIQD_02}C;NPaLk^)!xFq0KP*qNo67zUc?u zhb6f!z!qMuoY#y!J}=EcwBF#R7X+iDU4k}&?pCdih(cGV(T5?kN!i0-p>=jXI_#;@ z@nER%cX)nsc=A%cJpcBE8uf?YsEd=s{_t>Lot>-m(J?L_56+&q6#n$ovvXV?siXeW z7lVWT=uq|l*dLtqpPn45;mP@ExUc#rm;IMRb#Q!m@QpeeycmvJ|J=#%@^=C7Z|A2! zbqNKoyU-D9WA~YH^TIB>h&6%M*E;Dg=5bes2d~Gr=(;@W!lWz-UHsC(k_U2KRIK9l z=4)rU{?~8uN)`{uoB!ATftDRws;N@9r{*eC)Uq!+v`0)1bojZ5lzNhcrfsosOije;T(K$hE literal 0 HcmV?d00001 diff --git a/src/prompts/templates/chat_templates.py b/src/prompts/templates/chat_templates.py new file mode 100644 index 0000000..4d5d7c5 --- /dev/null +++ b/src/prompts/templates/chat_templates.py @@ -0,0 +1,226 @@ +def get_booking_prompt(): + + return """ + +## System Instructions & Persona + +You are DroneBot, a professional and knowledgeable drone survey booking assistant working for a leading renewable energy inspection company. You have extensive experience in the renewable energy sector and understand the critical importance of regular asset inspections for solar farms, wind turbines, and other renewable energy installations. + +Your personality is friendly yet professional, efficient yet thorough. You take pride in helping facility managers, site operators, and maintenance teams schedule high-quality drone inspections that keep their renewable energy assets operating at peak performance. You understand that downtime costs money, so you work quickly to get surveys scheduled while ensuring all safety protocols and requirements are properly addressed. + +You are detail-oriented and safety-conscious, always ensuring that our certified drone engineers have all the information they need to conduct safe, effective inspections. You're also resourceful - when challenges arise, you find solutions and alternatives to meet our clients' needs. + +Your primary function is to guide users through a comprehensive booking process, collecting all necessary information to schedule drone inspections with our certified engineers. You follow a specific step-by-step process to ensure no critical details are missed. + +## Initial Greeting & Introduction +**Bot:** "Hello! I'm your drone survey booking assistant. I'll help you schedule a drone inspection with one of our certified engineers. This will take just a few minutes - I'll ask you some questions about your site and requirements. Let's get started!" + +--- + +## Step 1: Asset Type Identification +**Bot:** "First, what type of asset needs surveying? + +Please select: +1. Solar Farm +2. Wind Turbine +3. Other renewable energy asset + +If you selected 'Other', please specify what type of asset it is." + +**Follow-up if needed:** "Could you provide more details about the asset type? This helps us assign the right specialist engineer." + +--- + +## Step 2: Site Identification +**Bot:** "Great! Now I need some basic site information. + +What's the name or identifier for this site? (This could be a site name, project code, or any reference you use)" + +**Follow-up:** "Perfect. Could you also provide the location? Please share: +- Full address, OR +- GPS coordinates, OR +- Nearest landmark/town if exact address isn't available" + +--- + +## Step 3: System Size/Scope +**Bot:** "To help our engineer prepare properly, what's the size/scope of your installation? + +For example: +- Solar: Number of panels or total kW/MW capacity +- Wind: Number of turbines or total MW capacity +- Other: Any relevant size/capacity details" + +**If unclear:** "Any rough estimate is fine - this helps our engineer know what equipment to bring and how long the survey might take." + +--- + +## Step 4: Access Requirements Check +**Bot:** "Now I need to understand site access requirements. + +Is your site gated or has restricted access?" + +**If YES:** +"I'll need access details: +- Gate code (if applicable) +- Key holder contact details +- Any specific access instructions +- Best entry point or directions" + +**If NO:** +"Great! Is the site easily accessible by vehicle for our drone equipment?" + +--- + +## Step 5: On-Site Contact Information +**Bot:** "Who should our engineer contact on the day of the survey? + +Please provide: +- Contact person's name +- Phone number +- Their role (optional - e.g., site manager, maintenance technician)" + +**Follow-up:** "Will this person be available on-site during the survey, or should our engineer call ahead?" + +--- + +## Step 6: Special Access/Safety Requirements +**Bot:** "Are there any special requirements our engineer should know about? + +For example: +- Specific PPE needed +- Safety induction required +- Time restrictions (e.g., only accessible during certain hours) +- Security clearance needed +- Any site hazards or restrictions" + +**If none:** "That's fine - our engineers always bring standard safety equipment." + +--- + +## Step 7: Survey Purpose +**Bot:** "What's the main purpose of this drone survey? + +Please select: +1. Routine maintenance inspection +2. Insurance assessment +3. Fault diagnosis/investigation +4. Compliance/regulatory requirement +5. Performance optimization +6. Other (please specify)" + +**Follow-up based on selection:** +- **Maintenance:** "Any specific areas of concern or components to focus on?" +- **Insurance:** "Do you have any specific requirements from your insurer?" +- **Fault:** "Can you describe the issue you're experiencing?" +- **Compliance:** "Which standards or regulations need to be met?" + +--- + +## Step 8: Special Notes/Additional Requirements +**Bot:** "Almost done! Are there any other details, special requests, or concerns you'd like our engineer to know about? + +For example: +- Specific weather requirements +- Preferred time of day +- Areas to avoid +- Additional documentation needed +- Urgent timeline requirements" + +**If none:** "No problem - we'll proceed with standard survey procedures." + +--- + +## Step 9: Preferred Timing +**Bot:** "When would you prefer the survey to take place? + +Please let me know: +- Any specific dates you prefer +- Days of the week that work best +- Time of day preferences +- How urgent this is (ASAP, within 1 week, within 1 month, flexible) + +Or simply tell me if you're flexible and want to see all available options." + +--- + +## Step 10: Calendar Check & Booking Confirmation +**Bot:** "Perfect! Let me check our engineer availability based on your location and requirements... + +[SYSTEM: Check available calendar slots based on location, engineer expertise, and user preferences using the approriate tools] + +Here are the available time slots: + +**Next Week:** +- Monday, [Date] - 9:00 AM - 12:00 PM +- Wednesday, [Date] - 2:00 PM - 5:00 PM +- Friday, [Date] - 10:00 AM - 1:00 PM + +**Following Week:** +- Tuesday, [Date] - 8:00 AM - 11:00 AM +- Thursday, [Date] - 1:00 PM - 4:00 PM + +Which slot works best for you?" + +--- + +## Final Confirmation +**Bot:** "Excellent! Let me confirm your booking: + +**Survey Details:** +- Asset: [Asset Type] at [Site Name] +- Location: [Address/GPS] +- Date & Time: [Selected Slot] +- Purpose: [Survey Purpose] +- Engineer: [Engineer Name] - [Phone Number] + +**Your Contact:** [Contact Person] - [Phone Number] + +**Access Details:** [Summary of access requirements] + +**Special Notes:** [Any special requirements] + +This booking is now confirmed! You'll receive a confirmation email shortly. + +Our engineer will call your on-site contact 30 minutes before arrival. If you need to make any changes, please contact us at [contact details]. + +Is there anything else I can help you with today?" + +--- + +## Error Handling & Clarification Prompts + +### If User Provides Incomplete Information: +**Bot:** "I need a bit more information to complete your booking. Could you please provide [specific missing detail]?" + +### If No Available Slots Match Preferences: +**Bot:** "I don't see any available slots that match your exact preferences. However, I have these alternatives: +[List alternative slots] + +Would any of these work, or would you prefer me to check with our scheduling team for other options?" + +### If User Wants to Modify Information: +**Bot:** "No problem! Which detail would you like to change? I can update: +1. Site information +2. Access details +3. Survey requirements +4. Contact information +5. Preferred timing" + +### If Technical Issues: +**Bot:** "I'm having trouble accessing our calendar system right now. Let me take your details and have our scheduling team contact you within 2 hours to confirm your appointment. Is that acceptable?" + +--- + +## Additional Context for Bot Behavior + +- **Tone:** Professional but friendly, efficient but not rushed +- **Flexibility:** Always offer alternatives if first options don't work +- **Confirmation:** Summarize key details at each major step +- **Safety Focus:** Always emphasize safety requirements and proper preparation +- **Contact:** Provide clear next steps and contact information +- **Urgency Handling:** Prioritize urgent requests and escalate when needed + +NOTE: THIS IS SOLELY YOUR TASK PLEASE, NO OTHR THING + : DO NOT FABRICATE AVAILABLE SLOTS, ALWAYS CHECK FIRST +""" diff --git a/src/prompts/validation/__init__.py b/src/prompts/validation/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/prompts/validation/prompt_validator.py b/src/prompts/validation/prompt_validator.py new file mode 100644 index 0000000..e69de29 diff --git a/template.py b/template.py new file mode 100644 index 0000000..3d8d126 --- /dev/null +++ b/template.py @@ -0,0 +1,44 @@ +import os + +project_structure = { + "README.md": "", + "requirements.txt": "", + "pyproject.toml": "", + ".env.example": "", + ".gitignore": "", + "Dockerfile": "", + "src/__init__.py": "", + "src/config/__init__.py": "", + "src/config/llm_config.py": "", + "src/prompts/__init__.py": "", + "src/prompts/manager.py": "", + "src/prompts/templates/__init__.py": "", + "src/prompts/templates/chat_templates.py": "", + "src/prompts/validation/__init__.py": "", + "src/prompts/validation/prompt_validator.py": "", + "src/llm/__init__.py": "", + "src/llm/clients/__init__.py": "", + "src/llm/clients/openai_client.py": "", + "src/llm/orchestrator.py": "", + "src/chains/__init__.py": "", + "src/chains/base_chain.py": "", + "src/agents/__init__.py": "", + "src/agents/base_agent.py": "", + "src/evaluation/__init__.py": "", + "src/evaluation/prompt_evaluator.py": "", + "scripts/evaluate_prompts.py": "", + "tests/__init__.py": "", + "tests/unit/test_prompt_manager.py": "", +} + +def create_structure(base_path="."): + for path, content in project_structure.items(): + full_path = os.path.join(base_path, path) + dir_path = os.path.dirname(full_path) + os.makedirs(dir_path, exist_ok=True) + with open(full_path, "w") as f: + f.write(content) + print("✅ Project structure generated successfully.") + +if __name__ == "__main__": + create_structure() diff --git a/test.py b/test.py new file mode 100644 index 0000000..49f5e73 --- /dev/null +++ b/test.py @@ -0,0 +1,38 @@ +# terminal_chat.py +from src.llm.orchestrator import DroneBot, Message # Adjust import path as needed + +def terminal_chat(): + print("🚁 DroneBot Terminal Chat") + print("Type 'exit' to quit.\n") + + # Initial conversation history + history = [] + + # Initialize the bot + bot = DroneBot(history=[], use_openai_as_fallback=True) + + # Get initial user input + while True: + user_input = input("👤 You: ") + if user_input.lower() in ("exit", "quit"): + print("👋 Exiting DroneBot. Goodbye!") + break + + # Add user message to history + history.append(Message(role="human", content=user_input)) + + # Update bot's history + bot.history = history + + # Get bot response + response = bot.chat(user_input) + + # Add bot response to history + history.append(Message(role="ai", content=response["final_message"])) + + # Print bot response + print(f"🤖 DroneBot: {response['final_message']}\n") + + +if __name__ == "__main__": + terminal_chat() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/integration/bot_chat.py b/tests/integration/bot_chat.py new file mode 100644 index 0000000..49f5e73 --- /dev/null +++ b/tests/integration/bot_chat.py @@ -0,0 +1,38 @@ +# terminal_chat.py +from src.llm.orchestrator import DroneBot, Message # Adjust import path as needed + +def terminal_chat(): + print("🚁 DroneBot Terminal Chat") + print("Type 'exit' to quit.\n") + + # Initial conversation history + history = [] + + # Initialize the bot + bot = DroneBot(history=[], use_openai_as_fallback=True) + + # Get initial user input + while True: + user_input = input("👤 You: ") + if user_input.lower() in ("exit", "quit"): + print("👋 Exiting DroneBot. Goodbye!") + break + + # Add user message to history + history.append(Message(role="human", content=user_input)) + + # Update bot's history + bot.history = history + + # Get bot response + response = bot.chat(user_input) + + # Add bot response to history + history.append(Message(role="ai", content=response["final_message"])) + + # Print bot response + print(f"🤖 DroneBot: {response['final_message']}\n") + + +if __name__ == "__main__": + terminal_chat() diff --git a/tests/unit/test_prompt_manager.py b/tests/unit/test_prompt_manager.py new file mode 100644 index 0000000..e69de29