mishrabp commited on
Commit
7a2fae0
·
verified ·
1 Parent(s): ae6b965

Upload folder using huggingface_hub

Browse files
README.md CHANGED
@@ -34,3 +34,11 @@ This chatbot helps you perform **market research tasks** using AI.
34
  ### Notes
35
  - Make sure your API keys are configured in the Space secrets
36
  - Built using Streamlit and deployed as a Docker Space
 
 
 
 
 
 
 
 
 
34
  ### Notes
35
  - Make sure your API keys are configured in the Space secrets
36
  - Built using Streamlit and deployed as a Docker Space
37
+
38
+ ### References
39
+
40
+ https://openai.github.io/openai-agents-python/
41
+
42
+
43
+ https://github.com/openai/openai-agents-python/tree/main/examples/mcp
44
+
appagents/InputValidationAgent.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from agents import Agent, OpenAIChatCompletionsModel, Runner, GuardrailFunctionOutput
3
+ from pydantic import BaseModel
4
+ import json
5
+ from openai import AsyncOpenAI
6
+
7
+ class ValidatedOutput(BaseModel):
8
+ is_valid: bool
9
+ reasoning: str
10
+
11
+ class InputValidationAgent:
12
+ """
13
+ Encapsulates the AI agent definition for conducting comprehensive web searches and synthesizing information.
14
+ """
15
+
16
+ @staticmethod
17
+ def create():
18
+ """
19
+ Returns a configured Agent instance ready for use.
20
+ """
21
+
22
+ instructions = """
23
+ You are a highly efficient and specialized **Agent** 🌐. Your sole function is to validate the user inputs.
24
+
25
+ ## Core Directives & Priorities
26
+ 1. You should flag if the user uses unparaliamentary language ONLY.
27
+ 2. You MUST give reasoning for the same.
28
+
29
+ ## Rules
30
+ - If it contains any of these, mark `"is_valid": false` and explain **why** in `"reasoning"`.
31
+ - Otherwise, mark `"is_valid": true` with reasoning like "The input follows respectful communication guidelines."
32
+
33
+
34
+ ## Output Format (MANDATORY)
35
+ * Return a JSON object with the following structure:
36
+ {
37
+ "is_valid": <boolean>,
38
+ "reasoning": <string>
39
+ }
40
+
41
+ """
42
+
43
+
44
+ GEMINI_BASE_URL = "https://generativelanguage.googleapis.com/v1beta/openai/"
45
+ google_api_key = os.getenv('GOOGLE_API_KEY')
46
+ gemini_client = AsyncOpenAI(base_url=GEMINI_BASE_URL, api_key=google_api_key)
47
+ gemini_model = OpenAIChatCompletionsModel(model="gemini-2.0-flash", openai_client=gemini_client)
48
+
49
+ agent = Agent(
50
+ name="Guardrail Input Validation Agent",
51
+ instructions=instructions,
52
+ model=gemini_model,
53
+ output_type=ValidatedOutput,
54
+ )
55
+ return agent
56
+
57
+ async def input_validation_guardrail(ctx, agent, input_data):
58
+ result = await Runner.run(InputValidationAgent.create(), input_data, context=ctx.context)
59
+ raw_output = result.final_output
60
+
61
+ # print("Raw Output from Guardrail Model:", raw_output)
62
+
63
+ # Handle different return shapes gracefully
64
+ if isinstance(raw_output, ValidatedOutput):
65
+ final_output = raw_output
66
+ print("Parsed ValidatedOutput:", final_output)
67
+ else:
68
+ final_output = ValidatedOutput(
69
+ is_valid=False,
70
+ reasoning=f"Unexpected output type: {type(raw_output)}"
71
+ )
72
+
73
+ return GuardrailFunctionOutput(
74
+ output_info=final_output,
75
+ tripwire_triggered=not final_output.is_valid,
76
+ )
appagents/OrchestratorAgent.py CHANGED
@@ -1,68 +1,167 @@
 
 
1
  from appagents.FinancialAgent import FinancialAgent
2
  from appagents.NewsAgent import NewsAgent
3
  from appagents.SearchAgent import SearchAgent
4
- import os
5
- from agents import Agent, OpenAIChatCompletionsModel
6
  from openai import AsyncOpenAI
7
 
 
8
  class OrchestratorAgent:
9
  """
10
- The OrchestratorAgent coordinates multiple specialized agents
11
- (financial, news, and search) to provide accurate and up-to-date
12
- market research insights.
13
  """
14
 
 
 
 
 
 
15
  @staticmethod
16
  def create(model: str = "gpt-4o-mini"):
17
  """
18
- Creates and returns a configured Agent instance with
19
- predefined instructions and connected sub-agents.
20
  """
21
 
