mtyrrell commited on
Commit
4d95fe3
Β·
1 Parent(s): f8b24dc

telemetry prelim

Browse files
Files changed (2) hide show
  1. app/main.py +405 -134
  2. requirements.txt +7 -0
app/main.py CHANGED
@@ -1,180 +1,451 @@
 
 
 
 
 
 
1
  import gradio as gr
2
- from gradio_client import Client
 
3
  from langgraph.graph import StateGraph, START, END
4
- from typing import TypedDict, Optional
 
 
 
 
 
 
5
  import io
6
  from PIL import Image
7
- import os
 
 
 
 
 
 
 
8
 
9
- #OPEN QUESTION: SHOULD WE PASS ALL PARAMS FROM THE ORCHESTRATOR TO THE NODES INSTEAD OF SETTING IN EACH MODULE?
10
- HF_TOKEN = os.environ.get("HF_TOKEN")
11
  # Define the state schema
12
  class GraphState(TypedDict):
13
  query: str
14
  context: str
15
  result: str
16
- # Add orchestrator-level parameters (addressing your open question)
17
  reports_filter: str
18
  sources_filter: str
19
  subtype_filter: str
20
  year_filter: str
 
21
 
22
- # node 2: retriever
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  def retrieve_node(state: GraphState) -> GraphState:
24
- client = Client("giz/chatfed_retriever_old", hf_token=HF_TOKEN) # HF repo name
25
- context = client.predict(
26
- query=state["query"],
27
- reports_filter=state.get("reports_filter", ""),
28
- sources_filter=state.get("sources_filter", ""),
29
- subtype_filter=state.get("subtype_filter", ""),
30
- year_filter=state.get("year_filter", ""),
31
- api_name="/retrieve"
32
- )
33
- return {"context": context}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
- # node 3: generator
36
  def generate_node(state: GraphState) -> GraphState:
37
- client = Client("giz/chatfed_generator", hf_token=HF_TOKEN)
38
- result = client.predict(
39
- query=state["query"],
40
- context=state["context"],
41
- api_name="/generate"
42
- )
43
- return {"result": result}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
- # build the graph
46
  workflow = StateGraph(GraphState)
47
-
48
- # Add nodes
49
  workflow.add_node("retrieve", retrieve_node)
50
  workflow.add_node("generate", generate_node)
51
-
52
- # Add edges
53
  workflow.add_edge(START, "retrieve")
54
  workflow.add_edge("retrieve", "generate")
55
  workflow.add_edge("generate", END)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
 
57
- # Compile the graph
58
- graph = workflow.compile()
 
59
 
60
- # Single tool for processing queries
61
- def process_query(
62
  query: str,
63
  reports_filter: str = "",
64
  sources_filter: str = "",
65
  subtype_filter: str = "",
66
  year_filter: str = ""
67
  ) -> str:
68
- """
69
- Execute the ChatFed orchestration pipeline to process a user query.
70
-
71
- This function orchestrates a two-step workflow:
72
- 1. Retrieve relevant context using the ChatFed retriever service with optional filters
73
- 2. Generate a response using the ChatFed generator service with the retrieved context
74
-
75
- Args:
76
- query (str): The user's input query/question to be processed
77
- reports_filter (str, optional): Filter for specific report types. Defaults to "".
78
- sources_filter (str, optional): Filter for specific data sources. Defaults to "".
79
- subtype_filter (str, optional): Filter for document subtypes. Defaults to "".
80
- year_filter (str, optional): Filter for specific years. Defaults to "".
81
-
82
- Returns:
83
- str: The generated response from the ChatFed generator service
84
- """
85
- initial_state = {
86
- "query": query,
87
- "context": "",
88
- "result": "",
89
- "reports_filter": reports_filter or "",
90
- "sources_filter": sources_filter or "",
91
- "subtype_filter": subtype_filter or "",
92
- "year_filter": year_filter or ""
93
- }
94
- final_state = graph.invoke(initial_state)
95
- return final_state["result"]
96
-
97
- # Simple testing interface
98
- ui = gr.Interface(
99
- fn=process_query,
100
- inputs=gr.Textbox(lines=2, placeholder="Enter query here"),
101
- outputs="text",
102
- flagging_mode="never"
103
- )
104
 
