surfiniaburger commited on
Commit
3206264
·
1 Parent(s): 862b712
Files changed (3) hide show
  1. app.py +160 -128
  2. miscellaneous/instructions.md +258 -0
  3. tools.py +69 -0
app.py CHANGED
@@ -1,16 +1,11 @@
1
  # ==============================================================================
2
- # Aura Mind Glow - Field Mode MVP v1.0
3
  # ==============================================================================
4
  """
5
- This script launches a 100% offline-capable Gradio application for maize health diagnosis.
6
- It is the foundational MVP for the Aura Mind Glow project.
7
-
8
- Workflow:
9
- 1. A fine-tuned Unsloth/Gemma-3N vision model analyzes an uploaded image to
10
- diagnose the plant's condition (e.g., "Phosphorus Deficiency").
11
- 2. A local RAG (Retrieval-Augmented Generation) pipeline uses the diagnosis
12
- text to search an in-memory knowledge base for relevant remedies.
13
- 3. The diagnosis and the retrieved remedies are displayed to the user.
14
  """
15
 
16
  # --- Step 0: Essential Imports ---
@@ -19,6 +14,8 @@ import torch
19
  from PIL import Image
20
  import os
21
  import warnings
 
 
22
 
23
  # Suppress potential warnings for a cleaner console
24
  warnings.filterwarnings("ignore")
@@ -28,23 +25,33 @@ os.environ["TORCH_COMPILE_DISABLE"] = "1" # Ensure torch compile is off
28
  from unsloth import FastVisionModel
29
  from transformers import AutoProcessor
30
 
31
- # LangChain and RAG components for the local knowledge base
32
  from langchain_community.vectorstores import FAISS
33
  from langchain_community.document_loaders import TextLoader
34
  from langchain_huggingface import HuggingFaceEmbeddings
35
  from langchain.text_splitter import RecursiveCharacterTextSplitter
36
 
 
 
 
 
 
 
 
 
37
  print("✅ All libraries imported successfully.")
38
 
39
- # --- Step 1: Global Setup - Vision Model (The 'Diagnoser') ---
40
  # This expensive setup runs only ONCE when the application starts.
41
 
42
- print("Performing initial setup for the Vision Model...")
43
  VISION_MODEL = None
44
  PROCESSOR = None
 
45
  ADAPTER_PATH = "surfiniaburger/maize-health-diagnosis-adapter"
46
 
47
  try:
 