22
- # Define sub-agents for specialized handoffs
23
  handoffs = [
24
  FinancialAgent.create(),
25
  NewsAgent.create(),
26
- SearchAgent.create()
27
  ]
28
 
29
- # Core behavioral and ethical instructions
30
  instructions = """
31
- You are an AI research assistant designed to deliver accurate,
32
- concise, and verifiable answers.
33
-
34
- **Your Core Priorities**
35
- 1. **Current Awareness:** Always use the Time Tool to maintain awareness
36
- of the current date and time for contextual accuracy.
37
- 2. **Accuracy and Recency:** Base all responses on the latest and most
38
- reliable data available.
39
- 3. **Tool Usage:** For questions involving current events, statistics,
40
- or factual updates, invoke the appropriate tools (Search, News, APIs)
41
- to confirm or refresh information before responding.
42
- 4. **Synthesis:** When using multiple data sources, integrate findings
43
- into a coherent, factual, and reader-friendly summary.
44
- 5. **Source Transparency:** Always cite your sources clearly (e.g., URLs,
45
- named publications, or APIs).
46
- 6. **Clarity and Brevity:** Use professional, direct language. Avoid
47
- speculation, filler text, or redundancy.
48
- 7. **Simplification:** Present complex topics in simple, accessible terms
49
- suitable for a general audience.
50
- 8. **Verification:** If information cannot be confirmed, explicitly state
51
- that the data could not be verified or is unavailable.
52
-
53
- ⚠️ Do **not fabricate** or infer details without verifiable evidence.
54
  """
55
 
 
56
  GEMINI_BASE_URL = "https://generativelanguage.googleapis.com/v1beta/openai/"
57
- google_api_key = os.getenv('GOOGLE_API_KEY')
58
  gemini_client = AsyncOpenAI(base_url=GEMINI_BASE_URL, api_key=google_api_key)
59
- gemini_model = OpenAIChatCompletionsModel(model="gemini-2.0-flash", openai_client=gemini_client)
 
 
 
60
 
61
- # Create and return the configured agent
62
  agent = Agent(
63
  name="AI Market Research Assistant",
64
  handoffs=handoffs,
65
  instructions=instructions.strip(),
66
- model=gemini_model
 
 
 
 
 
 
67
  )
 
 
 
68
  return agent
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import asyncio
3
  from appagents.FinancialAgent import FinancialAgent
4
  from appagents.NewsAgent import NewsAgent
5
  from appagents.SearchAgent import SearchAgent
6
+ from appagents.InputValidationAgent import input_validation_guardrail
7
+ from agents import Agent, OpenAIChatCompletionsModel, InputGuardrail
8
  from openai import AsyncOpenAI
9
 
10
+
11
  class OrchestratorAgent:
12
  """
13
+ The OrchestratorAgent coordinates multiple specialized sub-agents
14
+ (Financial, News, and Search) to provide accurate, up-to-date,
15
+ and well-routed market research insights.
16
  """
17
 
18
+ MAX_RETRIES = 2
19
+
20
+ # ----------------------------------------------------------
21
+ # MAIN CREATION METHOD
22
+ # ----------------------------------------------------------
23
  @staticmethod
24
  def create(model: str = "gpt-4o-mini"):
25
  """
26
+ Creates and returns a configured Orchestrator agent.
 
27
  """
28
 
29
+ # --- Sub-agent setup ---
30
  handoffs = [
31
  FinancialAgent.create(),
32
  NewsAgent.create(),
33
+ SearchAgent.create(),
34
  ]
35
 
36
+ # --- Behavioral instructions ---
37
  instructions = """
38
+ You are the Orchestrator Agent responsible for coordinating specialized sub-agents
39
+ to generate accurate and well-rounded market research responses.
40
+
41
+ **Your Core Responsibilities**
42
+ 1. **Task Routing:** Determine which sub-agent (Financial, News, or Search) is best suited
43
+ to handle each user query based on intent and context.
44
+ 2. **Delegation:** Forward the request to the appropriate sub-agent and wait for its result.
45
+ 3. **Synthesis:** When multiple agents provide responses, summarize and merge their findings
46
+ into a clear, concise, and accurate overall answer.
47
+ 4. **Recency and Accuracy:** Prioritize the most up-to-date, verifiable data from sub-agents.
48
+ 5. **Transparency:** Clearly identify which insights came from which sub-agent when relevant.
49
+ 6. **Error Handling:** If a sub-agent fails or provides insufficient data, attempt fallback
50
+ strategies such as rerouting the query or notifying the user.
51
+ 7. **Clarity:** Always present the final response in a professional, well-structured,
52
+ and easy-to-understand format.
53
+
54
+ ⚠️ Do **not** perform the underlying data analysis or external lookup yourself —
55
+ ALWAYS delegate those tasks to the respective sub-agents.
 
 
 
 
 
56
  """
