Testys commited on
Commit
9111274
·
1 Parent(s): 5889992

FEAT: Publishing to HuggingFace from GitHub

Browse files
Dockerfile ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dockerfile for FastAPI application (with Telegram bot and Redis) deployed on Hugging Face Spaces
2
+ FROM python:3.11-slim
3
+
4
+ # Install Redis server (requires root privileges)
5
+ USER root
6
+ RUN apt-get update && \
7
+ apt-get install -y redis-server && \
8
+ apt-get clean && \
9
+ rm -rf /var/lib/apt/lists/*
10
+
11
+ # Create and switch to a non-root user for security
12
+ RUN useradd -m -u 1000 user
13
+ USER user
14
+
15
+ # Ensure local binaries are in PATH and disable output buffering
16
+ ENV PATH="/home/user/.local/bin:$PATH" \
17
+ PYTHONUNBUFFERED=1
18
+
19
+ # Set the working directory
20
+ WORKDIR /app
21
+
22
+ # Install Python dependencies
23
+ COPY --chown=user ./requirements.txt requirements.txt
24
+ RUN pip install --no-cache-dir --upgrade -r requirements.txt
25
+
26
+ # Copy all application code into the container
27
+ COPY --chown=user . /app
28
+
29
+ # Copy the entrypoint script into the container and make it executable
30
+ COPY --chown=user entrypoint.sh /app/entrypoint.sh
31
+ RUN chmod +x /app/entrypoint.sh
32
+
33
+ # Expose the port used by the FastAPI app (Hugging Face Spaces expects this)
34
+ EXPOSE 7860
35
+
36
+ # Start everything via the entrypoint script
37
+ CMD ["/app/entrypoint.sh"]
entrypoint.sh ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ set -e
2
+
3
+ echo "Starting Redis server..."
4
+ # Start Redis in the background
5
+ redis-server &
6
+
7
+ echo "Starting Telegram bot..."
8
+ # Start the Telegram bot in the background (ensure __main__ in bot.py is set to run the bot)
9
+ python -m src.tele_bot.bot &
10
+
11
+ echo "Starting FastAPI application..."
12
+ # Start FastAPI with Uvicorn in the foreground so the container keeps running.
13
+ exec uvicorn app.src.api:app --host 0.0.0.0 --port 7860 --workers 1
src/api.py CHANGED
@@ -9,15 +9,10 @@ import uvicorn
9
  from multiprocessing import Process
10
 
11
  from src.llm.routes import router as conversation_router
12
- from src.tele_bot.bot import main as run_telegram_bot
13
  from src.llm.core.config import settings
14
  from src.llm.agents.conversation_agent import ConversationAgent
15
 
16
- def on_startup():
17
- global conversation_agent
18
- conversation_agent = ConversationAgent()
19
 
20
-
21
  app = FastAPI(
22
  title="TheryAI API",
23
  description="API for TheryAI",
@@ -26,7 +21,6 @@ app = FastAPI(
26
  redoc_url="/redoc",
27
  openapi_url="/openapi.json",
28
  debug=True,
29
- on_startup=[on_startup]
30
  )
31
 
32
 
@@ -63,7 +57,7 @@ thread.daemon = True
63
  thread.start()
64
 
65
 
66
- def run_bot():
67
  uvicorn.run(
68
  "src.api:app",
69
  host="0.0.0.0",
@@ -71,18 +65,6 @@ def run_bot():
71
  log_level="info",
72
  reload=True,
73
  )
74
-
75
- def run_telegram_bot():
76
- asyncio(run_telegram_bot())
77
-
78
-
79
- if __name__ == "__main__":
80
- fastapi_process = Process(target=run_bot)
81
- telegram_process = Process(target=run_telegram_bot)
82
 
83
- fastapi_process.start()
84
- telegram_process.start()
85
-
86
- fastapi_process.join()
87
- telegram_process.join()
88
-
 
9
  from multiprocessing import Process
10
 
11
  from src.llm.routes import router as conversation_router
 
12
  from src.llm.core.config import settings
13
  from src.llm.agents.conversation_agent import ConversationAgent
14
 
 
 
 
15
 
 
16
  app = FastAPI(
17
  title="TheryAI API",
18
  description="API for TheryAI",
 
21
  redoc_url="/redoc",
22
  openapi_url="/openapi.json",
23
  debug=True,
 
24
  )
25
 
26
 
 
57
  thread.start()
58
 
59
 
60
+ def run_fastapi():
61
  uvicorn.run(
62
  "src.api:app",
63
  host="0.0.0.0",
 
65
  log_level="info",
66
  reload=True,
67
  )
 
 
 
 
 
 
 
 
68
 
69
+ if __name__ == "__main__":
70
+ run_fastapi()
 
 
 
 
src/llm/agents/context_agent.py CHANGED
@@ -56,8 +56,36 @@ class ContextAgent(BaseAgent):
56
  return "Vector search unavailable"
57
 
58
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  async def process_async(self, query: str) -> ContextInfo:
60
- return await asyncio.get_event_loop().run_in_executor(
61
- None,
62
- lambda: self.process(query)
63
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  return "Vector search unavailable"
57
 
58
 
59
+ # Updated async web search handling
60
+ async def _get_web_context_async(self, query: str) -> str:
61
+ """Async version of web context retrieval"""
62
+ try:
63
+ loop = asyncio.get_event_loop()
64
+ results = await loop.run_in_executor(
65
+ None,
66
+ lambda: self.web_search.invoke(query)
67
+ )
68
+ return "\n".join([res["content"] for res in results])
69
+ except Exception as e:
70
+ self._log_action(action="web_search_error", metadata={"error": str(e)}, level=logging.ERROR)
71
+ return "Web search unavailable"
72
+
73
  async def process_async(self, query: str) -> ContextInfo:
74
+ """Async version with parallel context gathering"""
75
+ web_task = self._get_web_context_async(query)
76
+ vector_task = asyncio.get_event_loop().run_in_executor(
77
+ None,
78
+ lambda: self._get_vector_context(query)
79
+ )
80
+
81
+ web_context, vector_context = await asyncio.gather(web_task, vector_task)
82
+
83
+ combined_context = f"{web_context}\n\n{vector_context}"
84
+
85
+ self._log_action(action="context_gathered", metadata={"query": query}, level=logging.INFO)
86
+ return ContextInfo(
87
+ query=query,
88
+ web_context=web_context,
89
+ vector_context=vector_context,
90
+ combined_context=combined_context,
91
+ )
src/llm/agents/conversation_agent.py CHANGED
@@ -186,7 +186,8 @@ class ConversationAgent(BaseAgent):
186
 
187
  You: "I can sense your frustration. Can you tell me more about what's been going on, and how you've been coping with these challenges?"
188
 
189
- Please respond as a therapist would, using the guidelines and attributes above. Make sure your responses are not overly long. BE NATURAL, SUUPPORTIVE, AND EMPHATIZING
 
190
 
191
  """
192
 
 
186
 
187
  You: "I can sense your frustration. Can you tell me more about what's been going on, and how you've been coping with these challenges?"
188
 
189
+ ONLY USE CONTEXT AND EMOTIONAL ANALYSIS IF THEY ALIGN WITH YOUR THOUGHTS ON THE USER'S QUERY, DO NOT REPLY WITH CONTEXT IF THE CONTEXT DOESN'T HELP THE USER.
190
+ Please respond as a therapist would, using the guidelines and attributes above. Make sure your responses are not overly long. BE NATURAL, SUUPPORTIVE, AND EMPHATIZING.
191
 
192
  """
193
 
src/llm/core/config.py CHANGED
@@ -7,11 +7,11 @@ load_dotenv()
7
  class Settings(BaseSettings):
8
  GOOGLE_API_KEY: str = os.getenv("GOOGLE_API_KEY")
9
  TAVILY_API_KEY: str = os.getenv("TAVILY_API_KEY")
10
- REDIS_HOST: str = "localhost"
11
- REDIS_PORT: int = 6379
12
- REDIS_DB: int = 0
13
- REDIS_USER: str = "redis"
14
- REDIS_PASSWORD: str = ""
15
  SESSION_TTL: int = 86400
16
  MAX_RETRIES: int = 3
17
  MAX_TOKENS: int = 200
 
7
  class Settings(BaseSettings):
8
  GOOGLE_API_KEY: str = os.getenv("GOOGLE_API_KEY")
9
  TAVILY_API_KEY: str = os.getenv("TAVILY_API_KEY")
10
+ REDIS_HOST: str = os.getenv("REDIS_HOST")
11
+ REDIS_PORT: int = os.getenv("REDIS_PORT")
12
+ # REDIS_DB: int = 0
13
+ REDIS_USERNAME: str = os.getenv("REDIS_USERNAME")
14
+ REDIS_PASSWORD: str = os.getenv("REDIS_PASSWORD")
15
  SESSION_TTL: int = 86400
16
  MAX_RETRIES: int = 3
17
  MAX_TOKENS: int = 200
src/llm/memory/redis_connection.py CHANGED
@@ -17,7 +17,7 @@ class RedisConnection:
17
  self.redis = redis.Redis(
18
  host=settings.REDIS_HOST,
19
  port=settings.REDIS_PORT,
20
- db=settings.REDIS_DB,
21
  password=settings.REDIS_PASSWORD,
22
  decode_responses=True
23
  )
 
17
  self.redis = redis.Redis(
18
  host=settings.REDIS_HOST,
19
  port=settings.REDIS_PORT,
20
+ username=settings.REDIS_USERNAME,
21
  password=settings.REDIS_PASSWORD,
22
  decode_responses=True
23
  )
src/llm/routes.py CHANGED
@@ -1,28 +1,20 @@
1
- from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks
2
- from fastapi.responses import JSONResponse
3
  from typing import List, Optional
4
  from datetime import datetime
5
-
6
- from src.llm.agents.conversation_agent import ConversationAgent
7
  from src.llm.models.schemas import ConversationResponse, SessionData
8
  from src.llm.utils.logging import TheryBotLogger
9
  from src.llm.memory.history import RedisHistory
10
  from src.llm.memory.memory_manager import RedisMemoryManager
11
  from src.llm.memory.session_manager import SessionManager
12
- from src.llm.core.config import settings
13
 
14
  router = APIRouter(
15
  prefix="/api/v1",
16
- tags=["TheryAI Services"],
17
- responses={
18
- 200: {"description": "Success"},
19
- 400: {"description": "Bad Request"},
20
- 404: {"description": "Not found"},
21
- 500: {"description": "Internal Server Error"}
22
- },
23
  )
24
 
25
- # Initialize managers
26
  session_manager = SessionManager()
27
  memory_manager = RedisMemoryManager()
28
  history = RedisHistory()
@@ -31,76 +23,41 @@ conversation_agent = ConversationAgent()
31
 
32
  @router.post("/users", response_model=dict)
33
  async def create_user():
34
- """Create a new user and return user_id"""
35
  try:
36
  user_id, _ = session_manager.generate_ids()
37
  return {"user_id": user_id}
38
  except Exception as e:
39
- logger.error(f"Error creating user: {str(e)}")
40
- raise HTTPException(status_code=500, detail="Failed to create user")
41
-
42
- @router.get("/users/{user_id}/sessions", response_model=List[dict])
43
- async def get_user_sessions(user_id: str):
44
- """Get all sessions for a user"""
45
- try:
46
- sessions = session_manager.get_user_sessions(user_id)
47
- return sessions
48
- except Exception as e:
49
- logger.error(f"Error fetching sessions for user {user_id}: {str(e)}")
50
- raise HTTPException(status_code=404, detail="User not found")
51
 
52
  @router.post("/sessions", response_model=SessionData)
53
- async def create_session(user_id: Optional[str] = None):
54
- """Create a new session"""
55
- try:
56
- user_id, session_id = session_manager.generate_ids(existing_user_id=user_id)
57
- return SessionData(
58
- user_id=user_id,
59
- session_id=session_id,
60
- is_new_user=(user_id is None),
61
- is_new_session=True
62
- )
63
- except Exception as e:
64
- logger.error(f"Error creating session: {str(e)}")
65
- raise HTTPException(status_code=500, detail="Failed to create session")
66
-
67
- @router.get("/sessions/{session_id}", response_model=SessionData)
68
- async def get_session(session_id: str):
69
- """Get session metadata"""
70
  try:
71
- user_id = session_manager.validate_session(session_id)
72
- if not user_id:
73
- raise HTTPException(status_code=404, detail="Session not found")
74
-
75
  return SessionData(
76
  user_id=user_id,
77
  session_id=session_id,
78
  is_new_user=False,
79
- is_new_session=False
80
  )
81
- except HTTPException as he:
82
- raise he
83
  except Exception as e:
84
- logger.error(f"Error fetching session {session_id}: {str(e)}")
85
- raise HTTPException(status_code=500, detail="Failed to fetch session")
86
 
87
  @router.get("/sessions/{session_id}/messages", response_model=List[ConversationResponse])
88
- async def get_session_messages(
89
- session_id: str,
90
- limit: Optional[int] = 10
91
- ):
92
- """Get messages from a session"""
93
  try:
94
  if not session_manager.validate_session(session_id):
95
- raise HTTPException(status_code=404, detail="Session not found")
96
-
97
  messages = history.get_conversation_history(session_id, limit=limit)
98
  return [msg["response"] for msg in messages]
99
- except HTTPException as he:
100
- raise he
101
  except Exception as e:
102
- logger.error(f"Error fetching messages for session {session_id}: {str(e)}")
103
- raise HTTPException(status_code=500, detail="Failed to fetch messages")
104
 
105
  @router.post("/sessions/{session_id}/messages", response_model=ConversationResponse)
106
  async def create_message(
@@ -108,72 +65,30 @@ async def create_message(
108
  message: str,
109
  background_tasks: BackgroundTasks
110
  ):
111
- """Create a new message in a session"""
112
  try:
113
  user_id = session_manager.validate_session(session_id)
114
  if not user_id:
115
- raise HTTPException(status_code=404, detail="Session not found")
116
-
117
- session_data = SessionData(
118
- user_id=user_id,
119
- session_id=session_id,
120
- is_new_user=False,
121
- is_new_session=False
122
- )
123
-
124
  response = await conversation_agent.process_async(
125
  query=message,
126
- session_data=session_data
 
 
 
 
 
127
  )
128
 
129
- # Store conversation asynchronously
130
  background_tasks.add_task(
131
  memory_manager.store_conversation,
132
  session_id,
133
- str(datetime.now().timestamp()),
134
  response
135
  )
136
 
137
  return response
138
- except HTTPException as he:
139
- raise he
140
- except Exception as e:
141
- logger.error(f"Error processing message in session {session_id}: {str(e)}")
142
- raise HTTPException(status_code=500, detail="Failed to process message")
143
-
144
- @router.get("/sessions/{session_id}/memory", response_model=dict)
145
- async def get_session_memory(session_id: str):
146
- """Get all memory data for a session"""
147
- try:
148
- if not session_manager.validate_session(session_id):
149
- raise HTTPException(status_code=404, detail="Session not found")
150
-
151
- conversations = memory_manager.get_session_conversations(session_id)
152
- emotional_state = memory_manager.get_emotional_state(session_id)
153
-
154
- return {
155
- "conversations": conversations,
156
- "emotional_state": emotional_state
157
- }
158
- except HTTPException as he:
159
- raise he
160
- except Exception as e:
161
- logger.error(f"Error fetching memory for session {session_id}: {str(e)}")
162
- raise HTTPException(status_code=500, detail="Failed to fetch memory")
163
-
164
- @router.delete("/sessions/{session_id}", response_model=dict)
165
- async def end_session(session_id: str):
166
- """End a session and clean up resources"""
167
- try:
168
- if not session_manager.validate_session(session_id):
169
- raise HTTPException(status_code=404, detail="Session not found")
170
-
171
- history.clear_history(session_id)
172
- session_manager.end_session(session_id)
173
-
174
- return {"message": "Session ended successfully"}
175
- except HTTPException as he:
176
- raise he
177
  except Exception as e:
178
- logger.error(f"Error ending session {session_id}: {str(e)}")
179
- raise HTTPException(status_code=500, detail="Failed to end session")
 
1
+ from fastapi import APIRouter, HTTPException, BackgroundTasks
 
2
  from typing import List, Optional
3
  from datetime import datetime
 
 
4
  from src.llm.models.schemas import ConversationResponse, SessionData
5
  from src.llm.utils.logging import TheryBotLogger
6
  from src.llm.memory.history import RedisHistory
7
  from src.llm.memory.memory_manager import RedisMemoryManager
8
  from src.llm.memory.session_manager import SessionManager
9
+ from src.llm.agents.conversation_agent import ConversationAgent
10
 
11
  router = APIRouter(
12
  prefix="/api/v1",
13
+ tags=["TheryAI Core Services"],
14
+ responses={500: {"description": "Internal Server Error"}}
 
 
 
 
 
15
  )
16
 
17
+ # Initialize core components
18
  session_manager = SessionManager()
19
  memory_manager = RedisMemoryManager()
20
  history = RedisHistory()
 
23
 
24
  @router.post("/users", response_model=dict)
25
  async def create_user():
26
+ """Create a new user ID"""
27
  try:
28
  user_id, _ = session_manager.generate_ids()
29
  return {"user_id": user_id}
30
  except Exception as e:
31
+ logger.error(f"User creation failed: {str(e)}")
32
+ raise HTTPException(500, "User creation failed")
 
 
 
 
 
 
 
 
 
 
33
 
34
  @router.post("/sessions", response_model=SessionData)
35
+ async def create_session(user_id: str):
36
+ """Create a new session ID for a user"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  try:
38
+ _, session_id = session_manager.generate_ids(existing_user_id=user_id)
 
 
 
39
  return SessionData(
40
  user_id=user_id,
41
  session_id=session_id,
42
  is_new_user=False,
43
+ is_new_session=True
44
  )
 
 
45
  except Exception as e:
46
+ logger.error(f"Session creation failed: {str(e)}")
47
+ raise HTTPException(500, "Session creation failed")
48
 
49
  @router.get("/sessions/{session_id}/messages", response_model=List[ConversationResponse])
50
+ async def get_messages(session_id: str, limit: int = 50):
51
+ """Get message history for a session"""
 
 
 
52
  try:
53
  if not session_manager.validate_session(session_id):
54
+ raise HTTPException(404, "Session not found")
55
+
56
  messages = history.get_conversation_history(session_id, limit=limit)
57
  return [msg["response"] for msg in messages]
 
 
58
  except Exception as e:
59
+ logger.error(f"Message retrieval failed: {str(e)}")
60
+ raise HTTPException(500, "Message retrieval failed")
61
 
62
  @router.post("/sessions/{session_id}/messages", response_model=ConversationResponse)
63
  async def create_message(
 
65
  message: str,
66
  background_tasks: BackgroundTasks
67
  ):
68
+ """Process and store a new message"""
69
  try:
70
  user_id = session_manager.validate_session(session_id)
71
  if not user_id:
72
+ raise HTTPException(404, "Invalid session")
73
+
 
 
 
 
 
 
 
74
  response = await conversation_agent.process_async(
75
  query=message,
76
+ session_data=SessionData(
77
+ user_id=user_id,
78
+ session_id=session_id,
79
+ is_new_user=False,
80
+ is_new_session=False
81
+ )
82
  )
83
 
 
84
  background_tasks.add_task(
85
  memory_manager.store_conversation,
86
  session_id,
87
+ datetime.now().isoformat(),
88
  response
89
  )
90
 
91
  return response
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  except Exception as e:
93
+ logger.error(f"Message processing failed: {str(e)}")
94
+ raise HTTPException(500, "Message processing failed")
src/main.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from multiprocessing import Process
2
+ from src.api import run_fastapi # Make sure run_fastapi is importable from src/api.py
3
+ from src.tele_bot.bot import main as run_telegram_bot
4
+
5
+ if __name__ == "__main__":
6
+ # Start the Telegram bot in a separate process
7
+ telegram_process = Process(target=run_telegram_bot)
8
+ telegram_process.start()
9
+
10
+ # Start FastAPI (this will block in the main process)
11
+ run_fastapi()
12
+
13
+ # Optionally, wait for the Telegram process to finish
14
+ telegram_process.join()
src/tele_bot/bot.py CHANGED
@@ -47,6 +47,13 @@ async def handle_message(update: Update, context: CallbackContext):
47
  user = update.effective_user
48
  text = update.message.text
49
 
 
 
 
 
 
 
 
50
  # Get or create session
51
  session_data = context.user_data.get('session_data')
52
 
@@ -60,10 +67,7 @@ async def handle_message(update: Update, context: CallbackContext):
60
  context.user_data['session_data'] = response.session_data
61
 
62
  # Send response with typing indicator
63
- await context.bot.send_chat_action(
64
- chat_id=update.effective_chat.id,
65
- action="typing"
66
- )
67
  await update.message.reply_text(response.response)
68
 
69
  except Exception as e:
 
47
  user = update.effective_user
48
  text = update.message.text
49
 
50
+ typing_task = asyncio.create_task(
51
+ context.bot.send_chat_action(
52
+ chat_id=update.effective_chat.id,
53
+ action="typing"
54
+ )
55
+ )
56
+
57
  # Get or create session
58
  session_data = context.user_data.get('session_data')
59
 
 
67
  context.user_data['session_data'] = response.session_data
68
 
69
  # Send response with typing indicator
70
+ await typing_task
 
 
 
71
  await update.message.reply_text(response.response)
72
 
73
  except Exception as e: