File size: 10,823 Bytes
7c7c363
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c6e4140
 
 
 
 
 
 
42a5cc8
c6e4140
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f05c774
 
 
a44cb1b
f05c774
 
7c7c363
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89dfc8f
7c7c363
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c6e4140
 
89dfc8f
7c7c363
 
 
 
 
89dfc8f
 
7c7c363
 
89dfc8f
7c7c363
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a6afbe2
 
7c7c363
 
 
 
 
 
 
 
 
c6e4140
7c7c363
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89dfc8f
7c7c363
 
 
 
 
 
 
 
 
 
 
 
ab6c841
7c7c363
 
89dfc8f
c6e4140
89dfc8f
7c7c363
 
 
c6e4140
7c7c363
 
9adc62a
7c7c363
 
 
9adc62a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7c7c363
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89dfc8f
7c7c363
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c6e4140
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
########## Imports ############
from langchain_google_genai import ChatGoogleGenerativeAI
import os
from typing import TypedDict, List, Dict, Any, Optional
from langgraph.graph import StateGraph, START, END
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage

from langchain_community.tools import WikipediaQueryRun
from langchain_community.utilities import WikipediaAPIWrapper
import string

from langchain_experimental.tools import PythonREPLTool
import ast, json

from langchain_community.tools import DuckDuckGoSearchRun
import os
from langchain_huggingface import HuggingFaceEndpoint , ChatHuggingFace
from langchain import LLMChain, PromptTemplate

def get_gpt_and_answer(question):
      # إنشاء LLM باستخدام endpoint من Hugging Face
      llm = HuggingFaceEndpoint(
          repo_id="openai/gpt-oss-20b",               # أو أي نموذج موجود في HF
          task="text-generation",

          huggingfacehub_api_token=os.environ["HUGGINGFACEHUB_API_TOKEN"],
          provider="auto"              # يسمح باختيار المزود تلقائياً

      )

      # إنشاء سلسلة (chain) بسيطة
      template = """
      Question: {question}
      Answer:
      """
      llm = ChatHuggingFace(llm=llm)

      prompt = PromptTemplate.from_template(template)
      chain = LLMChain(llm=llm, prompt=prompt)

      # تجربة
      # result = chain.invoke({"question": question})

      model = ChatGoogleGenerativeAI(model="gemini-2.5-flash")
      result = (model.invoke([HumanMessage(content=template)]).content)

      return result #(result['text'])


########## State ############
class InfoState(TypedDict):
    question: str
    answer_type: Optional[str] # WebInfo  - WIKI - MATH
    answer_code : Optional[str]
    main_parts: Optional[List[str]]
    tool_answer : Optional[list[str]]
    final_answer : Optional[str]



######### Nodes ############
def get_wiki_relate(state: InfoState) -> InfoState:
    """
    Tool to Get the wikipedia info from keywords extracted from preprocessing at main_parts.

    Uses: Wikipedia API
    Returns: tool_answer (summary)
    """
    print("Using Wikipedia...")
    # Create the Wikipedia utility
    wiki = WikipediaAPIWrapper(
        lang="en",        # Wikipedia language
        top_k_results=1,  # how many results to fetch
        doc_content_chars_max=2000
    )

    # Make a tool from it
    wiki_tool = WikipediaQueryRun(api_wrapper=wiki)

    try:
      wiki_answer = wiki_tool.run(" ".join(state["main_parts"]) + " full wikipedia article about this topic")
      state['tool_answer'] = wiki_answer
      return state
    except Exception as e:
      print("Rate limit Exception")
      state['tool_answer'] = ""
      return state


def execute_code(state: InfoState) -> InfoState :
    """Tool to calculate any math using python code or get current date time."""
    print("Execut Code...")
    python_tool = PythonREPLTool()
    code = state["answer_code"]
    state["tool_answer"]=python_tool.run(code)
    return state

def get_code(state:InfoState) -> InfoState:
    """From prompt get the code to run."""
    print("Getting Code (Gemini)...")
    prompt = (
    f"You are a strict code generator. "
    f"Given the question: '{state['question']}', "
    f"return ONLY valid Python code that computes the answer IF the question is about math, date, or time. "
    f"Otherwise, return exactly: print('not valid')\n\n"
    f"Rules:\n"
    f"- Output ONLY the code or print('not valid')\n"
    f"- No explanations, no markdown, no extra text\n"
    f"- No quotes around the code\n"
    f"- Use print() to show the result\n"
    f"- Import modules only if needed (e.g. datetime, math)"
)

 
    state["answer_code"] = get_gpt_and_answer(prompt).strip()


    return state