57
 
58
+ # --- Model setup ---
59
  GEMINI_BASE_URL = "https://generativelanguage.googleapis.com/v1beta/openai/"
60
+ google_api_key = os.getenv("GOOGLE_API_KEY")
61
  gemini_client = AsyncOpenAI(base_url=GEMINI_BASE_URL, api_key=google_api_key)
62
+ gemini_model = OpenAIChatCompletionsModel(
63
+ model="gemini-2.0-flash",
64
+ openai_client=gemini_client
65
+ )
66
 
67
+ # --- Create orchestrator agent ---
68
  agent = Agent(
69
  name="AI Market Research Assistant",
70
  handoffs=handoffs,
71
  instructions=instructions.strip(),
72
+ model=gemini_model,
73
+ # input_guardrails=[
74
+ # InputGuardrail(
75
+ # name="Input Validation Guardrail",
76
+ # guardrail_function=input_validation_guardrail,
77
+ # )
78
+ # ],
79
  )
80
+
81
+ # Attach orchestration logic
82
+ agent.respond = lambda prompt: OrchestratorAgent.respond(prompt, handoffs, gemini_model)
83
  return agent
84
+
85
+ # ----------------------------------------------------------
86
+ # RESPONSE HANDLING + SELF-CORRECTION
87
+ # ----------------------------------------------------------
88
+ @staticmethod
89
+ async def respond(prompt: str, handoffs: list, model) -> str:
90
+ """
91
+ Routes prompt to the most relevant agent, retries if output seems irrelevant.
92
+ """
93
+ attempted_agents = set()
94
+
95
+ for attempt in range(OrchestratorAgent.MAX_RETRIES):
96
+ # Step 1: Route intelligently
97
+ chosen_agent = await OrchestratorAgent._route_to_agent(prompt, handoffs, attempted_agents)
98
+ if not chosen_agent:
99
+ return "⚠️ No available agent could handle this query."
100
+
101
+ print(f"🤖 Attempt {attempt+1}: Sending query to {chosen_agent.name}")
102
+
103
+ # Step 2: Run agent
104
+ try:
105
+ response = await chosen_agent.run(prompt)
106
+ except Exception as e:
107
+ print(f"⚠️ Agent {chosen_agent.name} failed: {e}")
108
+ attempted_agents.add(chosen_agent.name)
109
+ continue
110
+
111
+ # Step 3: Evaluate if relevant
112
+ if await OrchestratorAgent._is_relevant(prompt, response, model):
113
+ return f"✅ {chosen_agent.name} handled this successfully:\n\n{response}"
114
+
115
+ print(f"🔁 {chosen_agent.name}'s response deemed irrelevant. Re-routing...")
116
+ attempted_agents.add(chosen_agent.name)
117
+
118
+ return "⚠️ Could not find a relevant answer after multiple attempts."
119
+
120
+ # ----------------------------------------------------------
121
+ # ROUTING LOGIC
122
+ # ----------------------------------------------------------
123
+ @staticmethod
124
+ async def _route_to_agent(prompt: str, handoffs: list, attempted_agents: set):
125
+ """
126
+ Determines the best-fit agent for the given prompt.
127
+ Avoids previously tried agents.
128
+ """
129
+ lowered = prompt.lower()
130
+ available = [a for a in handoffs if a.name not in attempted_agents]
131
+
132
+ if not available:
133
+ return None
134
+
135
+ if any(k in lowered for k in ["finance", "stock", "market", "earnings"]):
136
+ return next((a for a in available if "financial" in a.name.lower()), available[0])
137
+ elif any(k in lowered for k in ["news", "headline", "press release"]):
138
+ return next((a for a in available if "news" in a.name.lower()), available[0])
139
+ elif any(k in lowered for k in ["search", "find", "lookup", "discover"]):
140
+ return next((a for a in available if "search" in a.name.lower()), available[0])
141
+ else:
142
+ # fallback — first available agent
143
+ return available[0]
144
+
145
+ # ----------------------------------------------------------
146
+ # LLM-BASED EVALUATOR
147
+ # ----------------------------------------------------------
148
+ @staticmethod
149
+ async def _is_relevant(prompt: str, response: str, model) -> bool:
150
+ """
151
+ Uses the model itself to check if the response matches the prompt intent.
152
+ """
153
+ eval_prompt = f"""
154
+ You are an evaluator checking multi-agent responses.
155
+ User asked: "{prompt}"
156
+ Agent responded: "{response}"
157
+
158
+ Does this response accurately and completely answer the user's intent?
159
+ Reply with only 'yes' or 'no'.
160
+ """
161
+ try:
162
+ eval_result = await model.run(eval_prompt)
163
+ print(f"🧠 Evaluation result: {eval_result}")
164
+ return "yes" in eval_result.lower()
165
+ except Exception as e:
166
+ print(f"⚠️ Evaluation failed: {e}")
167
+ return False
appagents/SearchAgent.py CHANGED
@@ -1,6 +1,5 @@
1
  from tools.google_tools import GoogleTools