105
- # Add a function to generate the graph visualization
106
  def get_graph_visualization():
107
- """Generate and return the LangGraph workflow visualization as a PIL Image."""
108
- # Generate the graph as PNG bytes
109
- graph_png_bytes = graph.get_graph().draw_mermaid_png()
110
-
111
- # Convert bytes to PIL Image for Gradio display
112
- graph_image = Image.open(io.BytesIO(graph_png_bytes))
113
- return graph_image
114
 
115
-
116
- # Guidance for ChatUI - can be removed later. Questionable whether front end even necessary. Maybe nice to show the graph.
117
- with gr.Blocks(title="ChatFed Orchestrator") as demo:
118
- gr.Markdown("# ChatFed Orchestrator")
119
- gr.Markdown("This LangGraph server exposes MCP endpoints for the ChatUI module to call (which triggers the graph).")
120
-
121
- with gr.Row():
122
- # Left column - Graph visualization
123
- with gr.Column(scale=1):
124
- gr.Markdown("**Workflow Visualization**")
125
- graph_display = gr.Image(
126
- value=get_graph_visualization(),
127
- label="LangGraph Workflow",
128
- interactive=False,
129
- height=300
130
- )
131
-
132
- # Add a refresh button for the graph
133
- refresh_graph_btn = gr.Button("πŸ”„ Refresh Graph", size="sm")
134
- refresh_graph_btn.click(
135
- fn=get_graph_visualization,
136
- outputs=graph_display
137
- )
138
 
139
- # Right column - Interface and documentation
140
- with gr.Column(scale=2):
141
- gr.Markdown("**Available MCP Tools:**")
142
-
143
- with gr.Accordion("MCP Endpoint Information", open=True):
144
- gr.Markdown(f"""
145
- **MCP Server Endpoint:** https://giz-chatfed-orchestrator.hf.space/gradio_api/mcp/sse
 
 
 
 
146
 
147
- **For ChatUI Integration:**
148
- ```python
149
- from gradio_client import Client
150
 
151
- # Connect to orchestrator
152
- orchestrator_client = Client("https://giz-chatfed-orchestrator.hf.space")
153
 
154
- # Basic usage (no filters)
155
- response = orchestrator_client.predict(
156
- query="query",
157
- api_name="/process_query"
158
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
 
160
- # Advanced usage with any combination of filters
161
- response = orchestrator_client.predict(
162
- query="query",
163
- reports_filter="annual_reports",
164
- sources_filter="internal",
165
- year_filter="2024",
166
- api_name="/process_query"
167
- )
168
- ```
169
- """)
170
 
171
- with gr.Accordion("Quick Testing Interface", open=True):
172
- ui.render()
 
173
 
