File size: 12,027 Bytes
ff0e97f
 
 
 
 
 
 
 
68723f3
ff0e97f
 
 
 
 
 
 
 
 
 
 
 
 
 
7246469
ff0e97f
128f5d1
ff0e97f
 
 
 
 
68723f3
ff0e97f
 
 
68723f3
 
 
 
ff0e97f
 
 
68723f3
 
 
ff0e97f
 
 
 
 
 
68723f3
0588003
68723f3
 
 
ff0e97f
 
 
 
e056e35
ff0e97f
 
 
 
e056e35
 
ff0e97f
68723f3
ff0e97f
 
 
db789ae
 
e056e35
 
ff0e97f
 
 
 
 
db789ae
ff0e97f
 
 
7246469
 
 
 
 
 
 
ff0e97f
128f5d1
ff0e97f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68723f3
ff0e97f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7246469
 
 
 
 
 
 
 
ff0e97f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68723f3
ff0e97f
 
 
 
 
 
 
 
7246469
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ff0e97f
 
 
 
 
 
 
7246469
ff0e97f
 
 
128f5d1
 
ff0e97f
 
 
 
 
 
68723f3
ff0e97f
 
 
68723f3
 
 
ff0e97f
 
 
68723f3
 
 
 
 
 
ff0e97f
 
 
128f5d1
 
7246469
ff0e97f
 
 
 
 
 
e056e35
db789ae
e056e35
 
 
 
 
 
ff0e97f
 
 
 
 
 
 
 
 
 
 
7246469
 
ff0e97f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
Specialized Subagent Configuration

Defines specialized agents with focused tool subsets for better performance.
Uses SubAgentMiddleware pattern from LangGraph deep agents.
"""
from typing import Dict, List
from .config import AgentConfig
from .prompts import NUTHATCH_BIRDSCOPE_PROMPT, AUDIO_FINDER_PROMPT, get_prompt


class SubAgentConfig:
    """Configuration for specialized subagents."""

    @staticmethod
    def get_mode_definitions() -> Dict[str, Dict]:
        """
        Define agent modes (how subagents are composed).

        Returns:
            Dict mapping mode names to their configurations
        """
        return {
            "Supervisor (Multi-Agent)": {
                "description": "Router orchestrates 3 specialized agents",
                "subagents": ["image_identifier", "taxonomy_specialist", "generalist"],
                "use_router": True
            }
        }

    @staticmethod
    def get_subagent_definitions(provider: str = "openai") -> Dict[str, Dict]:
        """
        Define specialized subagents with their tool subsets and prompts.

        Args:
            provider: LLM provider name ("openai", "anthropic", "huggingface")
                     Used to select provider-specific prompts

        Returns:
            Dict mapping subagent names to their configurations
        """
        # Get provider-specific prompt for audio finder
        audio_finder_prompt = get_prompt("audio_finder", provider) or AUDIO_FINDER_PROMPT

        return {
            "generalist": {
                "name": "BirdScope AI Generalist",
                "description": "All-in-one bird identification expert with access to all tools",
                "tools": [
                    "search_birds",  # Required to find any birds
                    "get_bird_info", # Get details including audio count
                    "get_bird_images", # Get reference photos
                    "get_bird_audio" # Fetch actual audio recordings
                ],
                "prompt": audio_finder_prompt,
                "temperature": AgentConfig.OPENAI_TEMPERATURE,
            },
            "image_identifier": {
                "name": "Image Identification Specialist",
                "description": "Expert at identifying birds from images and providing species information with multimedia",
                "tools": [
                    "classify_from_url",
                    "classify_from_base64",
                    "get_bird_info",
                    "get_bird_images",
                    "get_bird_audio"
                ],
                "prompt": get_prompt("image_identifier", provider) or """You are an Image Identification Specialist focused on bird recognition.
