Spaces:
Sleeping
Sleeping
JerameeUC commited on
Commit ·
9e1932d
1
Parent(s): cb71edd
4th commit Trimmed More
Browse files- app.zip +0 -0
- app/app/app.py +65 -0
- app/app/routes.py +72 -0
- app/mbf_bot/app.py +0 -138
- full/.env.example +0 -9
- full/README.md +0 -13
- full/agenticcore/__init__.py +0 -0
- full/agenticcore/chatbot/__init__.py +0 -0
- full/agenticcore/chatbot/services.py +0 -102
- full/agenticcore/providers_unified.py +0 -273
- full/backend/__init__.py +0 -0
- full/backend/app/__init__.py +0 -0
- full/backend/app/main.py +0 -69
- full/backend/app/routers/__init__.py +0 -0
- full/backend/app/routers/chatbot.py +0 -27
- full/extras/__init__.py +0 -0
- full/frontend/__init__.py +0 -0
- full/frontend/agenticcore_frontend.html +0 -200
- full/frontend/chat_console.html +0 -77
- full/frontend/chat_minimal.html +0 -89
- full/requirements.txt +0 -14
- full/tests/__init__.py +0 -0
- full/tests/test_routes.py +0 -6
- full/tools/__init__.py +0 -0
- full/tools/quick_sanity.py +0 -13
- full/tools/smoke_test.py +0 -11
app.zip
ADDED
|
Binary file (14.1 kB). View file
|
|
|
app/app/app.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# app/app.py — aiohttp + Bot Framework bootstrap
|
| 3 |
+
|
| 4 |
+
import os, sys
|
| 5 |
+
from aiohttp import web
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
from botbuilder.core import BotFrameworkAdapter, BotFrameworkAdapterSettings, TurnContext
|
| 8 |
+
|
| 9 |
+
from app.routes import init_routes
|
| 10 |
+
from bot import SimpleBot
|
| 11 |
+
|
| 12 |
+
# Credentials
|
| 13 |
+
APP_ID = os.environ.get("MicrosoftAppId") or None
|
| 14 |
+
APP_PASSWORD = os.environ.get("MicrosoftAppPassword") or None
|
| 15 |
+
|
| 16 |
+
adapter_settings = BotFrameworkAdapterSettings(APP_ID, APP_PASSWORD)
|
| 17 |
+
adapter = BotFrameworkAdapter(adapter_settings)
|
| 18 |
+
|
| 19 |
+
async def on_error(context: TurnContext, error: Exception):
|
| 20 |
+
print(f"[on_turn_error] {error}", file=sys.stderr, flush=True)
|
| 21 |
+
try:
|
| 22 |
+
await context.send_activity("Oops. Something went wrong!")
|
| 23 |
+
except Exception as send_err:
|
| 24 |
+
print(f"[on_turn_error][send_activity_failed] {send_err}", file=sys.stderr, flush=True)
|
| 25 |
+
|
| 26 |
+
adapter.on_turn_error = on_error
|
| 27 |
+
|
| 28 |
+
# Bot instance
|
| 29 |
+
bot = SimpleBot()
|
| 30 |
+
|
| 31 |
+
def create_app() -> web.Application:
|
| 32 |
+
app = web.Application()
|
| 33 |
+
init_routes(app, adapter, bot)
|
| 34 |
+
|
| 35 |
+
# Optional CORS
|
| 36 |
+
try:
|
| 37 |
+
import aiohttp_cors
|
| 38 |
+
cors = aiohttp_cors.setup(app, defaults={
|
| 39 |
+
"*": aiohttp_cors.ResourceOptions(
|
| 40 |
+
allow_credentials=True,
|
| 41 |
+
expose_headers="*",
|
| 42 |
+
allow_headers="*",
|
| 43 |
+
allow_methods=["GET","POST","OPTIONS"],
|
| 44 |
+
)
|
| 45 |
+
})
|
| 46 |
+
for route in list(app.router.routes()):
|
| 47 |
+
cors.add(route)
|
| 48 |
+
except Exception:
|
| 49 |
+
pass
|
| 50 |
+
|
| 51 |
+
# Static folder if present
|
| 52 |
+
static_dir = Path(__file__).parent / "static"
|
| 53 |
+
if static_dir.exists():
|
| 54 |
+
app.router.add_static("/static/", path=static_dir, show_index=True)
|
| 55 |
+
else:
|
| 56 |
+
print(f"[warn] static directory not found: {static_dir}", flush=True)
|
| 57 |
+
|
| 58 |
+
return app
|
| 59 |
+
|
| 60 |
+
app = create_app()
|
| 61 |
+
|
| 62 |
+
if __name__ == "__main__":
|
| 63 |
+
host = os.environ.get("HOST", "127.0.0.1") # use 0.0.0.0 in containers
|
| 64 |
+
port = int(os.environ.get("PORT", 3978))
|
| 65 |
+
web.run_app(app, host=host, port=port)
|
app/app/routes.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# app/routes.py — HTTP handlers
|
| 2 |
+
import json
|
| 3 |
+
from aiohttp import web
|
| 4 |
+
from botbuilder.core import TurnContext
|
| 5 |
+
from botbuilder.schema import Activity
|
| 6 |
+
|
| 7 |
+
# Prefer project logic if available
|
| 8 |
+
try:
|
| 9 |
+
from logic import handle_text as _handle_text
|
| 10 |
+
except Exception:
|
| 11 |
+
from skills import normalize, reverse_text, is_empty
|
| 12 |
+
def _handle_text(user_text: str) -> str:
|
| 13 |
+
text = (user_text or "").strip()
|
| 14 |
+
if not text:
|
| 15 |
+
return "Please provide text."
|
| 16 |
+
cmd = normalize(text)
|
| 17 |
+
if cmd in {"help", "capabilities"}:
|
| 18 |
+
return "Try: reverse <text> | or just say anything"
|
| 19 |
+
if cmd.startswith("reverse "):
|
| 20 |
+
original = text.split(" ", 1)[1] if " " in text else ""
|
| 21 |
+
return reverse_text(original)
|
| 22 |
+
return f"You said: {text}"
|
| 23 |
+
|
| 24 |
+
def init_routes(app: web.Application, adapter, bot) -> None:
|
| 25 |
+
async def messages(req: web.Request) -> web.Response:
|
| 26 |
+
ctype = (req.headers.get("Content-Type") or "").lower()
|
| 27 |
+
if "application/json" not in ctype:
|
| 28 |
+
return web.Response(status=415, text="Unsupported Media Type: expected application/json")
|
| 29 |
+
try:
|
| 30 |
+
body = await req.json()
|
| 31 |
+
except json.JSONDecodeError:
|
| 32 |
+
return web.Response(status=400, text="Invalid JSON body")
|
| 33 |
+
|
| 34 |
+
activity = Activity().deserialize(body)
|
| 35 |
+
auth_header = req.headers.get("Authorization")
|
| 36 |
+
|
| 37 |
+
invoke_response = await adapter.process_activity(activity, auth_header, bot.on_turn)
|
| 38 |
+
if invoke_response:
|
| 39 |
+
return web.json_response(data=invoke_response.body, status=invoke_response.status)
|
| 40 |
+
return web.Response(status=202, text="Accepted")
|
| 41 |
+
|
| 42 |
+
async def messages_get(_req: web.Request) -> web.Response:
|
| 43 |
+
return web.Response(
|
| 44 |
+
text="This endpoint only accepts POST (Bot Framework activities).",
|
| 45 |
+
content_type="text/plain",
|
| 46 |
+
status=405
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
async def home(_req: web.Request) -> web.Response:
|
| 50 |
+
return web.Response(
|
| 51 |
+
text="Bot is running. POST Bot Framework activities to /api/messages.",
|
| 52 |
+
content_type="text/plain"
|
| 53 |
+
)
|
| 54 |
+
|
| 55 |
+
async def healthz(_req: web.Request) -> web.Response:
|
| 56 |
+
return web.json_response({"status": "ok"})
|
| 57 |
+
|
| 58 |
+
async def plain_chat(req: web.Request) -> web.Response:
|
| 59 |
+
try:
|
| 60 |
+
payload = await req.json()
|
| 61 |
+
except Exception:
|
| 62 |
+
return web.json_response({"error": "Invalid JSON"}, status=400)
|
| 63 |
+
user_text = payload.get("text", "")
|
| 64 |
+
reply = _handle_text(user_text)
|
| 65 |
+
return web.json_response({"reply": reply})
|
| 66 |
+
|
| 67 |
+
# Wire routes
|
| 68 |
+
app.router.add_get("/", home)
|
| 69 |
+
app.router.add_get("/healthz", healthz)
|
| 70 |
+
app.router.add_get("/api/messages", messages_get)
|
| 71 |
+
app.router.add_post("/api/messages", messages)
|
| 72 |
+
app.router.add_post("/plain-chat", plain_chat)
|
app/mbf_bot/app.py
DELETED
|
@@ -1,138 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
# app.py — aiohttp + Bot Framework Echo bot
|
| 3 |
-
|
| 4 |
-
import os
|
| 5 |
-
import sys
|
| 6 |
-
import json
|
| 7 |
-
from logic import handle_text
|
| 8 |
-
from aiohttp import web
|
| 9 |
-
from botbuilder.core import BotFrameworkAdapter, BotFrameworkAdapterSettings, TurnContext
|
| 10 |
-
from botbuilder.schema import Activity
|
| 11 |
-
import aiohttp_cors
|
| 12 |
-
from pathlib import Path
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
# -------------------------------------------------------------------
|
| 16 |
-
# Your bot implementation
|
| 17 |
-
# -------------------------------------------------------------------
|
| 18 |
-
# Make sure this exists at packages/bots/echo_bot.py
|
| 19 |
-
# from bots.echo_bot import EchoBot
|
| 20 |
-
# Minimal inline fallback if you want to test quickly:
|
| 21 |
-
class EchoBot:
|
| 22 |
-
async def on_turn(self, turn_context: TurnContext):
|
| 23 |
-
if turn_context.activity.type == "message":
|
| 24 |
-
text = (turn_context.activity.text or "").strip()
|
| 25 |
-
if not text:
|
| 26 |
-
await turn_context.send_activity("Input was empty. Type 'help' for usage.")
|
| 27 |
-
return
|
| 28 |
-
|
| 29 |
-
lower = text.lower()
|
| 30 |
-
if lower == "help":
|
| 31 |
-
await turn_context.send_activity("Try: echo <msg> | reverse: <msg> | capabilities")
|
| 32 |
-
elif lower == "capabilities":
|
| 33 |
-
await turn_context.send_activity("- echo\n- reverse\n- help\n- capabilities")
|
| 34 |
-
elif lower.startswith("reverse:"):
|
| 35 |
-
payload = text.split(":", 1)[1].strip()
|
| 36 |
-
await turn_context.send_activity(payload[::-1])
|
| 37 |
-
elif lower.startswith("echo "):
|
| 38 |
-
await turn_context.send_activity(text[5:])
|
| 39 |
-
else:
|
| 40 |
-
await turn_context.send_activity("Unsupported command. Type 'help' for examples.")
|
| 41 |
-
else:
|
| 42 |
-
await turn_context.send_activity(f"[{turn_context.activity.type}] event received.")
|
| 43 |
-
|
| 44 |
-
# -------------------------------------------------------------------
|
| 45 |
-
# Adapter / bot setup
|
| 46 |
-
# -------------------------------------------------------------------
|
| 47 |
-
APP_ID = os.environ.get("MicrosoftAppId") or None
|
| 48 |
-
APP_PASSWORD = os.environ.get("MicrosoftAppPassword") or None
|
| 49 |
-
|
| 50 |
-
adapter_settings = BotFrameworkAdapterSettings(APP_ID, APP_PASSWORD)
|
| 51 |
-
adapter = BotFrameworkAdapter(adapter_settings)
|
| 52 |
-
|
| 53 |
-
async def on_error(context: TurnContext, error: Exception):
|
| 54 |
-
print(f"[on_turn_error] {error}", file=sys.stderr, flush=True)
|
| 55 |
-
try:
|
| 56 |
-
await context.send_activity("Oops. Something went wrong!")
|
| 57 |
-
except Exception as send_err:
|
| 58 |
-
print(f"[on_turn_error][send_activity_failed] {send_err}", file=sys.stderr, flush=True)
|
| 59 |
-
|
| 60 |
-
adapter.on_turn_error = on_error
|
| 61 |
-
bot = EchoBot()
|
| 62 |
-
|
| 63 |
-
# -------------------------------------------------------------------
|
| 64 |
-
# HTTP handlers
|
| 65 |
-
# -------------------------------------------------------------------
|
| 66 |
-
async def messages(req: web.Request) -> web.Response:
|
| 67 |
-
# Content-Type can include charset; do a contains check
|
| 68 |
-
ctype = (req.headers.get("Content-Type") or "").lower()
|
| 69 |
-
if "application/json" not in ctype:
|
| 70 |
-
return web.Response(status=415, text="Unsupported Media Type: expected application/json")
|
| 71 |
-
|
| 72 |
-
try:
|
| 73 |
-
body = await req.json()
|
| 74 |
-
except json.JSONDecodeError:
|
| 75 |
-
return web.Response(status=400, text="Invalid JSON body")
|
| 76 |
-
|
| 77 |
-
activity = Activity().deserialize(body)
|
| 78 |
-
auth_header = req.headers.get("Authorization")
|
| 79 |
-
|
| 80 |
-
invoke_response = await adapter.process_activity(activity, auth_header, bot.on_turn)
|
| 81 |
-
if invoke_response:
|
| 82 |
-
# For invoke activities, adapter returns explicit status/body
|
| 83 |
-
return web.json_response(data=invoke_response.body, status=invoke_response.status)
|
| 84 |
-
# Acknowledge standard message activities
|
| 85 |
-
return web.Response(status=202, text="Accepted")
|
| 86 |
-
|
| 87 |
-
async def home(_req: web.Request) -> web.Response:
|
| 88 |
-
return web.Response(
|
| 89 |
-
text="Bot is running. POST Bot Framework activities to /api/messages.",
|
| 90 |
-
content_type="text/plain"
|
| 91 |
-
)
|
| 92 |
-
|
| 93 |
-
async def messages_get(_req: web.Request) -> web.Response:
|
| 94 |
-
return web.Response(
|
| 95 |
-
text="This endpoint only accepts POST (Bot Framework activities).",
|
| 96 |
-
content_type="text/plain",
|
| 97 |
-
status=405
|
| 98 |
-
)
|
| 99 |
-
|
| 100 |
-
async def healthz(_req: web.Request) -> web.Response:
|
| 101 |
-
return web.json_response({"status": "ok"})
|
| 102 |
-
|
| 103 |
-
async def plain_chat(req: web.Request) -> web.Response:
|
| 104 |
-
try:
|
| 105 |
-
payload = await req.json()
|
| 106 |
-
except Exception:
|
| 107 |
-
return web.json_response({"error": "Invalid JSON"}, status=400)
|
| 108 |
-
user_text = payload.get("text", "")
|
| 109 |
-
reply = handle_text(user_text)
|
| 110 |
-
return web.json_response({"reply": reply})
|
| 111 |
-
|
| 112 |
-
# -------------------------------------------------------------------
|
| 113 |
-
# App factory and entrypoint
|
| 114 |
-
# -------------------------------------------------------------------
|
| 115 |
-
from pathlib import Path
|
| 116 |
-
|
| 117 |
-
def create_app() -> web.Application:
|
| 118 |
-
app = web.Application()
|
| 119 |
-
app.router.add_get("/", home)
|
| 120 |
-
app.router.add_get("/healthz", healthz)
|
| 121 |
-
app.router.add_get("/api/messages", messages_get)
|
| 122 |
-
app.router.add_post("/api/messages", messages)
|
| 123 |
-
app.router.add_post("/plain-chat", plain_chat)
|
| 124 |
-
|
| 125 |
-
static_dir = Path(__file__).parent / "static"
|
| 126 |
-
if static_dir.exists():
|
| 127 |
-
app.router.add_static("/static/", path=static_dir, show_index=True)
|
| 128 |
-
else:
|
| 129 |
-
print(f"[warn] static directory not found: {static_dir}", flush=True)
|
| 130 |
-
|
| 131 |
-
return app
|
| 132 |
-
|
| 133 |
-
app = create_app()
|
| 134 |
-
|
| 135 |
-
if __name__ == "__main__":
|
| 136 |
-
host = os.environ.get("HOST", "127.0.0.1") # use 0.0.0.0 in containers
|
| 137 |
-
port = int(os.environ.get("PORT", 3978))
|
| 138 |
-
web.run_app(app, host=host, port=port)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
full/.env.example
DELETED
|
@@ -1,9 +0,0 @@
|
|
| 1 |
-
# Optional provider pinning & keys
|
| 2 |
-
# AI_PROVIDER=hf|azure|openai|cohere|deepai|offline
|
| 3 |
-
# HF_API_KEY=
|
| 4 |
-
# MICROSOFT_AI_SERVICE_ENDPOINT=
|
| 5 |
-
# MICROSOFT_AI_API_KEY=
|
| 6 |
-
# OPENAI_API_KEY=
|
| 7 |
-
# COHERE_API_KEY=
|
| 8 |
-
# DEEPAI_API_KEY=
|
| 9 |
-
# HTTP_TIMEOUT=20
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
full/README.md
DELETED
|
@@ -1,13 +0,0 @@
|
|
| 1 |
-
# Storefront Chatbot (Unified)
|
| 2 |
-
|
| 3 |
-
This merges your skeleton with AgenticCore + a simple FastAPI backend and frontends.
|
| 4 |
-
|
| 5 |
-
## Run
|
| 6 |
-
1) python -m venv .venv && source .venv/bin/activate
|
| 7 |
-
2) pip install -r requirements.txt
|
| 8 |
-
3) uvicorn backend.app.main:app --reload --port 8000
|
| 9 |
-
4) Open http://127.0.0.1:8000/ui or open frontend/chat_minimal.html
|
| 10 |
-
|
| 11 |
-
## Test
|
| 12 |
-
- pytest -q
|
| 13 |
-
- python tools/smoke_test.py
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
full/agenticcore/__init__.py
DELETED
|
File without changes
|
full/agenticcore/chatbot/__init__.py
DELETED
|
File without changes
|
full/agenticcore/chatbot/services.py
DELETED
|
@@ -1,102 +0,0 @@
|
|
| 1 |
-
from __future__ import annotations
|
| 2 |
-
|
| 3 |
-
import json
|
| 4 |
-
import os
|
| 5 |
-
from dataclasses import dataclass
|
| 6 |
-
from typing import Dict
|
| 7 |
-
|
| 8 |
-
# Delegate sentiment to the unified provider layer
|
| 9 |
-
# If you put providers_unified.py under agenticcore/chatbot/, change the import to:
|
| 10 |
-
# from agenticcore.chatbot.providers_unified import analyze_sentiment
|
| 11 |
-
from agenticcore.providers_unified import analyze_sentiment
|
| 12 |
-
from ..providers_unified import analyze_sentiment
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
def _trim(s: str, max_len: int = 2000) -> str:
|
| 16 |
-
s = (s or "").strip()
|
| 17 |
-
return s if len(s) <= max_len else s[: max_len - 1] + "…"
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
@dataclass(frozen=True)
|
| 21 |
-
class SentimentResult:
|
| 22 |
-
label: str # "positive" | "neutral" | "negative" | "mixed" | "unknown"
|
| 23 |
-
confidence: float # 0.0 .. 1.0
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
class ChatBot:
|
| 27 |
-
"""
|
| 28 |
-
Minimal chatbot that uses provider-agnostic sentiment via providers_unified.
|
| 29 |
-
Public API:
|
| 30 |
-
- reply(text: str) -> Dict[str, object]
|
| 31 |
-
- capabilities() -> Dict[str, object]
|
| 32 |
-
"""
|
| 33 |
-
|
| 34 |
-
def __init__(self, system_prompt: str = "You are a concise helper.") -> None:
|
| 35 |
-
self._system_prompt = _trim(system_prompt, 800)
|
| 36 |
-
# Expose which provider is intended/active (for diagnostics)
|
| 37 |
-
self._mode = os.getenv("AI_PROVIDER") or "auto"
|
| 38 |
-
|
| 39 |
-
def capabilities(self) -> Dict[str, object]:
|
| 40 |
-
"""List what this bot can do."""
|
| 41 |
-
return {
|
| 42 |
-
"system": "chatbot",
|
| 43 |
-
"mode": self._mode, # "auto" or a pinned provider (hf/azure/openai/cohere/deepai/offline)
|
| 44 |
-
"features": ["text-input", "sentiment-analysis", "help"],
|
| 45 |
-
"commands": {"help": "Describe capabilities and usage."},
|
| 46 |
-
}
|
| 47 |
-
|
| 48 |
-
def reply(self, text: str) -> Dict[str, object]:
|
| 49 |
-
"""Produce a reply and sentiment for one user message."""
|
| 50 |
-
user = _trim(text)
|
| 51 |
-
if not user:
|
| 52 |
-
return self._make_response(
|
| 53 |
-
"I didn't catch that. Please provide some text.",
|
| 54 |
-
SentimentResult("unknown", 0.0),
|
| 55 |
-
)
|
| 56 |
-
|
| 57 |
-
if user.lower() in {"help", "/help"}:
|
| 58 |
-
return {"reply": self._format_help(), "capabilities": self.capabilities()}
|
| 59 |
-
|
| 60 |
-
s = analyze_sentiment(user) # -> {"provider", "label", "score", ...}
|
| 61 |
-
sr = SentimentResult(label=str(s.get("label", "neutral")), confidence=float(s.get("score", 0.5)))
|
| 62 |
-
return self._make_response(self._compose(sr), sr)
|
| 63 |
-
|
| 64 |
-
# ---- internals ----
|
| 65 |
-
|
| 66 |
-
def _format_help(self) -> str:
|
| 67 |
-
caps = self.capabilities()
|
| 68 |
-
feats = ", ".join(caps["features"])
|
| 69 |
-
return f"I can analyze sentiment and respond concisely. Features: {feats}. Send any text or type 'help'."
|
| 70 |
-
|
| 71 |
-
@staticmethod
|
| 72 |
-
def _make_response(reply: str, s: SentimentResult) -> Dict[str, object]:
|
| 73 |
-
return {"reply": reply, "sentiment": s.label, "confidence": round(float(s.confidence), 2)}
|
| 74 |
-
|
| 75 |
-
@staticmethod
|
| 76 |
-
def _compose(s: SentimentResult) -> str:
|
| 77 |
-
if s.label == "positive":
|
| 78 |
-
return "Thanks for sharing. I detected a positive sentiment."
|
| 79 |
-
if s.label == "negative":
|
| 80 |
-
return "I hear your concern. I detected a negative sentiment."
|
| 81 |
-
if s.label == "neutral":
|
| 82 |
-
return "Noted. The sentiment appears neutral."
|
| 83 |
-
if s.label == "mixed":
|
| 84 |
-
return "Your message has mixed signals. Can you clarify?"
|
| 85 |
-
return "I could not determine the sentiment. Please rephrase."
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
# Optional: local REPL for quick manual testing
|
| 89 |
-
def _interactive_loop() -> None:
|
| 90 |
-
bot = ChatBot()
|
| 91 |
-
try:
|
| 92 |
-
while True:
|
| 93 |
-
msg = input("> ").strip()
|
| 94 |
-
if msg.lower() in {"exit", "quit"}:
|
| 95 |
-
break
|
| 96 |
-
print(json.dumps(bot.reply(msg), ensure_ascii=False))
|
| 97 |
-
except (EOFError, KeyboardInterrupt):
|
| 98 |
-
pass
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
if __name__ == "__main__":
|
| 102 |
-
_interactive_loop()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
full/agenticcore/providers_unified.py
DELETED
|
@@ -1,273 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
providers_unified.py
|
| 3 |
-
Unified, switchable providers for sentiment + (optional) text generation.
|
| 4 |
-
Selection order unless AI_PROVIDER is set:
|
| 5 |
-
HF -> AZURE -> OPENAI -> COHERE -> DEEPAI -> OFFLINE
|
| 6 |
-
Env vars:
|
| 7 |
-
HF_API_KEY
|
| 8 |
-
MICROSOFT_AI_SERVICE_ENDPOINT, MICROSOFT_AI_API_KEY
|
| 9 |
-
OPENAI_API_KEY, OPENAI_MODEL=gpt-3.5-turbo
|
| 10 |
-
COHERE_API_KEY, COHERE_MODEL=command
|
| 11 |
-
DEEPAI_API_KEY
|
| 12 |
-
AI_PROVIDER = hf|azure|openai|cohere|deepai|offline
|
| 13 |
-
HTTP_TIMEOUT = 20
|
| 14 |
-
"""
|
| 15 |
-
from __future__ import annotations
|
| 16 |
-
import os, json
|
| 17 |
-
from typing import Dict, Any, Optional
|
| 18 |
-
import requests
|
| 19 |
-
|
| 20 |
-
TIMEOUT = float(os.getenv("HTTP_TIMEOUT", "20"))
|
| 21 |
-
|
| 22 |
-
def _env(name: str, default: Optional[str] = None) -> Optional[str]:
|
| 23 |
-
v = os.getenv(name)
|
| 24 |
-
return v if (v is not None and str(v).strip() != "") else default
|
| 25 |
-
|
| 26 |
-
def _pick_provider() -> str:
|
| 27 |
-
forced = _env("AI_PROVIDER")
|
| 28 |
-
if forced in {"hf", "azure", "openai", "cohere", "deepai", "offline"}:
|
| 29 |
-
return forced
|
| 30 |
-
if _env("HF_API_KEY"): return "hf"
|
| 31 |
-
if _env("MICROSOFT_AI_API_KEY") and _env("MICROSOFT_AI_SERVICE_ENDPOINT"): return "azure"
|
| 32 |
-
if _env("OPENAI_API_KEY"): return "openai"
|
| 33 |
-
if _env("COHERE_API_KEY"): return "cohere"
|
| 34 |
-
if _env("DEEPAI_API_KEY"): return "deepai"
|
| 35 |
-
return "offline"
|
| 36 |
-
|
| 37 |
-
# ---------------------------
|
| 38 |
-
# Sentiment
|
| 39 |
-
# ---------------------------
|
| 40 |
-
|
| 41 |
-
def analyze_sentiment(text: str) -> Dict[str, Any]:
|
| 42 |
-
provider = _pick_provider()
|
| 43 |
-
try:
|
| 44 |
-
if provider == "hf": return _sentiment_hf(text)
|
| 45 |
-
if provider == "azure": return _sentiment_azure(text)
|
| 46 |
-
if provider == "openai": return _sentiment_openai_prompt(text)
|
| 47 |
-
if provider == "cohere": return _sentiment_cohere_prompt(text)
|
| 48 |
-
if provider == "deepai": return _sentiment_deepai(text)
|
| 49 |
-
return _sentiment_offline(text)
|
| 50 |
-
except Exception as e:
|
| 51 |
-
return {"provider": provider, "label": "neutral", "score": 0.5, "error": str(e)}
|
| 52 |
-
|
| 53 |
-
def _sentiment_offline(text: str) -> Dict[str, Any]:
|
| 54 |
-
t = (text or "").lower()
|
| 55 |
-
pos = any(w in t for w in ["love","great","good","awesome","fantastic","thank","excellent","amazing"])
|
| 56 |
-
neg = any(w in t for w in ["hate","bad","terrible","awful","worst","angry","horrible"])
|
| 57 |
-
label = "positive" if pos and not neg else "negative" if neg and not pos else "neutral"
|
| 58 |
-
score = 0.9 if label != "neutral" else 0.5
|
| 59 |
-
return {"provider": "offline", "label": label, "score": score}
|
| 60 |
-
|
| 61 |
-
def _sentiment_hf(text: str) -> Dict[str, Any]:
|
| 62 |
-
"""
|
| 63 |
-
Hugging Face Inference API for sentiment.
|
| 64 |
-
Uses canonical repo id and handles 404/401 and various payload shapes.
|
| 65 |
-
"""
|
| 66 |
-
key = _env("HF_API_KEY")
|
| 67 |
-
if not key:
|
| 68 |
-
return _sentiment_offline(text)
|
| 69 |
-
|
| 70 |
-
# canonical repo id to avoid 404
|
| 71 |
-
model = _env("HF_MODEL_SENTIMENT", "distilbert/distilbert-base-uncased-finetuned-sst-2-english")
|
| 72 |
-
timeout = int(_env("HTTP_TIMEOUT", "30"))
|
| 73 |
-
|
| 74 |
-
headers = {
|
| 75 |
-
"Authorization": f"Bearer {key}",
|
| 76 |
-
"x-wait-for-model": "true",
|
| 77 |
-
"Accept": "application/json",
|
| 78 |
-
"Content-Type": "application/json",
|
| 79 |
-
}
|
| 80 |
-
|
| 81 |
-
r = requests.post(
|
| 82 |
-
f"https://api-inference.huggingface.co/models/{model}",
|
| 83 |
-
headers=headers,
|
| 84 |
-
json={"inputs": text},
|
| 85 |
-
timeout=timeout,
|
| 86 |
-
)
|
| 87 |
-
|
| 88 |
-
if r.status_code != 200:
|
| 89 |
-
return {"provider": "hf", "label": "neutral", "score": 0.5, "error": f"HTTP {r.status_code}: {r.text[:500]}"}
|
| 90 |
-
|
| 91 |
-
try:
|
| 92 |
-
data = r.json()
|
| 93 |
-
except Exception as e:
|
| 94 |
-
return {"provider": "hf", "label": "neutral", "score": 0.5, "error": str(e)}
|
| 95 |
-
|
| 96 |
-
if isinstance(data, dict) and "error" in data:
|
| 97 |
-
return {"provider": "hf", "label": "neutral", "score": 0.5, "error": data["error"]}
|
| 98 |
-
|
| 99 |
-
# normalize list shape
|
| 100 |
-
arr = data[0] if isinstance(data, list) and data and isinstance(data[0], list) else (data if isinstance(data, list) else [])
|
| 101 |
-
if not (isinstance(arr, list) and arr):
|
| 102 |
-
return {"provider": "hf", "label": "neutral", "score": 0.5, "error": f"Unexpected payload: {data}"}
|
| 103 |
-
|
| 104 |
-
top = max(arr, key=lambda x: x.get("score", 0.0) if isinstance(x, dict) else 0.0)
|
| 105 |
-
raw = str(top.get("label", "")).upper()
|
| 106 |
-
score = float(top.get("score", 0.5))
|
| 107 |
-
|
| 108 |
-
mapping = {
|
| 109 |
-
"LABEL_0": "negative", "LABEL_1": "neutral", "LABEL_2": "positive",
|
| 110 |
-
"NEGATIVE": "negative", "NEUTRAL": "neutral", "POSITIVE": "positive",
|
| 111 |
-
}
|
| 112 |
-
label = mapping.get(raw, (raw.lower() or "neutral"))
|
| 113 |
-
|
| 114 |
-
neutral_floor = float(os.getenv("SENTIMENT_NEUTRAL_THRESHOLD", "0.65"))
|
| 115 |
-
if label in {"positive", "negative"} and score < neutral_floor:
|
| 116 |
-
label = "neutral"
|
| 117 |
-
|
| 118 |
-
return {"provider": "hf", "label": label, "score": score}
|
| 119 |
-
|
| 120 |
-
def _sentiment_azure(text: str) -> Dict[str, Any]:
|
| 121 |
-
try:
|
| 122 |
-
from azure.core.credentials import AzureKeyCredential # type: ignore
|
| 123 |
-
from azure.ai.textanalytics import TextAnalyticsClient # type: ignore
|
| 124 |
-
except Exception:
|
| 125 |
-
return _sentiment_offline(text)
|
| 126 |
-
endpoint = _env("MICROSOFT_AI_SERVICE_ENDPOINT")
|
| 127 |
-
key = _env("MICROSOFT_AI_API_KEY")
|
| 128 |
-
if not (endpoint and key): return _sentiment_offline(text)
|
| 129 |
-
client = TextAnalyticsClient(endpoint=endpoint.strip(), credential=AzureKeyCredential(key.strip()))
|
| 130 |
-
resp = client.analyze_sentiment(documents=[text], show_opinion_mining=False)[0]
|
| 131 |
-
scores = {
|
| 132 |
-
"positive": float(getattr(resp.confidence_scores, "positive", 0.0) or 0.0),
|
| 133 |
-
"neutral": float(getattr(resp.confidence_scores, "neutral", 0.0) or 0.0),
|
| 134 |
-
"negative": float(getattr(resp.confidence_scores, "negative", 0.0) or 0.0),
|
| 135 |
-
}
|
| 136 |
-
label = max(scores, key=scores.get)
|
| 137 |
-
return {"provider": "azure", "label": label, "score": scores[label]}
|
| 138 |
-
|
| 139 |
-
def _sentiment_openai_prompt(text: str) -> Dict[str, Any]:
|
| 140 |
-
key = _env("OPENAI_API_KEY")
|
| 141 |
-
model = _env("OPENAI_MODEL", "gpt-3.5-turbo")
|
| 142 |
-
if not key: return _sentiment_offline(text)
|
| 143 |
-
url = "https://api.openai.com/v1/chat/completions"
|
| 144 |
-
prompt = f"Classify the sentiment of this text as positive, negative, or neutral. Reply JSON with keys label and score (0..1). Text: {text!r}"
|
| 145 |
-
r = requests.post(
|
| 146 |
-
url,
|
| 147 |
-
headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"},
|
| 148 |
-
json={"model": model, "messages": [{"role": "user", "content": prompt}], "temperature": 0},
|
| 149 |
-
timeout=TIMEOUT,
|
| 150 |
-
)
|
| 151 |
-
r.raise_for_status()
|
| 152 |
-
content = r.json()["choices"][0]["message"]["content"]
|
| 153 |
-
try:
|
| 154 |
-
obj = json.loads(content)
|
| 155 |
-
label = str(obj.get("label", "neutral")).lower()
|
| 156 |
-
score = float(obj.get("score", 0.5))
|
| 157 |
-
return {"provider": "openai", "label": label, "score": score}
|
| 158 |
-
except Exception:
|
| 159 |
-
l = "positive" if "positive" in content.lower() else "negative" if "negative" in content.lower() else "neutral"
|
| 160 |
-
return {"provider": "openai", "label": l, "score": 0.5}
|
| 161 |
-
|
| 162 |
-
def _sentiment_cohere_prompt(text: str) -> Dict[str, Any]:
|
| 163 |
-
key = _env("COHERE_API_KEY")
|
| 164 |
-
model = _env("COHERE_MODEL", "command")
|
| 165 |
-
if not key: return _sentiment_offline(text)
|
| 166 |
-
url = "https://api.cohere.ai/v1/generate"
|
| 167 |
-
prompt = f"Classify the sentiment (positive, negative, neutral) and return JSON with keys label and score (0..1). Text: {text!r}"
|
| 168 |
-
r = requests.post(
|
| 169 |
-
url,
|
| 170 |
-
headers={
|
| 171 |
-
"Authorization": f"Bearer {key}",
|
| 172 |
-
"Content-Type": "application/json",
|
| 173 |
-
"Cohere-Version": "2022-12-06",
|
| 174 |
-
},
|
| 175 |
-
json={"model": model, "prompt": prompt, "max_tokens": 30, "temperature": 0},
|
| 176 |
-
timeout=TIMEOUT,
|
| 177 |
-
)
|
| 178 |
-
r.raise_for_status()
|
| 179 |
-
gen = (r.json().get("generations") or [{}])[0].get("text", "")
|
| 180 |
-
try:
|
| 181 |
-
obj = json.loads(gen)
|
| 182 |
-
label = str(obj.get("label", "neutral")).lower()
|
| 183 |
-
score = float(obj.get("score", 0.5))
|
| 184 |
-
return {"provider": "cohere", "label": label, "score": score}
|
| 185 |
-
except Exception:
|
| 186 |
-
l = "positive" if "positive" in gen.lower() else "negative" if "negative" in gen.lower() else "neutral"
|
| 187 |
-
return {"provider": "cohere", "label": l, "score": 0.5}
|
| 188 |
-
|
| 189 |
-
def _sentiment_deepai(text: str) -> Dict[str, Any]:
|
| 190 |
-
key = _env("DEEPAI_API_KEY")
|
| 191 |
-
if not key: return _sentiment_offline(text)
|
| 192 |
-
url = "https://api.deepai.org/api/sentiment-analysis"
|
| 193 |
-
r = requests.post(url, headers={"api-key": key}, data={"text": text}, timeout=TIMEOUT)
|
| 194 |
-
r.raise_for_status()
|
| 195 |
-
data = r.json()
|
| 196 |
-
label = (data.get("output") or ["neutral"])[0].lower()
|
| 197 |
-
return {"provider": "deepai", "label": label, "score": 0.5 if label == "neutral" else 0.9}
|
| 198 |
-
|
| 199 |
-
# ---------------------------
|
| 200 |
-
# Text generation (optional)
|
| 201 |
-
# ---------------------------
|
| 202 |
-
|
| 203 |
-
def generate_text(prompt: str, max_tokens: int = 128) -> Dict[str, Any]:
|
| 204 |
-
provider = _pick_provider()
|
| 205 |
-
try:
|
| 206 |
-
if provider == "hf": return _gen_hf(prompt, max_tokens)
|
| 207 |
-
if provider == "openai": return _gen_openai(prompt, max_tokens)
|
| 208 |
-
if provider == "cohere": return _gen_cohere(prompt, max_tokens)
|
| 209 |
-
if provider == "deepai": return _gen_deepai(prompt, max_tokens)
|
| 210 |
-
return {"provider": "offline", "text": f"(offline) {prompt[:160]}"}
|
| 211 |
-
except Exception as e:
|
| 212 |
-
return {"provider": provider, "text": f"(error) {str(e)}"}
|
| 213 |
-
|
| 214 |
-
def _gen_hf(prompt: str, max_tokens: int) -> Dict[str, Any]:
|
| 215 |
-
key = _env("HF_API_KEY")
|
| 216 |
-
if not key: return {"provider": "offline", "text": f"(offline) {prompt[:160]}"}
|
| 217 |
-
model = _env("HF_MODEL_GENERATION", "tiiuae/falcon-7b-instruct")
|
| 218 |
-
r = requests.post(
|
| 219 |
-
f"https://api-inference.huggingface.co/models/{model}",
|
| 220 |
-
headers={"Authorization": f"Bearer {key}"},
|
| 221 |
-
json={"inputs": prompt, "parameters": {"max_new_tokens": max_tokens}},
|
| 222 |
-
timeout=TIMEOUT,
|
| 223 |
-
)
|
| 224 |
-
r.raise_for_status()
|
| 225 |
-
data = r.json()
|
| 226 |
-
if isinstance(data, list) and data and "generated_text" in data[0]:
|
| 227 |
-
return {"provider": "hf", "text": data[0]["generated_text"]}
|
| 228 |
-
return {"provider": "hf", "text": str(data)}
|
| 229 |
-
|
| 230 |
-
def _gen_openai(prompt: str, max_tokens: int) -> Dict[str, Any]:
|
| 231 |
-
key = _env("OPENAI_API_KEY")
|
| 232 |
-
model = _env("OPENAI_MODEL", "gpt-3.5-turbo")
|
| 233 |
-
if not key: return {"provider": "offline", "text": f"(offline) {prompt[:160]}"}
|
| 234 |
-
url = "https://api.openai.com/v1/chat/completions"
|
| 235 |
-
r = requests.post(
|
| 236 |
-
url,
|
| 237 |
-
headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"},
|
| 238 |
-
json={"model": model, "messages": [{"role": "user", "content": prompt}], "max_tokens": max_tokens},
|
| 239 |
-
timeout=TIMEOUT,
|
| 240 |
-
)
|
| 241 |
-
r.raise_for_status()
|
| 242 |
-
data = r.json()
|
| 243 |
-
text = data["choices"][0]["message"]["content"]
|
| 244 |
-
return {"provider": "openai", "text": text}
|
| 245 |
-
|
| 246 |
-
def _gen_cohere(prompt: str, max_tokens: int) -> Dict[str, Any]:
|
| 247 |
-
key = _env("COHERE_API_KEY")
|
| 248 |
-
model = _env("COHERE_MODEL", "command")
|
| 249 |
-
if not key: return {"provider": "offline", "text": f"(offline) {prompt[:160]}"}
|
| 250 |
-
url = "https://api.cohere.ai/v1/generate"
|
| 251 |
-
r = requests.post(
|
| 252 |
-
url,
|
| 253 |
-
headers={
|
| 254 |
-
"Authorization": f"Bearer {key}",
|
| 255 |
-
"Content-Type": "application/json",
|
| 256 |
-
"Cohere-Version": "2022-12-06",
|
| 257 |
-
},
|
| 258 |
-
json={"model": model, "prompt": prompt, "max_tokens": max_tokens},
|
| 259 |
-
timeout=TIMEOUT,
|
| 260 |
-
)
|
| 261 |
-
r.raise_for_status()
|
| 262 |
-
data = r.json()
|
| 263 |
-
text = data.get("generations", [{}])[0].get("text", "")
|
| 264 |
-
return {"provider": "cohere", "text": text}
|
| 265 |
-
|
| 266 |
-
def _gen_deepai(prompt: str, max_tokens: int) -> Dict[str, Any]:
|
| 267 |
-
key = _env("DEEPAI_API_KEY")
|
| 268 |
-
if not key: return {"provider": "offline", "text": f"(offline) {prompt[:160]}"}
|
| 269 |
-
url = "https://api.deepai.org/api/text-generator"
|
| 270 |
-
r = requests.post(url, headers={"api-key": key}, data={"text": prompt}, timeout=TIMEOUT)
|
| 271 |
-
r.raise_for_status()
|
| 272 |
-
data = r.json()
|
| 273 |
-
return {"provider": "deepai", "text": data.get("output", "")}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
full/backend/__init__.py
DELETED
|
File without changes
|
full/backend/app/__init__.py
DELETED
|
File without changes
|
full/backend/app/main.py
DELETED
|
@@ -1,69 +0,0 @@
|
|
| 1 |
-
# backend/app/main.py
|
| 2 |
-
from __future__ import annotations
|
| 3 |
-
|
| 4 |
-
import os
|
| 5 |
-
import time
|
| 6 |
-
import traceback
|
| 7 |
-
from pathlib import Path
|
| 8 |
-
|
| 9 |
-
from fastapi import FastAPI
|
| 10 |
-
from fastapi.middleware.cors import CORSMiddleware
|
| 11 |
-
from fastapi.responses import HTMLResponse
|
| 12 |
-
from fastapi.staticfiles import StaticFiles
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
def create_app() -> FastAPI:
|
| 16 |
-
app = FastAPI(title="AgenticCore Backend")
|
| 17 |
-
|
| 18 |
-
# --- CORS: allow local dev origins + file:// (appears as 'null') ---
|
| 19 |
-
app.add_middleware(
|
| 20 |
-
CORSMiddleware,
|
| 21 |
-
allow_origins=[
|
| 22 |
-
"http://127.0.0.1:3000", "http://localhost:3000",
|
| 23 |
-
"http://127.0.0.1:5173", "http://localhost:5173",
|
| 24 |
-
"http://127.0.0.1:8080", "http://localhost:8080",
|
| 25 |
-
"http://127.0.0.1:8000", "http://localhost:8000",
|
| 26 |
-
"null", # file:// pages in some browsers
|
| 27 |
-
],
|
| 28 |
-
allow_credentials=False,
|
| 29 |
-
allow_methods=["*"],
|
| 30 |
-
allow_headers=["*"],
|
| 31 |
-
)
|
| 32 |
-
|
| 33 |
-
@app.get("/health")
|
| 34 |
-
def health():
|
| 35 |
-
return {"ok": True, "version": "0.3.0", "time": int(time.time())}
|
| 36 |
-
|
| 37 |
-
@app.get("/status")
|
| 38 |
-
def status():
|
| 39 |
-
provider = os.getenv("AI_PROVIDER") or "auto"
|
| 40 |
-
return {"provider": provider}
|
| 41 |
-
|
| 42 |
-
# --- Routers ---
|
| 43 |
-
try:
|
| 44 |
-
from backend.app.routers.chatbot import router as chatbot_router
|
| 45 |
-
app.include_router(chatbot_router) # exposes POST /chatbot/message
|
| 46 |
-
print("✅ Chatbot router mounted at /chatbot")
|
| 47 |
-
except Exception as e:
|
| 48 |
-
print("❌ Failed to mount chatbot router:", e)
|
| 49 |
-
traceback.print_exc()
|
| 50 |
-
|
| 51 |
-
# --- Static frontends served by FastAPI (same-origin -> no CORS) ---
|
| 52 |
-
FRONTEND_DIR = Path(__file__).resolve().parents[2] / "frontend"
|
| 53 |
-
app.mount("/ui2", StaticFiles(directory=str(FRONTEND_DIR)), name="frontend")
|
| 54 |
-
|
| 55 |
-
# Keep your existing single-file UI at /ui (optional)
|
| 56 |
-
FRONTEND_FILE = FRONTEND_DIR / "agenticcore_frontend.html"
|
| 57 |
-
|
| 58 |
-
@app.get("/ui", response_class=HTMLResponse)
|
| 59 |
-
def ui():
|
| 60 |
-
try:
|
| 61 |
-
return FRONTEND_FILE.read_text(encoding="utf-8")
|
| 62 |
-
except Exception:
|
| 63 |
-
return HTMLResponse("<h1>UI not found</h1>", status_code=404)
|
| 64 |
-
|
| 65 |
-
return app
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
# Uvicorn entrypoint
|
| 69 |
-
app = create_app()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
full/backend/app/routers/__init__.py
DELETED
|
File without changes
|
full/backend/app/routers/chatbot.py
DELETED
|
@@ -1,27 +0,0 @@
|
|
| 1 |
-
from fastapi import APIRouter
|
| 2 |
-
from pydantic import BaseModel
|
| 3 |
-
from typing import Optional, Dict, Any
|
| 4 |
-
|
| 5 |
-
router = APIRouter(prefix="/chatbot", tags=["chatbot"])
|
| 6 |
-
|
| 7 |
-
class ChatIn(BaseModel):
|
| 8 |
-
message: str
|
| 9 |
-
thread: Optional[str] = None
|
| 10 |
-
|
| 11 |
-
class ChatOut(BaseModel):
|
| 12 |
-
reply: str
|
| 13 |
-
sentiment: Optional[str] = None
|
| 14 |
-
confidence: Optional[float] = None
|
| 15 |
-
thread: Optional[str] = None
|
| 16 |
-
|
| 17 |
-
@router.post("/message", response_model=ChatOut)
|
| 18 |
-
def post_message(body: ChatIn) -> Dict[str, Any]:
|
| 19 |
-
from agenticcore.chatbot.services import ChatBot
|
| 20 |
-
bot = ChatBot()
|
| 21 |
-
res = bot.reply(body.message)
|
| 22 |
-
return {
|
| 23 |
-
"reply": res.get("reply", ""),
|
| 24 |
-
"sentiment": res.get("sentiment"),
|
| 25 |
-
"confidence": res.get("confidence"),
|
| 26 |
-
"thread": body.thread or res.get("thread"),
|
| 27 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
full/extras/__init__.py
DELETED
|
File without changes
|
full/frontend/__init__.py
DELETED
|
File without changes
|
full/frontend/agenticcore_frontend.html
DELETED
|
@@ -1,200 +0,0 @@
|
|
| 1 |
-
<!doctype html>
|
| 2 |
-
<html lang="en">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="utf-8" />
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
| 6 |
-
<title>AgenticCore Chatbot Frontend</title>
|
| 7 |
-
<style>
|
| 8 |
-
:root {
|
| 9 |
-
--bg: #0b0d12;
|
| 10 |
-
--panel: #0f172a;
|
| 11 |
-
--panel-2: #111827;
|
| 12 |
-
--text: #e5e7eb;
|
| 13 |
-
--muted: #9ca3af;
|
| 14 |
-
--accent: #60a5fa;
|
| 15 |
-
--border: #1f2940;
|
| 16 |
-
--danger: #ef4444;
|
| 17 |
-
--success: #22c55e;
|
| 18 |
-
}
|
| 19 |
-
* { box-sizing: border-box; }
|
| 20 |
-
body { margin: 0; font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; background: var(--bg); color: var(--text); }
|
| 21 |
-
.wrap { max-width: 920px; margin: 32px auto; padding: 0 16px; }
|
| 22 |
-
header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; gap: 16px; }
|
| 23 |
-
header h1 { font-size: 18px; margin: 0; letter-spacing: .3px; }
|
| 24 |
-
header .badge { font-size: 12px; opacity: .85; padding: 4px 8px; border:1px solid var(--border); border-radius: 999px; background: rgba(255,255,255,0.03); }
|
| 25 |
-
.card { background: var(--panel); border: 1px solid var(--border); border-radius: 16px; padding: 16px; }
|
| 26 |
-
.row { display: flex; gap: 10px; align-items: center; }
|
| 27 |
-
.stack { display: grid; gap: 12px; }
|
| 28 |
-
label { font-size: 12px; color: var(--muted); }
|
| 29 |
-
input[type=text] { flex: 1; padding: 12px 14px; border-radius: 12px; border: 1px solid var(--border); background: var(--panel-2); color: var(--text); outline: none; }
|
| 30 |
-
input[type=text]::placeholder { color: #6b7280; }
|
| 31 |
-
button { padding: 10px 14px; border-radius: 12px; border: 1px solid var(--border); background: #1f2937; color: var(--text); cursor: pointer; transition: transform .02s ease, background .2s; }
|
| 32 |
-
button:hover { background: #273449; }
|
| 33 |
-
button:active { transform: translateY(1px); }
|
| 34 |
-
.btn-primary { background: #1f2937; border-color: #31405a; }
|
| 35 |
-
.btn-ghost { background: transparent; border-color: var(--border); }
|
| 36 |
-
.grid { display: grid; gap: 12px; }
|
| 37 |
-
.grid-2 { grid-template-columns: 1fr 1fr; }
|
| 38 |
-
.log { margin-top: 16px; display: grid; gap: 10px; }
|
| 39 |
-
.bubble { max-width: 80%; padding: 12px 14px; border-radius: 14px; line-height: 1.35; }
|
| 40 |
-
.user { background: #1e293b; border:1px solid #2b3b55; margin-left: auto; border-bottom-right-radius: 4px; }
|
| 41 |
-
.bot { background: #0d1b2a; border:1px solid #223049; margin-right: auto; border-bottom-left-radius: 4px; }
|
| 42 |
-
.meta { font-size: 12px; color: var(--muted); margin-top: 4px; }
|
| 43 |
-
pre { margin: 0; white-space: pre-wrap; word-break: break-word; }
|
| 44 |
-
.status { display:flex; align-items:center; gap:8px; font-size: 12px; color: var(--muted); }
|
| 45 |
-
.dot { width:8px; height:8px; border-radius:999px; background: #64748b; display:inline-block; }
|
| 46 |
-
.dot.ok { background: var(--success); }
|
| 47 |
-
.dot.bad { background: var(--danger); }
|
| 48 |
-
footer { margin: 24px 0; text-align:center; color: var(--muted); font-size: 12px; }
|
| 49 |
-
.small { font-size: 12px; }
|
| 50 |
-
@media (max-width: 700px) { .grid-2 { grid-template-columns: 1fr; } }
|
| 51 |
-
</style>
|
| 52 |
-
</head>
|
| 53 |
-
<body>
|
| 54 |
-
<div class="wrap">
|
| 55 |
-
<header>
|
| 56 |
-
<h1>AgenticCore Chatbot Frontend</h1>
|
| 57 |
-
<div class="badge">Frontend → FastAPI → providers_unified</div>
|
| 58 |
-
</header>
|
| 59 |
-
|
| 60 |
-
<section class="card stack">
|
| 61 |
-
<div class="grid grid-2">
|
| 62 |
-
<div class="stack">
|
| 63 |
-
<label for="backend">Backend URL</label>
|
| 64 |
-
<div class="row">
|
| 65 |
-
<input id="backend" type="text" placeholder="http://127.0.0.1:8000" />
|
| 66 |
-
<button id="save" class="btn-ghost">Save</button>
|
| 67 |
-
</div>
|
| 68 |
-
<div class="status" id="status"><span class="dot"></span><span>Not checked</span></div>
|
| 69 |
-
</div>
|
| 70 |
-
<div class="stack">
|
| 71 |
-
<label for="message">Message</label>
|
| 72 |
-
<div class="row">
|
| 73 |
-
<input id="message" type="text" placeholder="Type a message…" />
|
| 74 |
-
<button id="send" class="btn-primary">Send</button>
|
| 75 |
-
</div>
|
| 76 |
-
<div class="row">
|
| 77 |
-
<button id="cap" class="btn-ghost small">Capabilities</button>
|
| 78 |
-
<button id="health" class="btn-ghost small">Health</button>
|
| 79 |
-
<button id="clear" class="btn-ghost small">Clear</button>
|
| 80 |
-
</div>
|
| 81 |
-
</div>
|
| 82 |
-
</div>
|
| 83 |
-
<div class="log" id="log"></div>
|
| 84 |
-
</section>
|
| 85 |
-
|
| 86 |
-
<footer>
|
| 87 |
-
Use with your FastAPI backend at <code>/chatbot/message</code>. Configure CORS if you serve this file from a different origin.
|
| 88 |
-
</footer>
|
| 89 |
-
</div>
|
| 90 |
-
|
| 91 |
-
<script>
|
| 92 |
-
const $ = (sel) => document.querySelector(sel);
|
| 93 |
-
const backendInput = $('#backend');
|
| 94 |
-
const sendBtn = $('#send');
|
| 95 |
-
const saveBtn = $('#save');
|
| 96 |
-
const msgInput = $('#message');
|
| 97 |
-
const capBtn = $('#cap');
|
| 98 |
-
const healthBtn = $('#health');
|
| 99 |
-
const clearBtn = $('#clear');
|
| 100 |
-
const log = $('#log');
|
| 101 |
-
const status = $('#status');
|
| 102 |
-
const dot = status.querySelector('.dot');
|
| 103 |
-
const statusText = status.querySelector('span:last-child');
|
| 104 |
-
|
| 105 |
-
function getBackendUrl() {
|
| 106 |
-
return localStorage.getItem('BACKEND_URL') || 'http://127.0.0.1:8000';
|
| 107 |
-
}
|
| 108 |
-
function setBackendUrl(v) {
|
| 109 |
-
localStorage.setItem('BACKEND_URL', v);
|
| 110 |
-
}
|
| 111 |
-
function cardUser(text) {
|
| 112 |
-
const div = document.createElement('div');
|
| 113 |
-
div.className = 'bubble user';
|
| 114 |
-
div.textContent = text;
|
| 115 |
-
log.appendChild(div);
|
| 116 |
-
log.scrollTop = log.scrollHeight;
|
| 117 |
-
}
|
| 118 |
-
function cardBot(obj) {
|
| 119 |
-
const wrap = document.createElement('div');
|
| 120 |
-
wrap.className = 'bubble bot';
|
| 121 |
-
const pre = document.createElement('pre');
|
| 122 |
-
pre.textContent = typeof obj === 'string' ? obj : JSON.stringify(obj, null, 2);
|
| 123 |
-
wrap.appendChild(pre);
|
| 124 |
-
log.appendChild(wrap);
|
| 125 |
-
log.scrollTop = log.scrollHeight;
|
| 126 |
-
}
|
| 127 |
-
function setStatus(ok, text) {
|
| 128 |
-
dot.classList.toggle('ok', !!ok);
|
| 129 |
-
dot.classList.toggle('bad', ok === false);
|
| 130 |
-
statusText.textContent = text || (ok ? 'OK' : 'Error');
|
| 131 |
-
}
|
| 132 |
-
async function api(path, init) {
|
| 133 |
-
const base = backendInput.value.trim().replace(/\/$/, '');
|
| 134 |
-
const url = base + path;
|
| 135 |
-
const resp = await fetch(url, init);
|
| 136 |
-
if (!resp.ok) {
|
| 137 |
-
let t = await resp.text().catch(() => '');
|
| 138 |
-
throw new Error(`HTTP ${resp.status} ${resp.statusText} — ${t}`);
|
| 139 |
-
}
|
| 140 |
-
const contentType = resp.headers.get('content-type') || '';
|
| 141 |
-
if (contentType.includes('application/json')) return resp.json();
|
| 142 |
-
return resp.text();
|
| 143 |
-
}
|
| 144 |
-
|
| 145 |
-
async function checkHealth() {
|
| 146 |
-
try {
|
| 147 |
-
const h = await api('/health', { method: 'GET' });
|
| 148 |
-
setStatus(true, 'Healthy');
|
| 149 |
-
cardBot({ health: h });
|
| 150 |
-
} catch (e) {
|
| 151 |
-
setStatus(false, String(e.message || e));
|
| 152 |
-
cardBot({ error: String(e.message || e) });
|
| 153 |
-
}
|
| 154 |
-
}
|
| 155 |
-
|
| 156 |
-
async function sendMessage() {
|
| 157 |
-
const text = msgInput.value.trim();
|
| 158 |
-
if (!text) return;
|
| 159 |
-
cardUser(text);
|
| 160 |
-
msgInput.value = '';
|
| 161 |
-
try {
|
| 162 |
-
const data = await api('/chatbot/message', {
|
| 163 |
-
method: 'POST',
|
| 164 |
-
headers: { 'Content-Type': 'application/json' },
|
| 165 |
-
body: JSON.stringify({ message: text })
|
| 166 |
-
});
|
| 167 |
-
cardBot(data);
|
| 168 |
-
} catch (e) {
|
| 169 |
-
cardBot({ error: String(e.message || e) });
|
| 170 |
-
}
|
| 171 |
-
}
|
| 172 |
-
|
| 173 |
-
async function showCapabilities() {
|
| 174 |
-
try {
|
| 175 |
-
// Prefer API if available; if 404, fall back to library-like prompt.
|
| 176 |
-
const data = await api('/chatbot/message', {
|
| 177 |
-
method: 'POST',
|
| 178 |
-
headers: { 'Content-Type': 'application/json' },
|
| 179 |
-
body: JSON.stringify({ message: 'help' })
|
| 180 |
-
});
|
| 181 |
-
cardBot(data);
|
| 182 |
-
} catch (e) {
|
| 183 |
-
cardBot({ capabilities: ['text-input','sentiment-analysis','help'], note: 'API help failed, showing defaults', error: String(e.message || e) });
|
| 184 |
-
}
|
| 185 |
-
}
|
| 186 |
-
|
| 187 |
-
// Wire up
|
| 188 |
-
backendInput.value = getBackendUrl();
|
| 189 |
-
saveBtn.onclick = () => { setBackendUrl(backendInput.value.trim()); setStatus(null, 'Saved'); };
|
| 190 |
-
sendBtn.onclick = sendMessage;
|
| 191 |
-
msgInput.addEventListener('keydown', (ev) => { if (ev.key === 'Enter') sendMessage(); });
|
| 192 |
-
capBtn.onclick = showCapabilities;
|
| 193 |
-
healthBtn.onclick = checkHealth;
|
| 194 |
-
clearBtn.onclick = () => { log.innerHTML = ''; setStatus(null, 'Idle'); };
|
| 195 |
-
|
| 196 |
-
// Initial health ping
|
| 197 |
-
checkHealth();
|
| 198 |
-
</script>
|
| 199 |
-
</body>
|
| 200 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
full/frontend/chat_console.html
DELETED
|
@@ -1,77 +0,0 @@
|
|
| 1 |
-
<!doctype html>
|
| 2 |
-
<html lang="en">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="utf-8" />
|
| 5 |
-
<title>Console Chat Tester</title>
|
| 6 |
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
| 7 |
-
<style>
|
| 8 |
-
body{ font-family: ui-sans-serif, system-ui, Arial; margin:20px; }
|
| 9 |
-
.row{ display:flex; gap:8px; align-items:center; margin:6px 0; }
|
| 10 |
-
input[type=text]{ flex:1; padding:8px; }
|
| 11 |
-
button{ padding:8px 10px; }
|
| 12 |
-
pre{ background:#0b1020; color:#d6e7ff; padding:10px; height:320px; overflow:auto; }
|
| 13 |
-
.chip{ display:inline-block; padding:3px 8px; background:#eef; border-radius:12px; margin-left:8px; }
|
| 14 |
-
</style>
|
| 15 |
-
</head>
|
| 16 |
-
<body>
|
| 17 |
-
<h2>AgenticCore Console</h2>
|
| 18 |
-
|
| 19 |
-
<div class="row">
|
| 20 |
-
<label>Backend</label>
|
| 21 |
-
<input id="base" type="text" value="http://127.0.0.1:8000" />
|
| 22 |
-
<button id="btnHealth">Health</button>
|
| 23 |
-
<button id="btnRoutes">Routes</button>
|
| 24 |
-
</div>
|
| 25 |
-
|
| 26 |
-
<div class="row">
|
| 27 |
-
<input id="msg" type="text" placeholder="Say something…" />
|
| 28 |
-
<button id="btnSend">POST /chatbot/message</button>
|
| 29 |
-
</div>
|
| 30 |
-
|
| 31 |
-
<div>
|
| 32 |
-
<span>Mode:</span>
|
| 33 |
-
<span id="mode" class="chip">API</span>
|
| 34 |
-
</div>
|
| 35 |
-
|
| 36 |
-
<pre id="out"></pre>
|
| 37 |
-
|
| 38 |
-
<script>
|
| 39 |
-
const $ = id => document.getElementById(id);
|
| 40 |
-
const out = $("out");
|
| 41 |
-
function print(o){ out.textContent += (typeof o==="string" ? o : JSON.stringify(o,null,2)) + "\n"; out.scrollTop = out.scrollHeight; }
|
| 42 |
-
function join(b, p){ return b.replace(/\/+$/,"") + p; }
|
| 43 |
-
|
| 44 |
-
async function health(){
|
| 45 |
-
try{
|
| 46 |
-
const r = await fetch(join($("base").value, "/health"));
|
| 47 |
-
print(await r.json());
|
| 48 |
-
}catch(e){ print("health error: " + e); }
|
| 49 |
-
}
|
| 50 |
-
async function routes(){
|
| 51 |
-
try{
|
| 52 |
-
const r = await fetch(join($("base").value, "/openapi.json"));
|
| 53 |
-
const j = await r.json();
|
| 54 |
-
print({ routes: Object.keys(j.paths) });
|
| 55 |
-
}catch(e){ print("routes error: " + e); }
|
| 56 |
-
}
|
| 57 |
-
async function send(){
|
| 58 |
-
const text = $("msg").value.trim();
|
| 59 |
-
if(!text){ print("enter a message first"); return; }
|
| 60 |
-
try{
|
| 61 |
-
const r = await fetch(join($("base").value, "/chatbot/message"), {
|
| 62 |
-
method:"POST",
|
| 63 |
-
headers:{ "Content-Type":"application/json" },
|
| 64 |
-
body: JSON.stringify({ message: text })
|
| 65 |
-
});
|
| 66 |
-
print(await r.json());
|
| 67 |
-
}catch(e){ print("send error: " + e); }
|
| 68 |
-
}
|
| 69 |
-
$("btnHealth").onclick = health;
|
| 70 |
-
$("btnRoutes").onclick = routes;
|
| 71 |
-
$("btnSend").onclick = send;
|
| 72 |
-
|
| 73 |
-
// boot
|
| 74 |
-
health();
|
| 75 |
-
</script>
|
| 76 |
-
</body>
|
| 77 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
full/frontend/chat_minimal.html
DELETED
|
@@ -1,89 +0,0 @@
|
|
| 1 |
-
<!doctype html>
|
| 2 |
-
<html lang="en">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="utf-8" />
|
| 5 |
-
<title>Minimal Chat Tester</title>
|
| 6 |
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
| 7 |
-
<style>
|
| 8 |
-
body { font-family: system-ui, Arial, sans-serif; margin: 24px; }
|
| 9 |
-
.row { display:flex; gap:8px; align-items:center; margin-bottom:8px; }
|
| 10 |
-
input[type=text]{ width:420px; padding:8px; }
|
| 11 |
-
textarea{ width:100%; height:240px; padding:8px; }
|
| 12 |
-
button{ padding:8px 12px; }
|
| 13 |
-
.ok{ color:#1a7f37; }
|
| 14 |
-
.warn{ color:#b54708; }
|
| 15 |
-
.err{ color:#b42318; }
|
| 16 |
-
</style>
|
| 17 |
-
</head>
|
| 18 |
-
<body>
|
| 19 |
-
<h2>Minimal Chat Tester → FastAPI /chatbot/message</h2>
|
| 20 |
-
|
| 21 |
-
<div class="row">
|
| 22 |
-
<label>Backend URL:</label>
|
| 23 |
-
<input id="base" type="text" value="http://127.0.0.1:8000" />
|
| 24 |
-
<button id="btnHealth">Health</button>
|
| 25 |
-
<button id="btnCaps">Capabilities</button>
|
| 26 |
-
</div>
|
| 27 |
-
|
| 28 |
-
<div class="row">
|
| 29 |
-
<input id="msg" type="text" placeholder="Type a message…" />
|
| 30 |
-
<button id="btnSend">Send</button>
|
| 31 |
-
</div>
|
| 32 |
-
|
| 33 |
-
<p id="status"></p>
|
| 34 |
-
<textarea id="log" readonly></textarea>
|
| 35 |
-
|
| 36 |
-
<script>
|
| 37 |
-
const $ = id => document.getElementById(id);
|
| 38 |
-
const log = (o, cls="") => {
|
| 39 |
-
const line = (typeof o === "string") ? o : JSON.stringify(o, null, 2);
|
| 40 |
-
$("log").value += line + "\n";
|
| 41 |
-
$("log").scrollTop = $("log").scrollHeight;
|
| 42 |
-
if(cls) { $("status").className = cls; $("status").textContent = line; }
|
| 43 |
-
};
|
| 44 |
-
|
| 45 |
-
function urlJoin(base, path) {
|
| 46 |
-
return base.replace(/\/+$/,"") + path;
|
| 47 |
-
}
|
| 48 |
-
|
| 49 |
-
async function health() {
|
| 50 |
-
try {
|
| 51 |
-
const r = await fetch(urlJoin($("base").value, "/health"));
|
| 52 |
-
const j = await r.json();
|
| 53 |
-
log(j, "ok");
|
| 54 |
-
} catch (e) { log("Health error: " + e, "err"); }
|
| 55 |
-
}
|
| 56 |
-
|
| 57 |
-
async function caps() {
|
| 58 |
-
try {
|
| 59 |
-
// Prefer library-like caps endpoint if you expose one; otherwise call /openapi.json for visibility
|
| 60 |
-
const r = await fetch(urlJoin($("base").value, "/openapi.json"));
|
| 61 |
-
const j = await r.json();
|
| 62 |
-
log({paths: Object.keys(j.paths).slice(0,20)}, "ok");
|
| 63 |
-
} catch (e) { log("Caps error: " + e, "err"); }
|
| 64 |
-
}
|
| 65 |
-
|
| 66 |
-
async function sendMsg() {
|
| 67 |
-
const text = $("msg").value.trim();
|
| 68 |
-
if(!text) { log("Please type a message.", "warn"); return; }
|
| 69 |
-
try {
|
| 70 |
-
const r = await fetch(urlJoin($("base").value, "/chatbot/message"), {
|
| 71 |
-
method:"POST",
|
| 72 |
-
headers:{ "Content-Type":"application/json" },
|
| 73 |
-
body: JSON.stringify({ message: text })
|
| 74 |
-
});
|
| 75 |
-
if(!r.ok) throw new Error(`${r.status} ${r.statusText}`);
|
| 76 |
-
const j = await r.json();
|
| 77 |
-
log(j, "ok");
|
| 78 |
-
} catch (e) { log("Send error: " + e, "err"); }
|
| 79 |
-
}
|
| 80 |
-
|
| 81 |
-
$("btnHealth").onclick = health;
|
| 82 |
-
$("btnCaps").onclick = caps;
|
| 83 |
-
$("btnSend").onclick = sendMsg;
|
| 84 |
-
|
| 85 |
-
// Warmup
|
| 86 |
-
health();
|
| 87 |
-
</script>
|
| 88 |
-
</body>
|
| 89 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
full/requirements.txt
DELETED
|
@@ -1,14 +0,0 @@
|
|
| 1 |
-
# Web
|
| 2 |
-
fastapi>=0.112
|
| 3 |
-
uvicorn[standard]>=0.30
|
| 4 |
-
pydantic>=2.7
|
| 5 |
-
|
| 6 |
-
# HTTP
|
| 7 |
-
requests>=2.32
|
| 8 |
-
|
| 9 |
-
# Optional Bot Framework demo
|
| 10 |
-
aiohttp>=3.9
|
| 11 |
-
botbuilder-core>=4.15
|
| 12 |
-
|
| 13 |
-
# Dev
|
| 14 |
-
pytest>=8.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
full/tests/__init__.py
DELETED
|
File without changes
|
full/tests/test_routes.py
DELETED
|
@@ -1,6 +0,0 @@
|
|
| 1 |
-
def test_routes_mount():
|
| 2 |
-
from backend.app.main import create_app
|
| 3 |
-
app = create_app()
|
| 4 |
-
paths = [getattr(r, "path", "") for r in app.routes]
|
| 5 |
-
assert "/chatbot/message" in paths
|
| 6 |
-
assert "/health" in paths
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
full/tools/__init__.py
DELETED
|
File without changes
|
full/tools/quick_sanity.py
DELETED
|
@@ -1,13 +0,0 @@
|
|
| 1 |
-
# tools/quick_sanity.py
|
| 2 |
-
"""
|
| 3 |
-
Tiny sanity test for MBF helpers. Run from repo root or set PYTHONPATH.
|
| 4 |
-
"""
|
| 5 |
-
import sys, os
|
| 6 |
-
# Add repo root so 'mbf_bot' is importable if running directly
|
| 7 |
-
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
| 8 |
-
|
| 9 |
-
from mbf_bot.skills import reverse_text, capabilities, normalize
|
| 10 |
-
|
| 11 |
-
print("caps:", capabilities())
|
| 12 |
-
print("reverse:", reverse_text("hello"))
|
| 13 |
-
print("cmd:", normalize(" Help "))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
full/tools/smoke_test.py
DELETED
|
@@ -1,11 +0,0 @@
|
|
| 1 |
-
import os, json, requests
|
| 2 |
-
from agenticcore.chatbot.services import ChatBot
|
| 3 |
-
|
| 4 |
-
def p(title, data): print(f"\n== {title} ==\n{json.dumps(data, indent=2)}")
|
| 5 |
-
|
| 6 |
-
bot = ChatBot()
|
| 7 |
-
p("Lib/Direct", bot.reply("I really love this"))
|
| 8 |
-
|
| 9 |
-
url = os.getenv("BACKEND_URL", "http://127.0.0.1:8000")
|
| 10 |
-
r = requests.get(f"{url}/health"); p("API/Health", r.json())
|
| 11 |
-
r = requests.post(f"{url}/chatbot/message", json={"message":"api path test"}); p("API/Chat", r.json())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|