def preprocess_text(state: dict) -> InfoState:

    """
    Preprocess text to get the keywords to help get results directly from wikipedia.

    Input: raw question
    Output: main_parts (list of keywords)
    """
    print("Preprocess text (Gemini)...")
    # 1️⃣ Prepare the prompt
    prompt =  (
    "We want to find the best-matching English Wikipedia pages for a factual question, "
    "so we must extract only the essential topic names or entities that Wikipedia likely has pages for. "
    "These should include the main subject (e.g., a person, event, place, or concept) and any directly relevant subtopic "
    "(like 'Discography', 'Career', or 'History') if they help narrow the search.\n\n"

    "Rules:\n"
    "- Output 1 to 3 items maximum.\n"
    "- Use English Wikipedia title format (capitalize each main word).\n"
    "- Translate non-English names or terms to English.\n"
    "- Exclude question words, pronouns, and filler terms.\n"
    "- Fix spelling errors if necessary.\n"
    "- Prefer specific Wikipedia topics over vague ones.\n\n"
    "Do NOT include markdown formatting or code blocks like ```json```. Output plain JSON only.\n"


    "Example:\n"
    "Q: 'Who built the Eiffel Tower?'\n"
    "A: [\"Eiffel Tower\", \"Gustave Eiffel\"]\n\n"
    f"Question: '{state['question']}'\n\n"
    "Output ONLY a valid JSON list as described — no explanations, markdown, or extra formatting."
    )


    response = get_gpt_and_answer(prompt).strip()

    # 3️⃣ Try to safely parse
    try:
        # First, try JSON
        state["main_parts"] = json.loads(response)
    except json.JSONDecodeError:
        try:
            # If not JSON, try Python literal
            state["main_parts"] = ast.literal_eval(response)
        except Exception:
            # If both fail, store fallback info
            print("⚠️ Model returned invalid content:", response)
            state["main_parts"] = []

    return state




def get_answer(state: InfoState) -> InfoState :
    """
    Final Node that returns the final answer organized.

    Combines: tool_answer → final_answer
    """
    print("Getting Answer (Gemini)...")

    prompt = (
        "You are a knowledgeable assistant that answers questions based on context and common factual knowledge.\n"
        "Use the context first, but if it clearly lacks the needed details, you may rely on well-known public facts "
        "(such as from Wikipedia) that logically complete the answer.\n\n"
        f"Question: {state['question']}\n"
        f"Context:\n{state.get('tool_answer')}\n\n"
        "Instructions:\n"
        "- Focus on producing one short factual answer.\n"
        "- You Should think enough at first before giving final answer. \n"
        "- Do not include tool names, prefixes, or metadata.\n"
        "- If the context contains partial hints, you can infer the answer from general knowledge of the same topic.\n"
        "- If the question asks about an attached file or audio, reply briefly that you cannot access attachments or audio files."
        "- If the context lacks key details or references a file, start with: I'm not sure because the question depends on missing data or an attached file. Then, add what you reasonably know about the topic."
        "- Final answer should be complete text not part of answer"
        "Final Answer:"
    )

    state["final_answer"] = get_gpt_and_answer(prompt)

    return state
    
def get_type(state: InfoState) -> InfoState:
    """Choose which tool to use based on question type (WIKI, SEARCH, CODE)."""

    print("Getting Type (Gemini/OpenAI)...")
    q = state["question"].lower()
    if any(w in q for w in ["calculate", "sum", "add", "price", "how much", "date", "time"]):
        state["answer_type"] = "MATH"
    elif any(w in q for w in ["latest", "current", "today", "news"]):
        state["answer_type"] = "WebInfo"
    elif any(w in q for w in ["who", "where", "what", "when", "why", "how many", "which"]):
        state["answer_type"] = "WIKI"
    else:
        try:
            prompt = f"""
            You are a strict classifier.
            Your job is to classify the following question into ONE of four categories:
        
            - WIKI → informative, factual, or science question
            - WebInfo → up-to-date, news, or current event question
            - MATH → math, numeric, date, or time calculation
            - LLM → all others, including links, reasoning, and file-related tasks
        
            Question: "{state['question']}"
        
            Rules:
            - Reply with exactly one of these words: WIKI, WebInfo, MATH, or LLM
            - Output nothing else. No punctuation, no quotes, no explanation.
            - If unsure, default to LLM.
            """
            resp = get_gpt_and_answer(prompt)
            state["answer_type"] = resp.split()[0].strip()
        except:
            state["answer_type"] = "LLM"
    return state




def get_search_results(state: InfoState) -> InfoState:
    """Tool to search web for results using DuckDuckGo."""
    print("Searching...")

    search = DuckDuckGoSearchRun()

    try:
      state['tool_answer'] = search.run(state["question"])   #" " .join(state["main_parts"]))
      return state
    except Exception:
      state['tool_answer'] = ""
      return state


def route(state: InfoState):
    print(state["answer_type"])
    return state["answer_type"]



################# Graph ################
def get_graph():
        graph = StateGraph(InfoState)

        # Add nodes
        #graph.add_node("get_wiki_relate", get_wiki_relate)
        graph.add_node("preprocess_text", preprocess_text)
        graph.add_node("get_answer", get_answer)
        graph.add_node("get_type", get_type)
        graph.add_node("get_search_results", get_search_results)
        graph.add_node("execute_code", execute_code)
        graph.add_node("get_code", get_code)

        # Add edges
        graph.add_edge(START, "preprocess_text")
        graph.add_edge("preprocess_text", "get_type")


        # Add conditional edges
        graph.add_conditional_edges(
            "get_type",
            route,
            {
                "WebInfo": "get_search_results",
                "WIKI": "get_search_results",#"get_wiki_relate",
                "MATH": "get_code",
                "LLM": "get_answer"
            }
        )

        # Add final edges
        graph.add_edge("get_search_results", "get_answer")
        #graph.add_edge("get_wiki_relate", "get_answer")

        graph.add_edge("get_code", "execute_code")
        graph.add_edge("execute_code", "get_answer")

        graph.add_edge("get_answer", END)


        # Compile the graph
        compiled_graph = graph.compile()
        return compiled_graph

def ask(compiled_graph,question):
        legitimate_result = compiled_graph.invoke({
            "question": question,

        })

        return legitimate_result['final_answer'] #,legitimate_result