Karan6933 commited on
Commit
2972ed5
·
verified ·
1 Parent(s): 1c80413

Upload 4 files

Browse files
Files changed (4) hide show
  1. Dockerfile +29 -0
  2. entrypoint.sh +16 -0
  3. main.py +194 -0
  4. requirements.txt +11 -0
Dockerfile ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11
2
+
3
+ # 1. Install Ollama
4
+ RUN curl -fsSL https://ollama.com/install.sh | sh
5
+
6
+ # 2. User Setup
7
+ RUN useradd -m -u 1000 user
8
+ ENV USER=user
9
+ ENV PATH="/home/user/.local/bin:$PATH"
10
+ ENV HOME=/home/user
11
+ ENV OLLAMA_KEEP_ALIVE=5m
12
+
13
+ # 3. Workdir
14
+ WORKDIR $HOME/app
15
+
16
+ # 4. Switch User
17
+ USER user
18
+
19
+ # 5. Install Python Libs
20
+ RUN pip install --no-cache-dir fastapi uvicorn ollama
21
+ # 6. Copy Files
22
+ COPY --chown=user . .
23
+
24
+ # 7. Start Script Permission
25
+ RUN chmod +x entrypoint.sh
26
+
27
+ # 8. Ports
28
+ EXPOSE 7860
29
+ CMD ["./entrypoint.sh"]
entrypoint.sh ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ pip install -r requirements.txt
3
+ # 1. Ollama Server Start
4
+ ollama serve &
5
+
6
+ echo "Waiting for Ollama server..."
7
+ sleep 5
8
+
9
+ # 2. Qwen Model Pull
10
+ # Note: Ye model bada hai, download hone mein 2-3 minute lag sakte hain
11
+ echo "Pulling qwen2.5:3b..."
12
+ ollama pull qwen2.5:3b
13
+
14
+ # 3. FastAPI Start
15
+ echo "Starting Public API..."
16
+ uvicorn main:app --host 0.0.0.0 --port 7860
main.py ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import logging
3
+ import asyncio
4
+ import base64
5
+ import io
6
+ from typing import Annotated, List
7
+ from contextlib import asynccontextmanager
8
+
9
+ from fastapi import FastAPI
10
+ from fastapi.responses import StreamingResponse
11
+ from pydantic import BaseModel, Field
12
+
13
+ import httpx
14
+ from duckduckgo_search import DDGS
15
+ from bs4 import BeautifulSoup
16
+ from PIL import Image # Image save karne ke liye
17
+
18
+ # --- LangChain / AI Core ---
19
+ from langchain_ollama import ChatOllama
20
+ from langchain_core.messages import HumanMessage, SystemMessage, BaseMessage
21
+ from langchain_core.tools import tool
22
+ from langgraph.graph import StateGraph, END, START
23
+ from langgraph.prebuilt import ToolNode
24
+ from langgraph.checkpoint.memory import MemorySaver
25
+
26
+ # --------------------------------------------------------------------------------------
27
+ # 1. Configuration
28
+ # --------------------------------------------------------------------------------------
29
+ logging.basicConfig(level=logging.INFO)
30
+ logger = logging.getLogger("GenAI-Agent")
31
+
32
+ MODEL_NAME = "qwen2.5:3b"
33
+ BASE_URL = "http://localhost:11434"
34
+
35
+ # --- IMAGE JUGAAD CONFIG ---
36
+ # HuggingFace ka Free API use karenge (No GPU needed locally for image gen)
37
+ HF_API_URL = "https://api-inference.huggingface.co/models/black-forest-labs/FLUX.1-dev"
38
+ # Agar tumhara apna Token hai toh yahan daalo, warna kai models free open access dete hain
39
+ # Best practice: Get a free token from huggingface.co/settings/tokens
40
+ HF_TOKEN = os.getenv("HF_TOKEN", "")
41
+ headers = {"Authorization": f"Bearer {HF_TOKEN}"} if HF_TOKEN else {}
42
+
43
+ http_client = httpx.AsyncClient(timeout=30.0, follow_redirects=True)
44
+
45
+ @asynccontextmanager
46
+ async def lifespan(app: FastAPI):
47
+ # Create static folder for images
48
+ os.makedirs("static/images", exist_ok=True)
49
+ yield
50
+ await http_client.aclose()
51
+
52
+ app = FastAPI(title="GenAI Text & Image Agent", lifespan=lifespan)
53
+
54
+ # --------------------------------------------------------------------------------------
55
+ # 2. Tools (Web + Image Jugaad)
56
+ # --------------------------------------------------------------------------------------
57
+
58
+ @tool
59
+ async def web_search(query: str) -> str:
60
+ """Search the web for information."""
61
+ def run_sync_search(q):
62
+ try:
63
+ with DDGS() as ddgs:
64
+ return list(ddgs.text(q, max_results=4))
65
+ except Exception as e:
66
+ return str(e)
67
+
68
+ try:
69
+ results = await asyncio.to_thread(run_sync_search, query)
70
+ if isinstance(results, str) or not results:
71
+ return "No results found."
72
+
73
+ output = []
74
+ for r in results:
75
+ output.append(f"Title: {r.get('title')}\nLink: {r.get('href')}\nSnippet: {r.get('body')}\n---")
76
+ return "\n".join(output)
77
+ except Exception as e:
78
+ return f"Error: {str(e)}"
79
+
80
+ # --- YE HAI NAYA IMAGE JUGAAD TOOL ---
81
+ @tool
82
+ async def generate_image(prompt: str) -> str:
83
+ """
84
+ Generates an image based on the user's prompt using AI.
85
+ Use this when user asks to 'draw', 'create image', or 'paint'.
86
+ Returns the file path of the generated image.
87
+ """
88
+ logger.info(f"🎨 Generating Image for: {prompt}")
89
+
90
+ # Fallback payload
91
+ payload = {"inputs": prompt}
92
+
93
+ try:
94
+ # HuggingFace API Call
95
+ response = await http_client.post(HF_API_URL, headers=headers, json=payload)
96
+
97
+ if response.status_code != 200:
98
+ return f"Image Gen Failed: {response.text}"
99
+
100
+ # Image Bytes ko file mein save karna
101
+ image_bytes = response.content
102
+ image = Image.open(io.BytesIO(image_bytes))
103
+
104
+ # Unique filename banate hain
105
+ filename = f"static/images/gen_{asyncio.get_event_loop().time()}.png"
106
+
107
+ # Thread mein save karo taaki server freeze na ho
108
+ await asyncio.to_thread(image.save, filename)
109
+
110
+ return f"Image generated successfully! View at: {filename}"
111
+ except Exception as e:
112
+ return f"Image System Error: {str(e)}"
113
+
114
+ tools = [web_search, generate_image]
115
+
116
+ # --------------------------------------------------------------------------------------
117
+ # 3. Agent Setup
118
+ # --------------------------------------------------------------------------------------
119
+
120
+ class AgentState(TypedDict):
121
+ messages: Annotated[List[BaseMessage], "add_messages"]
122
+
123
+ llm = ChatOllama(
124
+ model=MODEL_NAME,
125
+ base_url=BASE_URL,
126
+ temperature=0.3,
127
+ ).bind_tools(tools)
128
+
129
+ SYSTEM_PROMPT = """You are a smart AI assistant capable of Web Search and Image Generation.
130
+
131
+ RULES:
132
+ 1. If user asks to DRAW, PAINT, or GENERATE an image, use 'generate_image'.
133
+ 2. If user asks for Info, use 'web_search'.
134
+ 3. If you generate an image, tell the user the file path returned by the tool.
135
+ """
136
+
137
+ async def agent_node(state: AgentState):
138
+ messages = [SystemMessage(content=SYSTEM_PROMPT)] + state["messages"]
139
+ response = await llm.ainvoke(messages)
140
+ return {"messages": [response]}
141
+
142
+ workflow = StateGraph(AgentState)
143
+ workflow.add_node("agent", agent_node)
144
+ workflow.add_node("tools", ToolNode(tools))
145
+ workflow.add_edge(START, "agent")
146
+ workflow.add_conditional_edges("agent", lambda s: "tools" if s["messages"][-1].tool_calls else END)
147
+ workflow.add_edge("tools", "agent")
148
+
149
+ memory = MemorySaver()
150
+ app_graph = workflow.compile(checkpointer=memory)
151
+
152
+ # --------------------------------------------------------------------------------------
153
+ # 4. API Endpoints
154
+ # --------------------------------------------------------------------------------------
155
+
156
+ class ChatRequest(BaseModel):
157
+ query: str
158
+ thread_id: str
159
+
160
+ # Serve static files so you can see images in browser
161
+ from fastapi.staticfiles import StaticFiles
162
+ app.mount("/static", StaticFiles(directory="static"), name="static")
163
+
164
+ async def event_generator(query: str, thread_id: str):
165
+ config = {"configurable": {"thread_id": thread_id}}
166
+ inputs = {"messages": [HumanMessage(content=query)]}
167
+
168
+ yield "🤖 **Agent Active...**\n\n"
169
+
170
+ async for event in app_graph.astream_events(inputs, config=config, version="v1"):
171
+ event_type = event["event"]
172
+
173
+ if event_type == "on_chat_model_stream":
174
+ chunk = event["data"]["chunk"].content
175
+ if chunk: yield chunk
176
+
177
+ elif event_type == "on_tool_start":
178
+ tool_name = event['name']
179
+ if tool_name == "generate_image":
180
+ yield f"\n\n🎨 **Artist Mode On:** Painting '{event['data'].get('input')}'. Please wait...\n\n"
181
+ else:
182
+ yield f"\n\n🔎 **Searching:** {tool_name}...\n\n"
183
+
184
+ elif event_type == "on_tool_end":
185
+ output = str(event['data'].get('output'))
186
+ if "static/images" in output:
187
+ # Markdown image syntax for frontend
188
+ yield f"\n\n🖼️ **Image Ready:**\n![]({output})\n\n"
189
+ else:
190
+ yield f"✅ **Data:** {output[:100]}...\n\n"
191
+
192
+ @app.post("/chat")
193
+ async def chat_endpoint(req: ChatRequest):
194
+ return StreamingResponse(event_generator(req.query, req.thread_id), media_type="text/plain")
requirements.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ httpx
4
+ duckduckgo-search
5
+ langchain-ollama
6
+ langchain-core
7
+ langgraph
8
+ beautifulsoup4
9
+ async-lru
10
+ pillow
11
+ huggingface_hub