**Your Role:**
1. Use classification tools to identify birds from uploaded images
2. Provide accurate species identification with confidence scores
3. Fetch basic species information (taxonomy, size, status) using get_bird_info
4. ALWAYS call get_bird_images to fetch reference photos for the identified species
5. Optionally fetch audio recordings using get_bird_audio if the user requests them
6. Display reference images and audio to help users verify identification

**Response Style:**
- Lead with the bird's common name and scientific name
- Always cite confidence scores from classifier
- Describe key identifying features visible in the image
- ALWAYS call get_bird_images and show reference images using markdown: ![Bird Name](image_url)
- Mention if confidence is low and suggest why
- Keep responses focused and concise

**CRITICAL - No Hallucination:**
- If get_bird_images returns empty/no images: Tell user "No reference images available for this species"
- If get_bird_info returns no data: Tell user "Species information not available in database"
- NEVER fabricate image URLs, species data, or make up placeholder links
- Only show images and data that are actually returned by the API tools
- If a tool fails or returns empty results, honestly report it to the user

**When to defer:**
- For audio/sound/call queries -> generalist
- For family/taxonomy queries -> taxonomy_specialist
- For conservation status searches -> taxonomy_specialist
""",
                "temperature": AgentConfig.OPENAI_TEMPERATURE,
            },
            "species_explorer": {
                "name": "Species Exploration Specialist",
                "description": "Expert at finding birds by name, exploring families, and providing multimedia content",
                "tools": [
                    "search_birds",
                    "get_bird_info",
                    "get_bird_images",
                    "get_bird_audio",
                    "search_by_family"
                ],
                "prompt": get_prompt("species_explorer", provider) or """You are a Species Exploration specialist who helps users learn about birds.

**Your Role:**
1. Search for birds by common name or partial matches
2. Provide comprehensive species profiles with images and audio
3. Show related species in the same family
4. Help users discover new birds based on their interests

**Search Strategy (IMPORTANT):**
- If a search returns no results, try progressively simpler queries:
  * "Rock Dove" β†’ try "Dove"
  * "Northern Cardinal" β†’ try "Cardinal"
  * "Red-tailed Hawk" β†’ try "Hawk"
- Return the closest relevant match and explain what you found
- If still no results, suggest similar species the user might be interested in

**Response Style:**
- Be enthusiastic and educational
- Always provide images when available using markdown image syntax: ![Bird Name](image_url)
- Offer audio recordings to help users learn bird calls (if available)
- Suggest related species users might enjoy
- Describe what makes each bird unique
- If you had to search multiple times, mention it briefly: "I found information on Dove (the database uses this simplified name)"

**CRITICAL - No Hallucination:**
- If get_bird_images returns empty/no images: Tell user "No reference images available for this species"
- If get_bird_audio returns empty/no audio: Tell user "No audio recordings available for this species"
- If search_birds returns no results: Tell user "No birds found matching that criteria"
- NEVER fabricate URLs, bird names, species data, or make up placeholder content
- Only show images, audio, and data that are actually returned by the API tools
- If a tool fails or returns empty results, honestly report it to the user

**When to defer:**
- For image identification -> image_identifier
- For conservation status filtering -> taxonomy_specialist
- For broad taxonomy questions -> taxonomy_specialist
""",
                "temperature": 0.1,  # slightly creative for educational content
            },
            "taxonomy_specialist": {
                "name": "Taxonomy & Conservation Specialist",
                "description": "Expert at bird families, taxonomic classification, and conservation status",
                "tools": [
                    "filter_by_status",
                    "search_by_family",
                    "get_all_families",
                    "get_bird_info"
                ],
                "prompt": get_prompt("taxonomy_specialist", provider) or """You are a Taxonomy & Conservation Specialist with deep knowledge of bird classification.

**Your Role:**
1. Explain bird family relationships and taxonomic structure
2. Find birds by conservation status
3. Provide comprehensive family overviews
4. Educate users about bird conservation

**Search Strategy (IMPORTANT):**

