ruslanmv commited on
Commit
d62044b
·
1 Parent(s): bbd26a0

Read from .env

Browse files
app/main.py CHANGED
@@ -9,7 +9,62 @@ from typing import Any, Dict
9
  from fastapi import FastAPI
10
  from fastapi.responses import RedirectResponse
11
 
12
- # --- Middlewares ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  # Prefer the canonical package name; if your repo uses "middlewares/", this tries both.
14
  try:
15
  from .middleware import attach_middlewares # singular
@@ -22,7 +77,9 @@ except Exception:
22
  "attach_middlewares not found; continuing without custom middlewares."
23
  )
24
 
25
- # --- Routers ---
 
 
26
  from .routers import health, plan, chat
27
 
28
  # Optional UI (Home/Chat/Dev). If missing, we gracefully fall back to a JSON root.
@@ -32,6 +89,7 @@ try:
32
  except Exception: # pragma: no cover
33
  HAS_UI = False
34
 
 
35
  TAGS_METADATA = [
36
  {"name": "Health", "description": "Liveness / readiness probes and basic service metadata."},
37
  {"name": "Planning", "description": "AI plan generation for Matrix Guardian (/v1/plan)."},
@@ -39,12 +97,19 @@ TAGS_METADATA = [
39
  {"name": "UI", "description": "Minimal web UI (Home, Chat, Dev) if enabled."},
40
  ]
41
 
 
42
  @asynccontextmanager
43
  async def lifespan(app: FastAPI):
44
  app.state.started_at = time.time()
45
  app.state.version = os.getenv("APP_VERSION", "1.0.0")
 
 
 
46
  logging.getLogger("uvicorn.error").info(
47
- "matrix-ai starting (version=%s, port=%s)", app.state.version, os.getenv("PORT", "7860")
 
 
 
48
  )
49
  try:
50
  yield
@@ -54,6 +119,7 @@ async def lifespan(app: FastAPI):
54
  "matrix-ai shutting down (uptime=%.2fs)", uptime
55
  )
56
 
 
57
  def create_app() -> FastAPI:
58
  app = FastAPI(
59
  title="matrix-ai",
@@ -94,4 +160,5 @@ def create_app() -> FastAPI:
94
 
95
  return app
96
 
 
97
  app = create_app()
 
9
  from fastapi import FastAPI
10
  from fastapi.responses import RedirectResponse
11
 
12
+ # -----------------------------------------------------------------------------
13
+ # Early: load .env (so HF_TOKEN, ADMIN_TOKEN, etc. are available locally)
14
+ # -----------------------------------------------------------------------------
15
+ def _load_env_file(paths: list[str]) -> None:
16
+ """Load environment variables from the first existing path in `paths`.
17
+ Prefer python-dotenv if present; otherwise use a tiny fallback parser."""
18
+ logger = logging.getLogger("uvicorn.error")
19
+
20
+ # 1) Try python-dotenv (best)
21
+ try:
22
+ from dotenv import load_dotenv # type: ignore
23
+ for p in paths:
24
+ if os.path.exists(p):
25
+ load_dotenv(dotenv_path=p, override=False)
26
+ logger.info("Loaded environment from %s", p)
27
+ return
28
+ logger.info("No .env file found in %s (skipping)", paths)
29
+ return
30
+ except Exception:
31
+ # 2) Fallback: simple parser
32
+ for p in paths:
33
+ if not os.path.exists(p):
34
+ continue
35
+ try:
36
+ with open(p, "r", encoding="utf-8") as f:
37
+ for raw in f:
38
+ line = raw.strip()
39
+ if not line or line.startswith("#"):
40
+ continue
41
+ if line.startswith("export "):
42
+ line = line[len("export ") :].strip()
43
+ if "=" not in line:
44
+ continue
45
+ key, val = line.split("=", 1)
46
+ key, val = key.strip(), val.strip()
47
+ # strip optional quotes
48
+ if (val.startswith('"') and val.endswith('"')) or (
49
+ val.startswith("'") and val.endswith("'")
50
+ ):
51
+ val = val[1:-1]
52
+ # do not clobber existing env (Space Secrets)
53
+ os.environ.setdefault(key, val)
54
+ logger.info("Loaded environment from %s (fallback parser)", p)
55
+ return
56
+ except Exception as e:
57
+ logger.warning("Failed loading env from %s: %s", p, e)
58
+
59
+ logger.info("No .env loaded (none found / parsers failed)")
60
+
61
+ # Try typical locations for local dev. HF Spaces will ignore this and use Secrets.
62
+ _load_env_file([".env", "configs/.env", ".env.local", "configs/.env.local"])
63
+
64
+
65
+ # -----------------------------------------------------------------------------
66
+ # Middlewares
67
+ # -----------------------------------------------------------------------------
68
  # Prefer the canonical package name; if your repo uses "middlewares/", this tries both.
69
  try:
70
  from .middleware import attach_middlewares # singular
 
77
  "attach_middlewares not found; continuing without custom middlewares."
78
  )