174
- if __name__ == "__main__":
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
  demo.launch(
176
  server_name="0.0.0.0",
177
- server_port=7860,
178
- mcp_server=True,
179
- show_error=True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
  )
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Hybrid ChatFed Orchestrator with both Gradio MCP endpoints and LangServe API.
4
+ Provides MCP compatibility while adding enhanced observability through LangServe.
5
+ """
6
+
7
  import gradio as gr
8
+ from fastapi import FastAPI
9
+ from langserve import add_routes
10
  from langgraph.graph import StateGraph, START, END
11
+ from typing import TypedDict, Optional, Dict, Any
12
+ from gradio_client import Client
13
+ import uvicorn
14
+ import os
15
+ from datetime import datetime
16
+ import logging
17
+ from contextlib import asynccontextmanager
18
  import io
19
  from PIL import Image
20
+ import threading
21
+
22
+ # Configure logging for observability
23
+ logging.basicConfig(
24
+ level=logging.INFO,
25
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
26
+ )
27
+ logger = logging.getLogger(__name__)
28
 
 
 
29
  # Define the state schema
30
  class GraphState(TypedDict):
31
  query: str
32
  context: str
33
  result: str
 
34
  reports_filter: str
35
  sources_filter: str
36
  subtype_filter: str
37
  year_filter: str
38
+ metadata: Optional[Dict[str, Any]]
39
 
40
+ # LangServe input/output schemas
41
+ class ChatFedInput(TypedDict):
42
+ query: str
43
+ reports_filter: Optional[str]
44
+ sources_filter: Optional[str]
45
+ subtype_filter: Optional[str]
46
+ year_filter: Optional[str]
47
+ session_id: Optional[str]
48
+ user_id: Optional[str]
49
+
50
+ class ChatFedOutput(TypedDict):
51
+ result: str
52
+ metadata: Dict[str, Any]
53
+
54
+ # Enhanced retriever node with logging
55
  def retrieve_node(state: GraphState) -> GraphState:
56
+ start_time = datetime.now()
57
+ logger.info(f"Starting retrieval for query: {state['query'][:100]}...")
58
+
59
+ try:
60
+ client = Client("giz/chatfed_retriever")
61
+ context = client.predict(
62
+ query=state["query"],
63
+ reports_filter=state.get("reports_filter", ""),
64
+ sources_filter=state.get("sources_filter", ""),
65
+ subtype_filter=state.get("subtype_filter", ""),
66
+ year_filter=state.get("year_filter", ""),
67
+ api_name="/retrieve"
68
+ )
69
+
70
+ duration = (datetime.now() - start_time).total_seconds()
71
+ metadata = state.get("metadata", {})
72
+ metadata.update({
73
+ "retrieval_duration_seconds": duration,
74
+ "context_length": len(context) if context else 0,
75
+ "retrieval_success": True
76
+ })
77
+
78
+ logger.info(f"Retrieval completed in {duration:.2f}s, context length: {len(context) if context else 0}")
79
+ return {"context": context, "metadata": metadata}
80
+
81
+ except Exception as e:
82
+ duration = (datetime.now() - start_time).total_seconds()
83
+ logger.error(f"Retrieval failed after {duration:.2f}s: {str(e)}")
84
+
85
+ metadata = state.get("metadata", {})
86
+ metadata.update({
87
+ "retrieval_duration_seconds": duration,
88
+ "retrieval_success": False,
89
+ "retrieval_error": str(e)
90
+ })
91
+ return {"context": "", "metadata": metadata}
92
 
93
+ # Enhanced generator node with logging
94
  def generate_node(state: GraphState) -> GraphState:
95
+ start_time = datetime.now()
96
+ logger.info(f"Starting generation for query: {state['query'][:100]}...")
97
+
98
+ try:
99
+ client = Client("giz/chatfed_generator")
100
+ result = client.predict(
101
+ query=state["query"],
102
+ context=state["context"],
103
+ api_name="/generate"
104
+ )
105
+
106
+ duration = (datetime.now() - start_time).total_seconds()
107
+ metadata = state.get("metadata", {})
108
+ metadata.update({
109
+ "generation_duration_seconds": duration,
110
+ "result_length": len(result) if result else 0,
111
+ "generation_success": True
112
+ })
113
+
114
+ logger.info(f"Generation completed in {duration:.2f}s, result length: {len(result) if result else 0}")
115
+ return {"result": result, "metadata": metadata}
116
+
117
+ except Exception as e:
118
+ duration = (datetime.now() - start_time).total_seconds()
119
+ logger.error(f"Generation failed after {duration:.2f}s: {str(e)}")
120
+
121
+ metadata = state.get("metadata", {})
122
+ metadata.update({
123
+ "generation_duration_seconds": duration,
124
+ "generation_success": False,
125
+ "generation_error": str(e)
126
+ })
127
+ return {"result": f"Error generating response: {str(e)}", "metadata": metadata}
128
 
129
+ # Build the graph
130
  workflow = StateGraph(GraphState)
 
 
131
  workflow.add_node("retrieve", retrieve_node)
132
  workflow.add_node("generate", generate_node)
 
 
133
  workflow.add_edge(START, "retrieve")
134
  workflow.add_edge("retrieve", "generate")
135
  workflow.add_edge("generate", END)
136
+ compiled_graph = workflow.compile()
137
+
138
+ # Core processing function (shared by both Gradio and LangServe)
139
+ def process_chatfed_query_core(
140
+ query: str,
141
+ reports_filter: str = "",
142
+ sources_filter: str = "",
143
+ subtype_filter: str = "",
144
+ year_filter: str = "",
145
+ session_id: Optional[str] = None,
146
+ user_id: Optional[str] = None,
147
+ return_metadata: bool = False
148
+ ):
149
+ """Core processing function used by both Gradio and LangServe interfaces."""
150
+ start_time = datetime.now()
151
+ if not session_id:
152
+ session_id = f"session_{start_time.strftime('%Y%m%d_%H%M%S')}"
153
+
154
+ logger.info(f"Processing query in session {session_id}: {query[:100]}...")
155
+
156
+ try:
157
+ initial_state = {
158
+ "query": query,
159
+ "context": "",
160
+ "result": "",
161
+ "reports_filter": reports_filter or "",
162
+ "sources_filter": sources_filter or "",
163
+ "subtype_filter": subtype_filter or "",
164
+ "year_filter": year_filter or "",
165
+ "metadata": {
166
+ "session_id": session_id,
167
+ "user_id": user_id,
168
+ "start_time": start_time.isoformat(),
169
+ "orchestrator": "hybrid_gradio_langserve"
170
+ }
171
+ }
172
+
173
+ final_state = compiled_graph.invoke(initial_state)
174
+ total_duration = (datetime.now() - start_time).total_seconds()
175
+
176
+ final_metadata = final_state.get("metadata", {})
177
+ final_metadata.update({
178
+ "total_duration_seconds": total_duration,
179
+ "end_time": datetime.now().isoformat(),
180
+ "pipeline_success": True
181
+ })
182
+
183
+ logger.info(f"Query processing completed in {total_duration:.2f}s for session {session_id}")
184
+
185
+ if return_metadata:
186
+ return {"result": final_state["result"], "metadata": final_metadata}
187
+ else:
188
+ return final_state["result"]
189
+
190
+ except Exception as e:
191
+ total_duration = (datetime.now() - start_time).total_seconds()
192
+ logger.error(f"Pipeline failed after {total_duration:.2f}s for session {session_id}: {str(e)}")
193
+
194
+ if return_metadata:
195
+ error_metadata = {
196
+ "session_id": session_id,
197
+ "total_duration_seconds": total_duration,
198
+ "pipeline_success": False,
199
+ "error": str(e)
200
+ }
201
+ return {"result": f"Error processing query: {str(e)}", "metadata": error_metadata}
202
+ else:
203
+ return f"Error processing query: {str(e)}"
204
 
205
+ # =============================================================================
206
+ # GRADIO INTERFACE (MCP ENDPOINTS)
207
+ # =============================================================================
208
 
209
+ # Gradio wrapper functions for MCP compatibility
210
+ def process_query_gradio(
211
  query: str,
212
  reports_filter: str = "",
213
  sources_filter: str = "",
214
  subtype_filter: str = "",
215
  year_filter: str = ""
216
  ) -> str:
217
+ """Gradio-compatible function that exposes MCP endpoints."""
218
+ return process_chatfed_query_core(
219
+ query=query,
220
+ reports_filter=reports_filter,
221
+ sources_filter=sources_filter,
222
+ subtype_filter=subtype_filter,
223
+ year_filter=year_filter,
224
+ session_id=f"gradio_{datetime.now().strftime('%Y%m%d_%H%M%S')}",
225
+ return_metadata=False
226
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
 
 
228
  def get_graph_visualization():
229
+ """Generate graph visualization for Gradio interface."""
230
+ try:
231
+ graph_png_bytes = compiled_graph.get_graph().draw_mermaid_png()
232
+ return Image.open(io.BytesIO(graph_png_bytes))
233
+ except Exception as e:
234
+ logger.error(f"Failed to generate graph visualization: {e}")
235
+ return None
236
 
237
+ # Create Gradio interface
238
+ def create_gradio_interface():
239
+ with gr.Blocks(title="ChatFed Orchestrator - MCP Endpoints") as demo:
240
+ gr.Markdown("# ChatFed Orchestrator")
241
+ gr.Markdown("**MCP Server Endpoints Available** - This interface provides MCP compatibility for ChatUI integration.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
 
243
+ with gr.Row():
244
+ with gr.Column(scale=1):
245
+ gr.Markdown("**Workflow Visualization**")
246
+ graph_display = gr.Image(
247
+ value=get_graph_visualization(),
248
+ label="LangGraph Workflow",
249
+ interactive=False,
250
+ height=300
251
+ )
252
+ refresh_graph_btn = gr.Button("πŸ”„ Refresh Graph", size="sm")
253
+ refresh_graph_btn.click(fn=get_graph_visualization, outputs=graph_display)
254
 
255
+ gr.Markdown("**πŸ”— MCP Integration**")
256
+ gr.Markdown("MCP endpoints are active and ready for ChatUI integration.")
 
257
 
258
+ with gr.Column(scale=2):
259
+ gr.Markdown("**MCP Endpoint Information**")
260
 
261
+ with gr.Accordion("MCP Usage", open=True):
262
+ gr.Markdown("""
263
+ **MCP Server Endpoint:** Available at `/gradio_api/mcp/sse`
264
+
265
+ **For ChatUI Integration:**
266
+ ```python
267
+ from gradio_client import Client
268
+
269
+ # Connect to orchestrator MCP endpoint
270
+ client = Client("https://your-space.hf.space")
271
+
272
+ # Basic usage
273
+ response = client.predict(
274
+ query="your question",
275
+ api_name="/process_query_gradio"
276
+ )
277
+
278
+ # With filters
279
+ response = client.predict(
280
+ query="your question",
281
+ reports_filter="annual_reports",
282
+ sources_filter="internal",
283
+ year_filter="2024",
284
+ api_name="/process_query_gradio"
285
+ )
286
+ ```
287
+ """)
288
+
289
+ with gr.Accordion("Test Interface", open=False):
290
+ # Test interface
291
+ with gr.Row():
292
+ with gr.Column():
293
+ query_input = gr.Textbox(label="Query", lines=2, placeholder="Enter your question...")
294
+ reports_filter_input = gr.Textbox(label="Reports Filter", placeholder="e.g., annual_reports")
295
+ sources_filter_input = gr.Textbox(label="Sources Filter", placeholder="e.g., internal")
296
+ subtype_filter_input = gr.Textbox(label="Subtype Filter", placeholder="e.g., financial")
297
+ year_filter_input = gr.Textbox(label="Year Filter", placeholder="e.g., 2024")
298
+ submit_btn = gr.Button("Submit", variant="primary")
299
 
300
+ with gr.Column():
301
+ output = gr.Textbox(label="Response", lines=10)
302
+
303
+ submit_btn.click(
304
+ fn=process_query_gradio,
305
+ inputs=[query_input, reports_filter_input, sources_filter_input, subtype_filter_input, year_filter_input],
306
+ outputs=output
307
+ )
308
+
309
+ return demo
310
 
311
+ # =============================================================================
312
+ # LANGSERVE API (ENHANCED OBSERVABILITY)
313
+ # =============================================================================
314
 
315
+ def process_chatfed_query_langserve(input_data: ChatFedInput) -> ChatFedOutput:
316
+ """LangServe function with full metadata return."""
317
+ result = process_chatfed_query_core(
318
+ query=input_data["query"],
319
+ reports_filter=input_data.get("reports_filter", ""),
320
+ sources_filter=input_data.get("sources_filter", ""),
321
+ subtype_filter=input_data.get("subtype_filter", ""),
322
+ year_filter=input_data.get("year_filter", ""),
323
+ session_id=input_data.get("session_id"),
324
+ user_id=input_data.get("user_id"),
325
+ return_metadata=True
326
+ )
327
+ return ChatFedOutput(result=result["result"], metadata=result["metadata"])
328
+
329
+ @asynccontextmanager
330
+ async def lifespan(app: FastAPI):
331
+ logger.info("πŸš€ Hybrid ChatFed Orchestrator starting up...")
332
+ logger.info("βœ… LangGraph compiled successfully")
333
+ logger.info("πŸ”— MCP endpoints will be available via Gradio")
334
+ logger.info("πŸ“Š Enhanced API available via LangServe")
335
+ yield
336
+ logger.info("πŸ›‘ Orchestrator shutting down...")
337
+
338
+ # Create FastAPI app
339
+ app = FastAPI(
340
+ title="ChatFed Orchestrator - Enhanced API",
341
+ version="1.0.0",
342
+ description="Enhanced API with observability. MCP endpoints available via Gradio interface.",
343
+ lifespan=lifespan
344
+ )
345
+
346
+ # Health check
347
+ @app.get("/health")
348
+ async def health_check():
349
+ return {
350
+ "status": "healthy",
351
+ "mcp_endpoints": "available_via_gradio",
352
+ "enhanced_api": "available_via_langserve"
353
+ }
354
+
355
+ # NEW: ChatUI-compatible input schema
356
+ from pydantic import BaseModel
357
+ from typing import List, Literal
358
+
359
+ class ChatMessage(BaseModel):
360
+ role: Literal["system", "user", "assistant"]
361
+ content: str
362
+
363
+ class ChatUIInput(BaseModel):
364
+ messages: List[ChatMessage]
365
+
366
+ def chatui_adapter(data: ChatUIInput):
367
+ """
368
+ Adapter to allow ChatUI to send full chat history.
369
+ We extract the latest user message for ChatFed.
370
+ """
371
+ last_user_msg = next(m.content for m in reversed(data.messages) if m.role == "user")
372
+ result = process_chatfed_query_core(query=last_user_msg)
373
+ return {"result": result, "metadata": {"source": "chatfed-langserve-adapter"}}
374
+
375
+ # Add LangServe routes
376
+ add_routes(
377
+ app,
378
+ process_chatfed_query_langserve,
379
+ path="/chatfed",
380
+ input_type=ChatFedInput,
381
+ output_type=ChatFedOutput
382
+ )
383
+
384
+ # NEW: ChatUI-compatible LangServe route
385
+ add_routes(
386
+ app,
387
+ chatui_adapter,
388
+ path="/chatfed-chatui",
389
+ input_type=ChatUIInput
390
+ )
391
+
392
+ # Backward compatibility endpoint
393
+ @app.post("/process_query")
394
+ async def process_query_endpoint(
395
+ query: str,
396
+ reports_filter: str = "",
397
+ sources_filter: str = "",
398
+ subtype_filter: str = "",
399
+ year_filter: str = "",
400
+ session_id: Optional[str] = None,
401
+ user_id: Optional[str] = None
402
+ ):
403
+ """Backward compatibility endpoint."""
404
+ return process_chatfed_query_core(
405
+ query=query,
406
+ reports_filter=reports_filter,
407
+ sources_filter=sources_filter,
408
+ subtype_filter=subtype_filter,
409
+ year_filter=year_filter,
410
+ session_id=session_id,
411
+ user_id=user_id,
412
+ return_metadata=False
413
+ )
414
+
415
+ # =============================================================================
416
+ # MAIN APPLICATION LAUNCHER
417
+ # =============================================================================
418
+
419
+ def run_gradio_server():
420
+ """Run Gradio server in a separate thread for MCP endpoints."""
421
+ demo = create_gradio_interface()
422
  demo.launch(
423
  server_name="0.0.0.0",
424
+ server_port=7861, # Different port from FastAPI
425
+ mcp_server=True, # Enable MCP endpoints!
426
+ show_error=True,
427
+ share=False,
428
+ quiet=True
429
+ )
430
+
431
+ if __name__ == "__main__":
432
+ # Start Gradio server in background thread for MCP endpoints
433
+ gradio_thread = threading.Thread(target=run_gradio_server, daemon=True)
434
+ gradio_thread.start()
435
+ logger.info("πŸ”— Gradio MCP server started on port 7861")
436
+
437
+ # Start FastAPI server for enhanced API
438
+ host = os.getenv("HOST", "0.0.0.0")
439
+ port = int(os.getenv("PORT", "7860"))
440
+
441
+ logger.info(f"πŸš€ Starting FastAPI server on {host}:{port}")
442
+ logger.info("πŸ“Š Enhanced API with observability available at /docs")
443
+ logger.info("πŸ”— MCP endpoints available via Gradio on port 7861")
444
+
445
+ uvicorn.run(
446
+ app,
447
+ host=host,
448
+ port=port,
449
+ log_level="info",
450
+ access_log=True
451
  )
requirements.txt CHANGED
@@ -2,4 +2,11 @@ gradio[mcp]
2
  gradio_client>=1.0.0
3
  langgraph>=0.2.0
4
  Pillow>=9.0.0
 
 
 
 
 
 
 
5
 
 
2
  gradio_client>=1.0.0
3
  langgraph>=0.2.0
4
  Pillow>=9.0.0
5
+ fastapi
6
+ langserve[all]
7
+ uvicorn[standard]
8
+ typing_extensions
9
+ python-multipart
10
+
11
+
12