48
  VISION_MODEL, PROCESSOR = FastVisionModel.from_pretrained(
49
  model_name="unsloth/gemma-3n-E2B-it-unsloth-bnb-4bit",
50
  max_seq_length=2048,
@@ -55,18 +62,7 @@ try:
55
  VISION_MODEL.load_adapter(ADAPTER_PATH)
56
  print(f"✅ Vision model and adapter '{ADAPTER_PATH}' loaded successfully!")
57
 
58
- except Exception as e:
59
- print(f"❌ CRITICAL ERROR during vision model loading: {e}")
60
- # We will allow the app to launch so the user can see the error in the UI
61
- pass
62
-
63
- # --- Step 2: Global Setup - RAG Knowledge Base (The 'Remedy Retriever') ---
64
- # We create an in-memory vector store with remedy information. This also runs only once.
65
-
66
- print("Building the RAG knowledge base for remedies...")
67
- RETRIEVER = None
68
- try:
69
- # This text is our local, offline knowledge base.
70
  remedy_knowledge_text = """
71
  # Maize Health Guide
72
  ## Phosphorus Deficiency
@@ -89,132 +85,168 @@ try:
89
  **Symptoms:** Vigorous growth with lush, uniformly dark green leaves. No visible spots, lesions, or discoloration.
90
  **Local Remedy:** No remedy needed. Maintain good agricultural practices, including proper watering, fertilization, and pest management to keep the plant healthy.
91
  """
92
-
93
- # Use LangChain to load and process the text
94
  with open("knowledge.txt", "w") as f:
95
  f.write(remedy_knowledge_text)
96
-
97
  loader = TextLoader("knowledge.txt")
98
  documents = loader.load()
99
  text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
100
  docs = text_splitter.split_documents(documents)
101
  embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
102
  db = FAISS.from_documents(docs, embeddings)
103
-
104
- # Create the retriever object that will find the relevant remedies
105
- RETRIEVER = db.as_retriever(search_kwargs={"k": 1}) # Retrieve the single most relevant remedy
106
  print("✅ RAG knowledge base and retriever created successfully!")
107
 
108
  except Exception as e:
109
- print(f"❌ CRITICAL ERROR during RAG setup: {e}")
110
- # Allow app to launch to show error
111
  pass
112
 
113
- # --- Step 3: Define the Core Functions for the Gradio App ---
114
-
115
- def diagnose_plant_from_image(uploaded_image: Image.Image) -> str:
116
- """
117
- Takes a PIL Image and runs the fine-tuned vision model to get a diagnosis.
118
- """
119
- if VISION_MODEL is None or PROCESSOR is None or uploaded_image is None:
120
- raise gr.Error("Vision model is not loaded. Cannot perform diagnosis. Check logs.")
121
 
122
- image = uploaded_image.convert("RGB")
123
- messages = [
124
- {"role": "user", "content": [{"type": "text", "text": "What is the condition of this maize plant? Provide only the name of the condition."}, {"type": "image", "image": image}]}
125
- ]
126
- text_prompt = PROCESSOR.tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
127
- inputs = PROCESSOR(text=text_prompt, images=image, return_tensors="pt").to(VISION_MODEL.device)
128
 
129
- with torch.inference_mode():
130
- outputs = VISION_MODEL.generate(**inputs, max_new_tokens=48, use_cache=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
 
132
- response = PROCESSOR.batch_decode(outputs, skip_special_tokens=True)[0]
133
-
134
- # Clean up the model's output to get only the diagnosis
135
- # This logic specifically finds the text after "model\n"
136
- answer_start_index = response.rfind("model\n")
137
- if answer_start_index != -1:
138
- return response[answer_start_index + len("model\n"):
139
- ].strip()
140
-
141
- # Fallback for a different output format
142
- answer_start_index = response.rfind("assistant\n")
143
- if answer_start_index != -1:
144
- return response[answer_start_index + len("assistant\n"):
145
- ].strip()
146
 
147
- return "Could not parse diagnosis from model output."
148
 
 
149
 
150
- def get_diagnosis_and_remedy(uploaded_image: Image.Image) -> str:
151
- """
152
- This is the main function for the Gradio interface. It orchestrates the
153
- 100% offline diagnosis-to-remedy workflow.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  """
155
- if uploaded_image is None:
156
- return "Please upload an image of a maize plant first."
157
- if RETRIEVER is None:
158
- raise gr.Error("Knowledge base is not loaded. Cannot find remedy. Check logs.")
159
-
160
- try:
161
- # Step 1: Get the initial diagnosis from the vision model.
162
- print("Workflow Step 1: Running diagnosis...")
163
- diagnosis = diagnose_plant_from_image(uploaded_image)
164
- print(f"Diagnosis received: {diagnosis}")
165
-
166
- if "Could not parse" in diagnosis:
167
- return f"Sorry, I couldn't identify the condition from the image. Raw output: {diagnosis}"
168
-
169
- # Step 2: Retrieve remedy from the local knowledge base.
170
- print(f"Workflow Step 2: Retrieving remedy for '{diagnosis}' from local DB...")
171
- retrieved_docs = RETRIEVER.invoke(diagnosis)
172
-
173
- # Step 3: Format the output for the user.
174
- remedy_text = retrieved_docs[0].page_content if retrieved_docs else "No specific remedy found in the knowledge base for this condition."
175
-
176
- final_response = f"""
177
- ## Diagnosis Report
178
-
179
- **Condition Identified:**
180
- ### {diagnosis}
181
-
182
- ---
183
 
184
- ## Suggested Remedy
185
-
186
- {remedy_text}
187
- """
188
- print("Workflow complete. Returning response.")
189
- return final_response
190
-
191
- except Exception as e:
192
- print(f"An error occurred during the analysis workflow: {e}")
193
- raise gr.Error(f"An unexpected error occurred: {e}")
194
-
195
-
196
- # --- Step 4: Build and Launch the Gradio Interface ---
197
 
198
- print("Building Gradio interface...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
 
200
- # Custom CSS for a cleaner look
201
- css = """
202
- footer {visibility: hidden !important;}
203
- .gradio-container {font-family: 'IBM Plex Sans', sans-serif;}
204
- """
205
 
206
- demo = gr.Interface(
207
- fn=get_diagnosis_and_remedy,
208
- inputs=gr.Image(type="pil", label="Upload Maize Plant Image", sources=["upload", "webcam"]),
209
- outputs=gr.Markdown(label="Diagnosis and Remedy Report", value="The report will appear here..."),
210
- title="🌽 Aura Mind Glow: Field Mode MVP",
211
- description="**A 100% Offline-Capable Farming Assistant.** Upload an image of a maize plant. The AI will diagnose its condition using a fine-tuned model and retrieve a treatment plan from its local knowledge base.",
212
- article="<p style='text-align: center;'>Built with Unsloth, LangChain, and Gradio. Version 1.0</p>",
213
- allow_flagging="never",
214
- theme=gr.themes.Soft(primary_hue="teal", secondary_hue="orange"),
215
- css=css
216
- )
217
 
218
  if __name__ == "__main__":
219
- print("Launching Gradio app...")
220
- demo.launch(share=True, debug=True)
 
 
 
 
 
 
 
1
  # ==============================================================================
2
+ # Aura Mind Glow - Main Application
3
  # ==============================================================================
4
  """
5
+ This script launches the Aura Mind Glow application.
6
+ It features two modes:
7
+ 1. Field Mode (100% Offline): For quick diagnosis and remedy retrieval.
8
+ 2. Connected Mode (Online): A conversational agent powered by the Google ADK.
 
 
 
 
 
9
  """
10
 
11
  # --- Step 0: Essential Imports ---
 
14
  from PIL import Image
15
  import os
16
  import warnings
17
+ import socket
18
+ import asyncio
19
 
20
  # Suppress potential warnings for a cleaner console
21
  warnings.filterwarnings("ignore")
 
25
  from unsloth import FastVisionModel
26
  from transformers import AutoProcessor
27
 
28
+ # LangChain and RAG components
29
  from langchain_community.vectorstores import FAISS
30
  from langchain_community.document_loaders import TextLoader
31
  from langchain_huggingface import HuggingFaceEmbeddings
32
  from langchain.text_splitter import RecursiveCharacterTextSplitter
33
 
34
+ # Google ADK
35
+ from google.adk import Agent, Runner
36
+ from google.adk.sessions import InMemorySessionService
37
+ from google.genai import types
38
+
39
+ # Custom Tools
40
+ from tools import PlantDiagnosisTool, RemedyRetrievalTool
41
+
42
  print("✅ All libraries imported successfully.")
43
 
44
+ # --- Step 1: Global Setup - Vision Model and RAG ---
45
  # This expensive setup runs only ONCE when the application starts.
46
 
47
+ print("Performing initial setup for Vision Model and RAG...")
48
  VISION_MODEL = None
49
  PROCESSOR = None
50
+ RETRIEVER = None
51
  ADAPTER_PATH = "surfiniaburger/maize-health-diagnosis-adapter"
52
 
53
  try:
54
+ # Load Vision Model
55
  VISION_MODEL, PROCESSOR = FastVisionModel.from_pretrained(
56
  model_name="unsloth/gemma-3n-E2B-it-unsloth-bnb-4bit",
57
  max_seq_length=2048,
 
62
  VISION_MODEL.load_adapter(ADAPTER_PATH)
63
  print(f"✅ Vision model and adapter '{ADAPTER_PATH}' loaded successfully!")
64
 
65
+ # Build RAG Knowledge Base
 
 
 
 
 
 
 
 
 
 
 
66
  remedy_knowledge_text = """
67
  # Maize Health Guide
68
  ## Phosphorus Deficiency
 
85
  **Symptoms:** Vigorous growth with lush, uniformly dark green leaves. No visible spots, lesions, or discoloration.
86
  **Local Remedy:** No remedy needed. Maintain good agricultural practices, including proper watering, fertilization, and pest management to keep the plant healthy.
87
  """
 
 
88
  with open("knowledge.txt", "w") as f:
89
  f.write(remedy_knowledge_text)
 
90
  loader = TextLoader("knowledge.txt")
91
  documents = loader.load()
92
  text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
93
  docs = text_splitter.split_documents(documents)
94
  embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
95
  db = FAISS.from_documents(docs, embeddings)
96
+ RETRIEVER = db.as_retriever(search_kwargs={"k": 1})
 
 
97
  print("✅ RAG knowledge base and retriever created successfully!")
98
 
99
  except Exception as e:
100
+ print(f"❌ CRITICAL ERROR during global setup: {e}")
 
101
  pass
102
 
103
+ # --- Step 2: Initialize ADK Tools and Agent ---
 
 
 
 
 
 
 
104
 
105
+ print("Initializing ADK Tools and Agent...")
106
+ DIAGNOSIS_TOOL = None
107
+ REMEDY_TOOL = None
108
+ ADK_AGENT = None
109
+ ADK_RUNNER = None
110
+ SESSION_SERVICE = None
111
 
112
+ try:
113
+ if VISION_MODEL and PROCESSOR and RETRIEVER:
114
+ DIAGNOSIS_TOOL = PlantDiagnosisTool(model=VISION_MODEL, processor=PROCESSOR)
115
+ REMEDY_TOOL = RemedyRetrievalTool(retriever=RETRIEVER)
116
+
117
+ ADK_AGENT = Agent(
118
+ name="AuraMindGlowAgent",
119
+ model="gemini-1.5-flash-001",
120
+ description="A farming assistant that can diagnose plant health and suggest remedies.",
121
+ instruction="You are a friendly farming assistant. Your goal is to help users identify plant health issues and find solutions. Use your tools to diagnose the plant from an image and then find a remedy.",
122
+ tools=[DIAGNOSIS_TOOL, REMEDY_TOOL]
123
+ )
124
+
125
+ SESSION_SERVICE = InMemorySessionService()
126
+ ADK_RUNNER = Runner(agent=ADK_AGENT, app_name="AuraMindGlow", session_service=SESSION_SERVICE)
127
+ print("✅ ADK Agent and Runner initialized successfully!")
128
+ else:
129
+ print("❌ Skipping ADK setup due to errors in model or RAG loading.")
130
 
131
+ except Exception as e:
132
+ print(f"❌ CRITICAL ERROR during ADK setup: {e}")
133
+ pass
 
 
 
 
 
 
 
 
 
 
 
134
 
 
135
 
136
+ # --- Step 3: Define Gradio UIs ---
137
 
138
+ def create_field_mode_ui():
139
+ """Creates the Gradio UI for the offline Field Mode."""
140
+
141
+ def get_diagnosis_and_remedy(uploaded_image: Image.Image) -> str:
142
+ if uploaded_image is None:
143
+ return "Please upload an image of a maize plant first."
144
+ if RETRIEVER is None:
145
+ raise gr.Error("Knowledge base is not loaded. Cannot find remedy. Check logs.")
146
+
147
+ try:
148
+ diagnosis_tool = PlantDiagnosisTool(model=VISION_MODEL, processor=PROCESSOR)
149
+ diagnosis = diagnosis_tool(uploaded_image)
150
+ print(f"Diagnosis received: {diagnosis}")
151
+
152
+ if "Could not parse" in diagnosis:
153
+ return f"Sorry, I couldn't identify the condition from the image. Raw output: {diagnosis}"
154
+
155
+ remedy_tool = RemedyRetrievalTool(retriever=RETRIEVER)
156
+ remedy = remedy_tool(diagnosis)
157
+
158
+ final_response = f"""
159
+ ## Diagnosis Report
160
+
161
+ **Condition Identified:**
162
+ ### {diagnosis}
163
+
164
+ ---
165
+
166
+ ## Suggested Remedy
167
+
168
+ {remedy}
169
+ """
170
+ print("Workflow complete. Returning response.")
171
+ return final_response
172
+
173
+ except Exception as e:
174
+ print(f"An error occurred during the analysis workflow: {e}")
175
+ raise gr.Error(f"An unexpected error occurred: {e}")
176
+
177
+ css = """
178
+ footer {visibility: hidden !important;}
179
+ .gradio-container {font-family: 'IBM Plex Sans', sans-serif;}
180
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
 
182
+ return gr.Interface(
183
+ fn=get_diagnosis_and_remedy,
184
+ inputs=gr.Image(type="pil", label="Upload Maize Plant Image", sources=["upload", "webcam"]),
185
+ outputs=gr.Markdown(label="Diagnosis and Remedy Report", value="The report will appear here..."),
186
+ title="🌽 Aura Mind Glow: Field Mode (Offline)",
187
+ description="**A 100% Offline-Capable Farming Assistant.** Upload an image of a maize plant. The AI will diagnose its condition and retrieve a treatment plan from its local knowledge base.",
188
+ article="<p style='text-align: center;'>Built with Unsloth, LangChain, and Gradio. Version 1.1</p>",
189
+ allow_flagging="never",
190
+ theme=gr.themes.Soft(primary_hue="teal", secondary_hue="orange"),
191
+ css=css
192
+ )
 
 
193
 
194
+ def create_connected_mode_ui():
195
+ """Creates the Gradio UI for the online Connected Mode."""
196
+ with gr.Blocks(theme=gr.themes.Soft(primary_hue="green", secondary_hue="lime")) as demo:
197
+ gr.Markdown("# 🌽 Aura Mind Glow: Connected Mode 🤖")
198
+ gr.Markdown("I am an AI farming assistant, powered by Google's ADK. I can help you diagnose plant health, get remedies, and more. How can I help you today?")
199
+
200
+ chatbot = gr.Chatbot()
201
+ msg = gr.Textbox(label="Your message")
202
+ clear = gr.Button("Clear")
203
+
204
+ async def user(user_message, history):
205
+ # This is a placeholder for image handling
206
+ # In a real implementation, we would handle image uploads here
207
+ return "", history + [[user_message, None]]
208
+
209
+ async def bot(history):
210
+ user_message = history[-1][0]
211
+
212
+ # Create a session for the user
213
+ session = await SESSION_SERVICE.create_session(app_name="AuraMindGlow", user_id="user123", session_id="session123")
214
+ content = types.Content(role='user', parts=[types.Part(text=user_message)])
215
+
216
+ # Stream the agent's response
217
+ events = ADK_RUNNER.run_async(user_id="user123", session_id="session123", new_message=content)
218
+
219
+ history[-1][1] = ""
220
+ async for event in events:
221
+ if event.is_final_response():
222
+ final_response = event.content.parts[0].text
223
+ history[-1][1] = final_response
224
+ yield history
225
+
226
+ msg.submit(user, [msg, chatbot], [msg, chatbot], queue=False).then(
227
+ bot, chatbot, chatbot
228
+ )
229
+ clear.click(lambda: None, None, chatbot, queue=False)
230
+
231
+ return demo
232
 
233
+ # --- Step 4: App Launcher ---
 
 
 
 
234
 
235
+ def check_internet_connection(host="8.8.8.8", port=53, timeout=3):
236
+ """Check for internet connectivity."""
237
+ try:
238
+ socket.setdefaulttimeout(timeout)
239
+ socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect((host, port))
240
+ return True
241
+ except socket.error:
242
+ return False
 
 
 
243
 
244
  if __name__ == "__main__":
245
+ if check_internet_connection() and ADK_RUNNER:
246
+ print("✅ Internet connection detected. Launching Connected Mode.")
247
+ ui = create_connected_mode_ui()
248
+ else:
249
+ print("❌ No internet connection or ADK setup failed. Launching Field Mode (Offline).")
250
+ ui = create_field_mode_ui()
251
+
252
+ ui.launch(share=True, debug=True)
miscellaneous/instructions.md CHANGED
@@ -2872,3 +2872,261 @@ Next
2872
  Built-in tools
2873
  Copyright Google 2025
2874
  Made with Material for MkDocs
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2872
  Built-in tools
2873
  Copyright Google 2025
2874
  Made with Material for MkDocs
2875
+
2876
+
2877
+
2878
+ Third Party Tools¶
2879
+ python_only
2880
+
2881
+ ADK is designed to be highly extensible, allowing you to seamlessly integrate tools from other AI Agent frameworks like CrewAI and LangChain. This interoperability is crucial because it allows for faster development time and allows you to reuse existing tools.
2882
+
2883
+ 1. Using LangChain Tools¶
2884
+ ADK provides the LangchainTool wrapper to integrate tools from the LangChain ecosystem into your agents.
2885
+
2886
+ Example: Web Search using LangChain's Tavily tool¶
2887
+ Tavily provides a search API that returns answers derived from real-time search results, intended for use by applications like AI agents.
2888
+
2889
+ Follow ADK installation and setup guide.
2890
+
2891
+ Install Dependencies: Ensure you have the necessary LangChain packages installed. For example, to use the Tavily search tool, install its specific dependencies:
2892
+
2893
+
2894
+ pip install langchain_community tavily-python
2895
+ Obtain a Tavily API KEY and export it as an environment variable.
2896
+
2897
+
2898
+ export TAVILY_API_KEY=<REPLACE_WITH_API_KEY>
2899
+ Import: Import the LangchainTool wrapper from ADK and the specific LangChain tool you wish to use (e.g, TavilySearchResults).
2900
+
2901
+
2902
+ from google.adk.tools.langchain_tool import LangchainTool
2903
+ from langchain_community.tools import TavilySearchResults
2904
+ Instantiate & Wrap: Create an instance of your LangChain tool and pass it to the LangchainTool constructor.
2905
+
2906
+
2907
+ # Instantiate the LangChain tool
2908
+ tavily_tool_instance = TavilySearchResults(
2909
+ max_results=5,
2910
+ search_depth="advanced",
2911
+ include_answer=True,
2912
+ include_raw_content=True,
2913
+ include_images=True,
2914
+ )
2915
+
2916
+ # Wrap it with LangchainTool for ADK
2917
+ adk_tavily_tool = LangchainTool(tool=tavily_tool_instance)
2918
+ Add to Agent: Include the wrapped LangchainTool instance in your agent's tools list during definition.
2919
+
2920
+
2921
+ from google.adk import Agent
2922
+
2923
+ # Define the ADK agent, including the wrapped tool
2924
+ my_agent = Agent(
2925
+ name="langchain_tool_agent",
2926
+ model="gemini-2.0-flash",
2927
+ description="Agent to answer questions using TavilySearch.",
2928
+ instruction="I can answer your questions by searching the internet. Just ask me anything!",
2929
+ tools=[adk_tavily_tool] # Add the wrapped tool here
2930
+ )
2931
+ Full Example: Tavily Search¶
2932
+ Here's the full code combining the steps above to create and run an agent using the LangChain Tavily search tool.
2933
+
2934
+
2935
+ # Copyright 2025 Google LLC
2936
+ #
2937
+ # Licensed under the Apache License, Version 2.0 (the "License");
2938
+ # you may not use this file except in compliance with the License.
2939
+ # You may obtain a copy of the License at
2940
+ #
2941
+ # http://www.apache.org/licenses/LICENSE-2.0
2942
+ #
2943
+ # Unless required by applicable law or agreed to in writing, software
2944
+ # distributed under the License is distributed on an "AS IS" BASIS,
2945
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2946
+ # See the License for the specific language governing permissions and
2947
+ # limitations under the License.
2948
+
2949
+ import os
2950
+ from google.adk import Agent, Runner
2951
+ from google.adk.sessions import InMemorySessionService
2952
+ from google.adk.tools.langchain_tool import LangchainTool
2953
+ from google.genai import types
2954
+ from langchain_community.tools import TavilySearchResults
2955
+
2956
+ # Ensure TAVILY_API_KEY is set in your environment
2957
+ if not os.getenv("TAVILY_API_KEY"):
2958
+ print("Warning: TAVILY_API_KEY environment variable not set.")
2959
+
2960
+ APP_NAME = "news_app"
2961
+ USER_ID = "1234"
2962
+ SESSION_ID = "session1234"
2963
+
2964
+ # Instantiate LangChain tool
2965
+ tavily_search = TavilySearchResults(
2966
+ max_results=5,
2967
+ search_depth="advanced",
2968
+ include_answer=True,
2969
+ include_raw_content=True,
2970
+ include_images=True,
2971
+ )
2972
+
2973
+ # Wrap with LangchainTool
2974
+ adk_tavily_tool = LangchainTool(tool=tavily_search)
2975
+
2976
+ # Define Agent with the wrapped tool
2977
+ my_agent = Agent(
2978
+ name="langchain_tool_agent",
2979
+ model="gemini-2.0-flash",
2980
+ description="Agent to answer questions using TavilySearch.",
2981
+ instruction="I can answer your questions by searching the internet. Just ask me anything!",
2982
+ tools=[adk_tavily_tool] # Add the wrapped tool here
2983
+ )
2984
+
2985
+ async def setup_session_and_runner():
2986
+ session_service = InMemorySessionService()
2987
+ session = await session_service.create_session(app_name=APP_NAME, user_id=USER_ID, session_id=SESSION_ID)
2988
+ runner = Runner(agent=my_agent, app_name=APP_NAME, session_service=session_service)
2989
+ return session, runner
2990
+
2991
+ # Agent Interaction
2992
+ async def call_agent_async(query):
2993
+ content = types.Content(role='user', parts=[types.Part(text=query)])
2994
+ session, runner = await setup_session_and_runner()
2995
+ events = runner.run_async(user_id=USER_ID, session_id=SESSION_ID, new_message=content)
2996
+
2997
+ async for event in events:
2998
+ if event.is_final_response():
2999
+ final_response = event.content.parts[0].text
3000
+ print("Agent Response: ", final_response)
3001
+
3002
+ # Note: In Colab, you can directly use 'await' at the top level.
3003
+ # If running this code as a standalone Python script, you'll need to use asyncio.run() or manage the event loop.
3004
+ await call_agent_async("stock price of GOOG")
3005
+ 2. Using CrewAI tools¶
3006
+ ADK provides the CrewaiTool wrapper to integrate tools from the CrewAI library.
3007
+
3008
+ Example: Web Search using CrewAI's Serper API¶
3009
+ Serper API provides access to Google Search results programmatically. It allows applications, like AI agents, to perform real-time Google searches (including news, images, etc.) and get structured data back without needing to scrape web pages directly.
3010
+
3011
+ Follow ADK installation and setup guide.
3012
+
3013
+ Install Dependencies: Install the necessary CrewAI tools package. For example, to use the SerperDevTool:
3014
+
3015
+
3016
+ pip install crewai-tools
3017
+ Obtain a Serper API KEY and export it as an environment variable.
3018
+
3019
+
3020
+ export SERPER_API_KEY=<REPLACE_WITH_API_KEY>
3021
+ Import: Import CrewaiTool from ADK and the desired CrewAI tool (e.g, SerperDevTool).
3022
+
3023
+
3024
+ from google.adk.tools.crewai_tool import CrewaiTool
3025
+ from crewai_tools import SerperDevTool
3026
+ Instantiate & Wrap: Create an instance of the CrewAI tool. Pass it to the CrewaiTool constructor. Crucially, you must provide a name and description to the ADK wrapper, as these are used by ADK's underlying model to understand when to use the tool.
3027
+
3028
+
3029
+ # Instantiate the CrewAI tool
3030
+ serper_tool_instance = SerperDevTool(
3031
+ n_results=10,
3032
+ save_file=False,
3033
+ search_type="news",
3034
+ )
3035
+
3036
+ # Wrap it with CrewaiTool for ADK, providing name and description
3037
+ adk_serper_tool = CrewaiTool(
3038
+ name="InternetNewsSearch",
3039
+ description="Searches the internet specifically for recent news articles using Serper.",
3040
+ tool=serper_tool_instance
3041
+ )
3042
+ Add to Agent: Include the wrapped CrewaiTool instance in your agent's tools list.
3043
+
3044
+
3045
+ from google.adk import Agent
3046
+
3047
+ # Define the ADK agent
3048
+ my_agent = Agent(
3049
+ name="crewai_search_agent",
3050
+ model="gemini-2.0-flash",
3051
+ description="Agent to find recent news using the Serper search tool.",
3052
+ instruction="I can find the latest news for you. What topic are you interested in?",
3053
+ tools=[adk_serper_tool] # Add the wrapped tool here
3054
+ )
3055
+ Full Example: Serper API¶
3056
+ Here's the full code combining the steps above to create and run an agent using the CrewAI Serper API search tool.
3057
+
3058
+
3059
+ # Copyright 2025 Google LLC
3060
+ #
3061
+ # Licensed under the Apache License, Version 2.0 (the "License");
3062
+ # you may not use this file except in compliance with the License.
3063
+ # You may obtain a copy of the License at
3064
+ #
3065
+ # http://www.apache.org/licenses/LICENSE-2.0
3066
+ #
3067
+ # Unless required by applicable law or agreed to in writing, software
3068
+ # distributed under the License is distributed on an "AS IS" BASIS,
3069
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3070
+ # See the License for the specific language governing permissions and
3071
+ # limitations under the License.
3072
+
3073
+ import os
3074
+ from google.adk import Agent, Runner
3075
+ from google.adk.sessions import InMemorySessionService
3076
+ from google.adk.tools.crewai_tool import CrewaiTool
3077
+ from google.genai import types
3078
+ from crewai_tools import SerperDevTool
3079
+
3080
+
3081
+ # Constants
3082
+ APP_NAME = "news_app"
3083
+ USER_ID = "user1234"
3084
+ SESSION_ID = "1234"
3085
+
3086
+ # Ensure SERPER_API_KEY is set in your environment
3087
+ if not os.getenv("SERPER_API_KEY"):
3088
+ print("Warning: SERPER_API_KEY environment variable not set.")
3089
+
3090
+ serper_tool_instance = SerperDevTool(
3091
+ n_results=10,
3092
+ save_file=False,
3093
+ search_type="news",
3094
+ )
3095
+
3096
+ adk_serper_tool = CrewaiTool(
3097
+ name="InternetNewsSearch",
3098
+ description="Searches the internet specifically for recent news articles using Serper.",
3099
+ tool=serper_tool_instance
3100
+ )
3101
+
3102
+ serper_agent = Agent(
3103
+ name="basic_search_agent",
3104
+ model="gemini-2.0-flash",
3105
+ description="Agent to answer questions using Google Search.",
3106
+ instruction="I can answer your questions by searching the internet. Just ask me anything!",
3107
+ # Add the Serper tool
3108
+ tools=[adk_serper_tool]
3109
+ )
3110
+
3111
+ # Session and Runner
3112
+ async def setup_session_and_runner():
3113
+ session_service = InMemorySessionService()
3114
+ session = await session_service.create_session(app_name=APP_NAME, user_id=USER_ID, session_id=SESSION_ID)
3115
+ runner = Runner(agent=serper_agent, app_name=APP_NAME, session_service=session_service)
3116
+ return session, runner
3117
+
3118
+
3119
+ # Agent Interaction
3120
+ async def call_agent_async(query):
3121
+ content = types.Content(role='user', parts=[types.Part(text=query)])
3122
+ session, runner = await setup_session_and_runner()
3123
+ events = runner.run_async(user_id=USER_ID, session_id=SESSION_ID, new_message=content)
3124
+
3125
+ async for event in events:
3126
+ if event.is_final_response():
3127
+ final_response = event.content.parts[0].text
3128
+ print("Agent Response: ", final_response)
3129
+
3130
+ # Note: In Colab, you can directly use 'await' at the top level.
3131
+ # If running this code as a standalone Python script, you'll need to use asyncio.run() or manage the event loop.
3132
+ await call_agent_async("what's the latest news on AI Agents?")
tools.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # aura-mind-glow/tools.py
2
+
3
+ import torch
4
+ from PIL import Image
5
+ from transformers import AutoProcessor
6
+ from unsloth import FastVisionModel
7
+ from langchain_community.vectorstores import FAISS
8
+
9
+ class PlantDiagnosisTool:
10
+ """A tool to diagnose the condition of a maize plant from an image."""
11
+
12
+ def __init__(self, model: FastVisionModel, processor: AutoProcessor):
13
+ self.model = model
14
+ self.processor = processor
15
+ self.name = "PlantDiagnoser"
16
+ self.description = "Analyzes an image of a maize plant and returns a diagnosis of its health condition (e.g., 'Phosphorus Deficiency', 'Healthy Plant')."
17
+
18
+ def __call__(self, image: Image.Image) -> str:
19
+ """
20
+ Takes a PIL Image and returns the diagnosis string.
21
+ """
22
+ if self.model is None or self.processor is None or image is None:
23
+ return "Error: Vision model is not loaded or no image was provided."
24
+
25
+ image = image.convert("RGB")
26
+ messages = [
27
+ {"role": "user", "content": [{"type": "text", "text": "What is the condition of this maize plant? Provide only the name of the condition."}, {"type": "image", "image": image}]}
28
+ ]
29
+ text_prompt = self.processor.tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
30
+ inputs = self.processor(text=text_prompt, images=image, return_tensors="pt").to(self.model.device)
31
+
32
+ with torch.inference_mode():
33
+ outputs = self.model.generate(**inputs, max_new_tokens=48, use_cache=True)
34
+
35
+ response = self.processor.batch_decode(outputs, skip_special_tokens=True)[0]
36
+
37
+ # Clean up the model's output to get only the diagnosis
38
+ answer_start_index = response.rfind("model\n")
39
+ if answer_start_index != -1:
40
+ return response[answer_start_index + len("model\n"):
41
+ ].strip()
42
+
43
+ answer_start_index = response.rfind("assistant\n")
44
+ if answer_start_index != -1:
45
+ return response[answer_start_index + len("assistant\n"):
46
+ ].strip()
47
+
48
+ return "Could not parse diagnosis from model output."
49
+
50
+
51
+ class RemedyRetrievalTool:
52
+ """A tool to retrieve remedies for a given plant diagnosis."""
53
+
54
+ def __init__(self, retriever: FAISS):
55
+ self.retriever = retriever
56
+ self.name = "RemedyRetriever"
57
+ self.description = "Takes a plant health diagnosis (e.g., 'Phosphorus Deficiency') and returns a suggested remedy from a local knowledge base."
58
+
59
+ def __call__(self, diagnosis: str) -> str:
60
+ """
61
+ Takes a diagnosis string and returns the remedy text.
62
+ """
63
+ if self.retriever is None:
64
+ return "Error: Knowledge base is not loaded."
65
+
66
+ retrieved_docs = self.retriever.invoke(diagnosis)
67
+ if retrieved_docs:
68
+ return retrieved_docs[0].page_content
69
+ return "No specific remedy found in the knowledge base for this condition."