File size: 4,499 Bytes
63f13b5
0ea9e1b
63f13b5
cfa3fb2
 
 
63f13b5
1488b1e
63f13b5
0ea9e1b
 
 
 
63f13b5
ba74371
713c540
 
 
 
 
 
 
0c774c2
d7bbf02
f53d974
63f13b5
 
 
0ea9e1b
cfa3fb2
 
 
 
63f13b5
ff3a79a
 
052837d
 
cfa3fb2
 
 
 
 
c932e96
57fcac6
713c540
 
 
 
0c774c2
713c540
a72ec7c
cfa3fb2
57fcac6
 
 
cfa3fb2
ff3a79a
a72ec7c
cfa3fb2
a72ec7c
cfa3fb2
052837d
 
 
 
a72ec7c
 
 
052837d
 
 
 
 
a72ec7c
 
cfa3fb2
 
 
 
 
 
 
 
 
 
 
 
a72ec7c
ff3a79a
 
 
a72ec7c
ff3a79a
 
cfa3fb2
 
 
 
 
 
 
 
 
 
 
a72ec7c
63f13b5
ff3a79a
63f13b5
 
 
6a63b68
63f13b5
 
 
a72ec7c
cfa3fb2
 
 
 
 
 
 
 
 
 
 
 
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
import os
import pandas as pd
from langchain_core.messages import HumanMessage, AIMessage
from langgraph.graph import StateGraph, END, MessagesState
from langchain_huggingface import HuggingFaceEndpoint
from tools import TOOLS

QA_PATH = "metadata.jsonl"
qa_pairs = pd.read_json(QA_PATH, lines=True)
qa_dict = {
    row["Question"].strip(): row["Final answer"].strip()
    for _, row in qa_pairs.iterrows()
}

def build_graph():
    llm = HuggingFaceEndpoint(
        endpoint_url="https://api-inference.huggingface.co/models/mistralai/Mistral-7B-Instruct-v0.3",
        task="text-generation",
        max_new_tokens=512,
        temperature=0.1,
        top_k=50,
        top_p=0.95,
        huggingfacehub_api_token=os.environ["HF_TOKEN"]
    )

    def retriever_node(state: MessagesState):
        query = state["messages"][-1].content.strip()
        if query in qa_dict:
            print("✅ Exact match found in retriever.")
            answer = qa_dict[query]
            return {"messages": [AIMessage(content=answer)]}
        print("🔍 No match. Passing to LLM.")
        return 

    def assistant_node(state: MessagesState):
        query = state["messages"][-1].content.strip()

        system_prompt = (
            "You are a helpful assistant. To answer the user's question, you can use tools. "
            "To use a tool, respond with a single line: 'tool:tool_name:input'. "
            "For example: 'tool:wiki_search:Apple Inc.' "
            "If you have the final answer, provide it directly without any prefixes. "
            "Never justify or explain your final answer."
        )
        
        messages = [
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": query}
        ]
        
        response = llm.invoke(messages).strip()

        for tag in ("", "Answer:", "assistant:"):
            if response.lower().startswith(tag.lower()):
                response = response[len(tag):].strip()
        
        return {"messages": [AIMessage(content=response)]}

    def tool_node(state: MessagesState):
        last_message = state["messages"][-1].content.strip()
        try:
            _, tool_name, tool_input = last_message.split(":", 2)
            tool_name = tool_name.strip()
            tool_input = tool_input.strip()
            tool_fn = TOOLS.get(tool_name)

            if not tool_fn:
                print(f"❌ Unknown tool: {tool_name}")
                return {"messages": [AIMessage(content="Unknown")]}
            
            print(f"🔧 Using tool: {tool_name} with input: {tool_input}")
            tool_result = tool_fn(tool_input)
            return {"messages": [AIMessage(content=str(tool_result))]}

        except Exception as e:
            print(f"⚠️ Tool error: {e}")
            return {"messages": [AIMessage(content="Unknown")]}

    def route_after_retriever(state: MessagesState):
        if isinstance(state["messages"][-1], AIMessage):
            return END
        return "assistant"

    def route_after_assistant(state: MessagesState):
        last_message = state["messages"][-1].content.strip().lower()
        if last_message.startswith("tool:"):
            return "tool"
        return END

    builder = StateGraph(MessagesState)
    builder.add_node("retriever", retriever_node)
    builder.add_node("assistant", assistant_node)
    builder.add_node("tool", tool_node)

    builder.set_entry_point("retriever")

    builder.add_conditional_edges(
        "retriever",
        route_after_retriever,
        {"assistant": "assistant", END: END}
    )
    builder.add_conditional_edges(
        "assistant",
        route_after_assistant,
        {"tool": "tool", END: END}
    )
    builder.add_edge("tool", "assistant")

    return builder.compile()

class BasicAgent:
    def __init__(self):
        print("✅ BasicAgent initialized with retriever + LLM + tools")
        self.graph = build_graph()

    def __call__(self, question: str) -> str:
        print(f"📥 Question: {question[:100]}")
        config = {"recursion_limit": 50}
        try:
            result = self.graph.invoke(
                {"messages": [HumanMessage(content=question)]},
                config=config
            )
            answer = result["messages"][-1].content.strip()
            print(f"📤 Answer: {answer}")
            return answer
        except Exception as e:
            print(f"Error during graph invocation: {e}")
            return f"AGENT ERROR: {e}"