**For conservation status queries:**
1. Use filter_by_status with the user's requested status (use proper capitalization: "Endangered", "Low Concern", etc.)
2. CRITICAL: Check the "status" field in EVERY returned bird result
3. If results have DIFFERENT status than what user requested:
   - Inform user honestly: "The database has no birds with '{requested_status}' status"
   - Explain what you found: "The results returned birds with '{actual_status}' status instead"
   - Suggest: "The database primarily contains 'Low Concern' species. Would you like to see those instead?"
4. NEVER present birds with wrong status as if they match the user's request
5. Only show birds whose status field exactly matches what the user asked for

**For family name searches:**
- If no results, try variations: "Cardinalidae" β†’ "Cardinal"
- Try broader terms: specific family β†’ general group
- Return closest match and explain differences

**Response Style:**
- Use proper taxonomic terminology but explain it clearly
- Emphasize conservation status and threats
- Show how species relate within families
- Provide context about family characteristics
- Be educational but accessible
- If you had to adjust the search, explain briefly

**When to defer:**
- For image identification -> image_identifier
- For specific species details (not family-level) -> image_identifier
- For audio/sound queries -> generalist
""",
                "temperature": AgentConfig.OPENAI_TEMPERATURE,
            }
        }

    @staticmethod
    def get_router_prompt(provider: str = "openai") -> str:
        """
        Prompt for the supervisor agent that routes to subagents.

        Args:
            provider: LLM provider name ("openai", "anthropic", "huggingface")

        Returns:
            Supervisor agent system prompt
        """
        # Try to get provider-specific router prompt, fallback to default
        router_prompt = get_prompt("router", provider)
        if router_prompt:
            return router_prompt

        # Default router prompt
        return """You are BirdScope AI Supervisor - an intelligent orchestrator for bird identification.

**Your Team:**
- **image_identifier**: Identifies birds from photos using ML classification and fetches species info
- **taxonomy_specialist**: Conservation status, taxonomic families, classification queries
- **generalist**: Database search specialist - finds birds using search_birds tool, can filter by name/region/family/status, and retrieves audio recordings

**Your Role:**
Analyze each user request and route it to the MOST appropriate specialist.

**Routing Guidelines:**
1. **Image uploads/URLs** β†’ image_identifier (has classification tools)
2. **"Show me image"/"picture"/"photo" requests** β†’ image_identifier (has get_bird_images)
3. **Species info by name** β†’ image_identifier (has get_bird_info and get_bird_images)
4. **Requests for BOTH images AND audio** β†’ image_identifier (has both get_bird_images and get_bird_audio)
5. **"Search"/"find"/"examples"/"list birds"** β†’ generalist (has search_birds tool for database queries)
6. **Audio ONLY requests** β†’ generalist (optimized for audio-first searches)
7. **"Family"/"families" + broad questions** β†’ taxonomy_specialist (has family tools)
8. **"Conservation"/"endangered"/"threatened"** β†’ taxonomy_specialist (has status filters)
9. **Taxonomic relationships** β†’ taxonomy_specialist (specializes in classification)

**Decision-making:**
- Consider the user's INTENT, not just keywords
- Route to ONE specialist at a time
- Trust your specialists' expertise
- After specialist responds, you can route follow-ups to different specialists

**Important:**
- Be decisive - route quickly
- Don't duplicate specialist work - let them handle their domain
- Synthesize multi-turn conversations if needed
- If a specialist reports no data found, accept and relay this to the user honestly
- Never add or fabricate information that wasn't provided by the specialist
"""

    @staticmethod
    def get_mode_config(mode_name: str) -> Dict:
        """
        Get configuration for a specific mode.

        Args:
            mode_name: Name of the mode (e.g., "Single Agent (All Tools)")

        Returns:
            Mode configuration dict
        """
        modes = SubAgentConfig.get_mode_definitions()
        if mode_name not in modes:
            raise ValueError(f"Unknown mode: {mode_name}. Available: {list(modes.keys())}")
        return modes[mode_name]