Karan6933 commited on
Commit
c6290c3
·
verified ·
1 Parent(s): 2da8755

Upload 5 files

Browse files
Files changed (5) hide show
  1. Dockerfile +29 -0
  2. README.md +4 -5
  3. entrypoint.sh +16 -0
  4. main.py +226 -0
  5. requirements.txt +9 -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"]
README.md CHANGED
@@ -1,11 +1,10 @@
1
  ---
2
- title: Coder Ai
3
- emoji:
4
- colorFrom: green
5
- colorTo: green
6
  sdk: docker
7
  pinned: false
8
- license: mit
9
  ---
10
 
11
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: FreeAi
3
+ emoji: 👁
4
+ colorFrom: indigo
5
+ colorTo: indigo
6
  sdk: docker
7
  pinned: false
 
8
  ---
9
 
10
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
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,226 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import logging
3
+ import asyncio
4
+ from typing import Annotated, TypedDict, List
5
+ from contextlib import asynccontextmanager
6
+
7
+ from fastapi import FastAPI
8
+ from fastapi.responses import StreamingResponse
9
+ from pydantic import BaseModel, Field
10
+
11
+ # --- Async & Network ---
12
+ import httpx
13
+ # FIX: Use standard DDGS and asyncio for non-blocking execution
14
+ from duckduckgo_search import DDGS
15
+ from bs4 import BeautifulSoup
16
+
17
+ # --- LangChain / AI Core ---
18
+ from langchain_ollama import ChatOllama
19
+ from langchain_core.messages import HumanMessage, SystemMessage, BaseMessage
20
+ from langchain_core.tools import tool
21
+ from langchain_core.prompts import ChatPromptTemplate
22
+
23
+ # --- LangGraph (The Brain) ---
24
+ from langgraph.graph import StateGraph, END, START
25
+ from langgraph.prebuilt import ToolNode
26
+ from langgraph.checkpoint.memory import MemorySaver
27
+
28
+ # --------------------------------------------------------------------------------------
29
+ # 1. Configuration & Global State
30
+ # --------------------------------------------------------------------------------------
31
+ logging.basicConfig(level=logging.INFO)
32
+ logger = logging.getLogger("GenAI-Agent")
33
+
34
+ # Model: Use a smart model suitable for tool calling (qwen2.5 or llama3.1)
35
+ MODEL_NAME = "qwen2.5:3b"
36
+ BASE_URL = "http://localhost:11434"
37
+
38
+ # Global HTTP Client
39
+ http_client = httpx.AsyncClient(timeout=15.0, follow_redirects=True)
40
+
41
+ @asynccontextmanager
42
+ async def lifespan(app: FastAPI):
43
+ yield
44
+ await http_client.aclose()
45
+
46
+ app = FastAPI(title="GenAI Advanced Agent", version="2.1", lifespan=lifespan)
47
+
48
+ # --------------------------------------------------------------------------------------
49
+ # 2. Advanced Tools (Fixed for Latest DuckDuckGo)
50
+ # --------------------------------------------------------------------------------------
51
+
52
+ @tool
53
+ async def web_search(query: str) -> str:
54
+ """
55
+ Search the web for latest information, technical docs, or news.
56
+ Returns top 5 results with snippets and URLs.
57
+ """
58
+ # Helper function to run sync library in async environment
59
+ def run_sync_search(q):
60
+ try:
61
+ with DDGS() as ddgs:
62
+ # Latest version returns a list of dicts directly
63
+ return list(ddgs.text(q, max_results=5))
64
+ except Exception as e:
65
+ return str(e)
66
+
67
+ logger.info(f"🔎 Searching for: {query}")
68
+ try:
69
+ # Offload sync task to a separate thread to keep server fast
70
+ results = await asyncio.to_thread(run_sync_search, query)
71
+
72
+ if isinstance(results, str): # Check if error occurred
73
+ return f"Search Error: {results}"
74
+
75
+ if not results:
76
+ return "No results found on the web."
77
+
78
+ # Format cleanly for the LLM
79
+ output = []
80
+ for r in results:
81
+ title = r.get('title', 'No Title')
82
+ link = r.get('href', '#')
83
+ body = r.get('body', '')
84
+ output.append(f"Title: {title}\nLink: {link}\nSnippet: {body}\n---")
85
+ return "\n".join(output)
86
+ except Exception as e:
87
+ return f"Search System Error: {str(e)}"
88
+
89
+ @tool
90
+ async def read_webpage(url: str) -> str:
91
+ """
92
+ Reads the full content of a specific URL.
93
+ Use this if the search snippet is not enough and you need deep technical details.
94
+ """
95
+ logger.info(f"📖 Reading page: {url}")
96
+ try:
97
+ # User-Agent to avoid blocking
98
+ headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"}
99
+ resp = await http_client.get(url, headers=headers)
100
+
101
+ if resp.status_code != 200:
102
+ return f"Failed to load page: Status {resp.status_code}"
103
+
104
+ soup = BeautifulSoup(resp.text, 'html.parser')
105
+
106
+ # Clean up unnecessary tags
107
+ for tag in soup(["script", "style", "nav", "footer", "svg"]):
108
+ tag.decompose()
109
+
110
+ text = soup.get_text(separator="\n")
111
+
112
+ # Clean whitespace
113
+ lines = (line.strip() for line in text.splitlines())
114
+ chunks = (phrase.strip() for line in lines for phrase in line.split(" "))
115
+ clean_text = '\n'.join(chunk for chunk in chunks if chunk)
116
+
117
+ # Limit text length to avoid context overflow (approx 4000 chars)
118
+ return clean_text[:4000] + "...(truncated)"
119
+ except Exception as e:
120
+ return f"Scraping Error: {str(e)}"
121
+
122
+ tools = [web_search, read_webpage]
123
+
124
+ # --------------------------------------------------------------------------------------
125
+ # 3. LangGraph Agent Architecture (Reasoning Engine)
126
+ # --------------------------------------------------------------------------------------
127
+
128
+ class AgentState(TypedDict):
129
+ messages: Annotated[List[BaseMessage], "add_messages"]
130
+
131
+ # Initialize LLM with Tools
132
+ llm = ChatOllama(
133
+ model=MODEL_NAME,
134
+ base_url=BASE_URL,
135
+ temperature=0.3, # Lower temp for more precise code/facts
136
+ keep_alive="1h"
137
+ ).bind_tools(tools)
138
+
139
+ # System Prompt - Formatting & Behavior Rules
140
+ SYSTEM_PROMPT = """You are an advanced GenAI technical assistant.
141
+
142
+ CORE RULES:
143
+ 1. **Latest Info:** Always use 'web_search' for current events, libraries, or news. Do not guess.
144
+ 2. **Deep Dive:** If search snippets are too short, use 'read_webpage' to get full documentation/code.
145
+ 3. **Format:**
146
+ - Start your response with a brief summary.
147
+ - Use headings (##) for sections.
148
+ - For code, ALWAYS use this XML format:
149
+ <code lang="python">
150
+ print("code here")
151
+ </code>
152
+ - NEVER use markdown triple backticks (```) for code.
153
+ 4. **Memory:** If the user asks "continue" or "more", check the previous conversation history.
154
+
155
+ Think step-by-step before answering.
156
+ """
157
+
158
+ # Node: Agent (Decides what to do)
159
+ async def agent_node(state: AgentState):
160
+ messages = [SystemMessage(content=SYSTEM_PROMPT)] + state["messages"]
161
+ response = await llm.ainvoke(messages)
162
+ return {"messages": [response]}
163
+
164
+ # Build Graph
165
+ workflow = StateGraph(AgentState)
166
+ workflow.add_node("agent", agent_node)
167
+ workflow.add_node("tools", ToolNode(tools))
168
+
169
+ workflow.add_edge(START, "agent")
170
+ workflow.add_conditional_edges(
171
+ "agent",
172
+ lambda state: "tools" if state["messages"][-1].tool_calls else END
173
+ )
174
+ workflow.add_edge("tools", "agent")
175
+
176
+ # Memory (Checkpointer)
177
+ memory = MemorySaver()
178
+ app_graph = workflow.compile(checkpointer=memory)
179
+
180
+ # --------------------------------------------------------------------------------------
181
+ # 4. API Handling & Streaming
182
+ # --------------------------------------------------------------------------------------
183
+
184
+ class ChatRequest(BaseModel):
185
+ query: str
186
+ thread_id: str = Field(..., description="Unique ID for conversation (e.g., 'session-1')")
187
+
188
+ async def event_generator(query: str, thread_id: str):
189
+ config = {"configurable": {"thread_id": thread_id}}
190
+ inputs = {"messages": [HumanMessage(content=query)]}
191
+
192
+ yield "🤖 **GenAI Agent Initialized...**\n\n"
193
+
194
+ try:
195
+ async for event in app_graph.astream_events(inputs, config=config, version="v1"):
196
+ event_type = event["event"]
197
+
198
+ # 1. Agent Thinking (Stream Tokens)
199
+ if event_type == "on_chat_model_stream":
200
+ chunk = event["data"]["chunk"].content
201
+ if chunk:
202
+ yield chunk
203
+
204
+ # 2. Tool Start (Thinking Process)
205
+ elif event_type == "on_tool_start":
206
+ tool_name = event['name']
207
+ yield f"\n\n🤔 **Thinking:** Searching external sources using `{tool_name}`...\n\n"
208
+
209
+ # 3. Tool End (Success)
210
+ elif event_type == "on_tool_end":
211
+ output = str(event['data'].get('output'))[:150] # Preview data
212
+ yield f"✅ **Data Found:** {output}...\n\n"
213
+
214
+ except Exception as e:
215
+ yield f"\n❌ **System Error:** {str(e)}"
216
+
217
+ @app.post("/chat")
218
+ async def chat_endpoint(req: ChatRequest):
219
+ return StreamingResponse(
220
+ event_generator(req.query, req.thread_id),
221
+ media_type="text/plain"
222
+ )
223
+
224
+ if __name__ == "__main__":
225
+ import uvicorn
226
+ uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ httpx
4
+ duckduckgo-search
5
+ langchain-ollama
6
+ langchain-core
7
+ langgraph
8
+ beautifulsoup4
9
+ async-lru