79
 
80
+ # -----------------------------------------------------------------------------
81
+ # Routers
82
+ # -----------------------------------------------------------------------------
83
  from .routers import health, plan, chat
84
 
85
  # Optional UI (Home/Chat/Dev). If missing, we gracefully fall back to a JSON root.
 
89
  except Exception: # pragma: no cover
90
  HAS_UI = False
91
 
92
+
93
  TAGS_METADATA = [
94
  {"name": "Health", "description": "Liveness / readiness probes and basic service metadata."},
95
  {"name": "Planning", "description": "AI plan generation for Matrix Guardian (/v1/plan)."},
 
97
  {"name": "UI", "description": "Minimal web UI (Home, Chat, Dev) if enabled."},
98
  ]
99
 
100
+
101
  @asynccontextmanager
102
  async def lifespan(app: FastAPI):
103
  app.state.started_at = time.time()
104
  app.state.version = os.getenv("APP_VERSION", "1.0.0")
105
+
106
+ # Minimal diagnostics; HF_TOKEN presence matters for inference
107
+ hf_token_present = bool(os.getenv("HF_TOKEN"))
108
  logging.getLogger("uvicorn.error").info(
109
+ "matrix-ai starting (version=%s, port=%s, hf_token_present=%s)",
110
+ app.state.version,
111
+ os.getenv("PORT", "7860"),
112
+ "yes" if hf_token_present else "no",
113
  )
114
  try:
115
  yield
 
119
  "matrix-ai shutting down (uptime=%.2fs)", uptime
120
  )
121
 
122
+
123
  def create_app() -> FastAPI:
124
  app = FastAPI(
125
  title="matrix-ai",
 
160
 
161
  return app
162
 
163
+
164
  app = create_app()
app/routers/chat.py CHANGED
@@ -1,18 +1,58 @@
1
- from fastapi import APIRouter, Depends, HTTPException
 
 
 
2
  from ..deps import get_settings
3
  from ..core.config import Settings
4
- from ..core.schema import ChatRequest, ChatResponse
5
- from ..services.chat_service import chat_answer
6
 
7
  router = APIRouter()
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  @router.post("/chat", response_model=ChatResponse)
10
- async def v1_chat(
11
- req: ChatRequest,
12
- settings: Settings = Depends(get_settings)
13
- ):
14
- """Answers questions about the MatrixHub ecosystem using RAG."""
15
  try:
16
- return await chat_answer(req, settings=settings)
17
- except Exception as e:
18
- raise HTTPException(status_code=500, detail=f"Failed to process chat request: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app/routers/chat.py
2
+ from fastapi import APIRouter, Depends, HTTPException, Query
3
+ from pydantic import BaseModel
4
+ from typing import List, Optional
5
  from ..deps import get_settings
6
  from ..core.config import Settings
7
+ from ..services.chat_service import ChatService
 
8
 
9
  router = APIRouter()
10
 
11
+ class ChatMessage(BaseModel):
12
+ role: str
13
+ content: str
14
+
15
+ class ChatRequest(BaseModel):
16
+ # accept several shapes so UI/clients don't 422:
17
+ query: Optional[str] = None
18
+ question: Optional[str] = None
19
+ prompt: Optional[str] = None
20
+ messages: Optional[List[ChatMessage]] = None
21
+
22
+ def as_text(self) -> str:
23
+ if self.query:
24
+ return self.query
25
+ if self.question:
26
+ return self.question
27
+ if self.prompt:
28
+ return self.prompt
29
+ if self.messages:
30
+ # prefer last user message
31
+ for m in reversed(self.messages):
32
+ if m.role.lower() == "user":
33
+ return m.content
34
+ # fallback to last message
35
+ if self.messages:
36
+ return self.messages[-1].content
37
+ raise ValueError("Body must include 'query'/'question'/'prompt' or 'messages'")
38
+
39
+ class ChatResponse(BaseModel):
40
+ answer: str
41
+
42
  @router.post("/chat", response_model=ChatResponse)
43
+ async def chat(req: ChatRequest, settings: Settings = Depends(get_settings)):
 
 
 
 
44
  try:
45
+ text = req.as_text()
46
+ except ValueError as e:
47
+ raise HTTPException(status_code=422, detail=str(e))
48
+ svc = ChatService(settings)
49
+ answer = await svc.answer(text)
50
+ return ChatResponse(answer=answer)
51
+
52
+ # Handy for curl/browser tests:
53
+ # GET /v1/chat?query=hello
54
+ @router.get("/chat", response_model=ChatResponse)
55
+ async def chat_get(query: str = Query(...), settings: Settings = Depends(get_settings)):
56
+ svc = ChatService(settings)
57
+ answer = await svc.answer(query)
58
+ return ChatResponse(answer=answer)
app/services/chat_service.py CHANGED
@@ -1,10 +1,23 @@
1
- # Placeholder for Stage-2 RAG chat service
2
- from ..core.schema import ChatRequest, ChatResponse
3
  from ..core.config import Settings
 
4
 
5
- async def chat_answer(req: ChatRequest, settings: Settings) -> ChatResponse:
6
- """Placeholder chat function."""
7
- return ChatResponse(
8
- answer="The RAG chat service is not yet enabled in Stage-1.",
9
- sources=[]
10
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app/services/chat_service.py
2
+ from __future__ import annotations
3
  from ..core.config import Settings
4
+ from ..core.inference.client import HFClient
5
 
6
+ SYSTEM_PROMPT = (
7
+ "You are MATRIX-AI, a concise, helpful assistant for the Matrix EcoSystem. "
8
+ "Answer clearly and briefly. If unsure, say so."
9
+ )
10
+
11
+ class ChatService:
12
+ def __init__(self, settings: Settings):
13
+ self.settings = settings
14
+ self.client = HFClient(model=settings.model.name)
15
+
16
+ async def answer(self, query: str) -> str:
17
+ prompt = f"{SYSTEM_PROMPT}\n\nUser: {query}\nAssistant:"
18
+ text = await self.client.generate(
19
+ prompt=prompt,
20
+ max_new_tokens=self.settings.model.max_new_tokens,
21
+ temperature=self.settings.model.temperature,
22
+ )
23
+ return (text or "").strip()
app/templates/base.html CHANGED
@@ -156,6 +156,8 @@
156
  <a href="/chat">Chat</a>
157
  <a href="/dev">Dev</a>
158
  <a href="/docs" target="_blank" rel="noreferrer">API Docs</a>
 
 
159
  </nav>
160
  </header>
161
 
 
156
  <a href="/chat">Chat</a>
157
  <a href="/dev">Dev</a>
158
  <a href="/docs" target="_blank" rel="noreferrer">API Docs</a>
159
+ <a href="https://github.com/agent-matrix/matrix-ai" target="_blank" rel="noreferrer" title="Give me a star on GitHub!">GitHub</a>
160
+
161
  </nav>
162
  </header>
163