2
  from tools.time_tools import TimeTools
3
- from agents import Agent, WebSearchTool # Assuming WebSearchTool is the same as GoogleTools
4
  import os
5
  from agents import Agent, OpenAIChatCompletionsModel
6
  from openai import AsyncOpenAI
 
1
  from tools.google_tools import GoogleTools
2
  from tools.time_tools import TimeTools
 
3
  import os
4
  from agents import Agent, OpenAIChatCompletionsModel
5
  from openai import AsyncOpenAI
requirements.txt CHANGED
@@ -1,11 +1,11 @@
1
- openai>=1.85.0
2
  # via
3
  # agents (pyproject.toml)
4
  # autogen-ext
5
  # langchain-openai
6
  # openai-agents
7
  # semantic-kernel
8
- openai-agents>=0.0.17
9
  # via agents (pyproject.toml)
10
  python-dotenv>=1.0.1
11
  requests>=2.31.0
 
1
+ openai>=2.6.1
2
  # via
3
  # agents (pyproject.toml)
4
  # autogen-ext
5
  # langchain-openai
6
  # openai-agents
7
  # semantic-kernel
8
+ openai-agents>=0.4.2
9
  # via agents (pyproject.toml)
10
  python-dotenv>=1.0.1
11
  requests>=2.31.0
ui/__init__.py ADDED
File without changes
ui/app.py CHANGED
@@ -9,6 +9,8 @@ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
9
 
10
  from appagents.OrchestratorAgent import OrchestratorAgent
11
  from agents import Runner, trace, SQLiteSession
 
 
12
 
13
  # -----------------------------
14
  # Load predefined prompts
@@ -165,20 +167,39 @@ if "auto_send_prompt" not in st.session_state:
165
  st.session_state.auto_send_prompt = None
166
 
167
  # Create (or reuse) a persistent SQLite session
 
 
 
 
 
 
 
 
 
168
  if "ai_session" not in st.session_state:
169
- st.session_state.ai_session = SQLiteSession("conversation_123")
170
 
171
  session = st.session_state.ai_session
172
 
173
 
 
174
  # -----------------------------
175
  # Async AI response
176
  # -----------------------------
177
  async def get_ai_response(prompt: str) -> str:
178
- agent = OrchestratorAgent.create()
179
- with trace("Chatbot Search Agent Run"):
180
- result = await Runner.run(agent, prompt, session=session)
181
- return result.final_output
 
 
 
 
 
 
 
 
 
182
 
183
  # -----------------------------
184
  # Desktop Sidebar Quick Prompts
 
9
 
10
  from appagents.OrchestratorAgent import OrchestratorAgent
11
  from agents import Runner, trace, SQLiteSession
12
+ from agents.exceptions import InputGuardrailTripwireTriggered
13
+
14
 
15
  # -----------------------------
16
  # Load predefined prompts
 
167
  st.session_state.auto_send_prompt = None
168
 
169
  # Create (or reuse) a persistent SQLite session
170
+ import uuid
171
+
172
+ # Generate a unique session ID for this browser session
173
+ if "ai_session_id" not in st.session_state:
174
+ st.session_state.ai_session_id = str(uuid.uuid4())
175
+
176
+ session_id = st.session_state.ai_session_id
177
+
178
+ # Create a unique SQLite session per user
179
  if "ai_session" not in st.session_state:
180
+ st.session_state.ai_session = SQLiteSession(f"conversation_{session_id}.db")
181
 
182
  session = st.session_state.ai_session
183
 
184
 
185
+
186
  # -----------------------------
187
  # Async AI response
188
  # -----------------------------
189
  async def get_ai_response(prompt: str) -> str:
190
+ try:
191
+ agent = OrchestratorAgent.create()
192
+ with trace("Chatbot Search Agent Run"):
193
+ result = await Runner.run(agent, prompt, session=session)
194
+ return result.final_output
195
+ except InputGuardrailTripwireTriggered as e:
196
+ reasoning = getattr(e, "reasoning", None) \
197
+ or getattr(getattr(e, "output", None), "reasoning", None) \
198
+ or getattr(getattr(e, "guardrail_output", None), "reasoning", None) \
199
+ or "Guardrail triggered, but no reasoning provided."
200
+
201
+ return f"⚠️ Guardrail Blocked Input:\n\n**Reason:** {reasoning}"
202
+
203
 
204
  # -----------------------------
205
  # Desktop Sidebar Quick Prompts