1+ """
2+ Manual Test for Generic Agent with Summarizer Skill
3+
4+ This is a MANUAL TEST that requires a running Ollama instance and cannot be
5+ run in automated CI/CD pipelines. It verifies that the generic agent can:
6+ 1. Load the summarizer skill from the SKILL_FOLDERS environment variable
7+ 2. Start successfully with Ollama as the LLM backend
8+ 3. Process a summarization request using the loaded skill
9+ 4. Generate a response that demonstrates the skill was used
10+
11+ Prerequisites:
12+ - Ollama running locally on default port (11434)
13+ - A model available (default: llama3.2:3b-instruct-fp16)
14+ - The summarizer skill located at ../../../skills/summarizer (relative to this test file)
15+
16+ Usage:
17+ Run this test manually using the provided shell script:
18+ ./run_skill_test.sh
19+ """
20+
21+ import asyncio
22+ import os
23+ import sys
24+ from pathlib import Path
25+
26+ # Add the generic_agent module to the path
27+ sys .path .insert (0 , str (Path (__file__ ).parent .parent / "src" ))
28+
29+ from generic_agent .config import Configuration
30+ from generic_agent .graph import get_graph , get_mcpclient , load_skills_content
31+ from langchain_core .messages import HumanMessage
32+
33+
34+ async def test_skill_loading ():
35+ """Test that the summarizer skill is loaded and used by the agent."""
36+
37+ print ("=" * 80 )
38+ print ("Generic Agent - Summarizer Skill Test" )
39+ print ("=" * 80 )
40+
41+ # Step 1: Set up environment to load the summarizer skill
42+ # Use relative path from this test file to the skills directory
43+ test_dir = Path (__file__ ).parent
44+ repo_root = test_dir .parent .parent .parent
45+ skill_path = str (repo_root / "skills" / "summarizer" )
46+ os .environ ["SKILL_FOLDERS" ] = skill_path
47+
48+ print (f"\n ✓ Set SKILL_FOLDERS environment variable to: { skill_path } " )
49+
50+ # Verify the skill folder exists
51+ if not Path (skill_path ).exists ():
52+ print (f"\n ✗ ERROR: Skill folder does not exist at { skill_path } " )
53+ return False
54+ print ("✓ Verified skill folder exists" )
55+
56+ # Step 2: Load configuration
57+ config = Configuration ()
58+ print ("\n ✓ Configuration loaded:" )
59+ print (f" - LLM Model: { config .LLM_MODEL } " )
60+ print (f" - LLM API Base: { config .LLM_API_BASE } " )
61+ print (f" - Skill Folders: { config .SKILL_FOLDERS } " )
62+
63+ # Step 3: Verify skill content is loaded
64+ print ("\n " + "=" * 80 )
65+ print ("Testing Skill Loading" )
66+ print ("=" * 80 )
67+
68+ skills_content = load_skills_content ()
69+
70+ if not skills_content :
71+ print ("\n ✗ ERROR: No skills content loaded" )
72+ return False
73+
74+ print ("\n ✓ Skills content loaded successfully" )
75+ print (f" - Content length: { len (skills_content )} characters" )
76+
77+ # Verify the summarizer skill is in the content
78+ if "summarizer" not in skills_content .lower ():
79+ print ("\n ✗ ERROR: Summarizer skill not found in loaded content" )
80+ return False
81+
82+ print ("✓ Verified 'summarizer' skill is present in loaded content" )
83+
84+ # Check for key skill components
85+ skill_indicators = ["summarization" , "bullet" , "executive summary" , "key points" ]
86+
87+ found_indicators = [ind for ind in skill_indicators if ind .lower () in skills_content .lower ()]
88+ print (f"✓ Found { len (found_indicators )} /{ len (skill_indicators )} skill indicators:" )
89+ for indicator in found_indicators :
90+ print (f" - { indicator } " )
91+
92+ # Step 4: Initialize MCP client (may be empty, that's OK)
93+ print ("\n " + "=" * 80 )
94+ print ("Initializing Agent Components" )
95+ print ("=" * 80 )
96+
97+ try :
98+ mcpclient = get_mcpclient ()
99+ print ("\n ✓ MCP client initialized" )
100+
101+ # Try to get tools (may be empty)
102+ try :
103+ tools = await mcpclient .get_tools ()
104+ print (f"✓ MCP tools available: { len (tools )} " )
105+ except Exception as e :
106+ print (f"⚠ No MCP tools available (this is OK for this test): { e } " )
107+ except Exception as e :
108+ print (f"⚠ MCP client initialization warning: { e } " )
109+ print (" (This is OK - agent will work without MCP tools)" )
110+
111+ # Step 5: Create the graph with the skill loaded
112+ print ("\n ✓ Creating agent graph with loaded skills..." )
113+
114+ try :
115+ graph = await get_graph (mcpclient )
116+ print ("✓ Agent graph created successfully" )
117+ except Exception as e :
118+ print (f"\n ✗ ERROR: Failed to create agent graph: { e } " )
119+ return False
120+
121+ # Step 6: Test the agent with a summarization request
122+ print ("\n " + "=" * 80 )
123+ print ("Testing Agent with Summarization Request" )
124+ print ("=" * 80 )
125+
126+ # Create a test document that needs summarization
127+ test_document = """
128+ The quarterly review meeting was held on January 15, 2026. The team discussed
129+ the product launch timeline and budget allocation. It was decided to move the
130+ launch date to March 1, 2026, to allow for additional testing. The marketing
131+ budget was approved at $250,000. Sarah will finalize the marketing plan by
132+ January 22. Mike needs to complete beta testing by February 1. The team agreed
133+ to review the pricing strategy in the next meeting. Revenue projections show
134+ a 23% increase over last quarter. Customer satisfaction scores improved to 4.5
135+ out of 5. The development team identified three critical bugs that must be
136+ fixed before launch. John will coordinate with the QA team to prioritize these
137+ issues. The meeting concluded with a commitment to weekly status updates.
138+ """
139+
140+ user_prompt = f"""Please summarize the following meeting notes in bullet points:
141+
142+ { test_document }
143+
144+ Provide a clear, concise summary with the key decisions, action items, and important data points."""
145+
146+ print (f"\n ✓ Test prompt prepared (length: { len (user_prompt )} characters)" )
147+ print ("\n Prompt preview:" )
148+ print ("-" * 80 )
149+ print (user_prompt [:200 ] + "..." )
150+ print ("-" * 80 )
151+
152+ # Create input message
153+ messages = [HumanMessage (content = user_prompt )]
154+ input_data = {"messages" : messages }
155+
156+ print ("\n ✓ Invoking agent..." )
157+ print (" (This may take 30-60 seconds depending on the model)" )
158+
159+ try :
160+ # Stream the graph execution
161+ final_output = None
162+ step_count = 0
163+
164+ async for event in graph .astream (input_data , stream_mode = "updates" ):
165+ step_count += 1
166+ print (f"\n Step { step_count } :" )
167+ for key , value in event .items ():
168+ # Print abbreviated output
169+ value_str = str (value )
170+ if len (value_str ) > 200 :
171+ value_str = value_str [:200 ] + "..."
172+ print (f" { key } : { value_str } " )
173+ final_output = event
174+
175+ print (f"\n ✓ Agent completed execution in { step_count } steps" )
176+
177+ except Exception as e :
178+ print (f"\n ✗ ERROR: Agent execution failed: { e } " )
179+ import traceback
180+
181+ traceback .print_exc ()
182+ return False
183+
184+ # Step 7: Verify the response
185+ print ("\n " + "=" * 80 )
186+ print ("Analyzing Agent Response" )
187+ print ("=" * 80 )
188+
189+ if not final_output :
190+ print ("\n ✗ ERROR: No output received from agent" )
191+ return False
192+
193+ # Extract the final answer
194+ final_answer = final_output .get ("assistant" , {}).get ("final_answer" )
195+
196+ if not final_answer :
197+ print ("\n ✗ ERROR: No final answer in agent output" )
198+ print (f"Output keys: { final_output .keys ()} " )
199+ return False
200+
201+ print (f"\n ✓ Final answer received (length: { len (final_answer )} characters)" )
202+ print ("\n Final Answer:" )
203+ print ("=" * 80 )
204+ print (final_answer )
205+ print ("=" * 80 )
206+
207+ # Step 8: Verify the response shows skill usage
208+ print ("\n " + "=" * 80 )
209+ print ("Verifying Skill Usage" )
210+ print ("=" * 80 )
211+
212+ # Check for indicators that the summarization skill was used
213+ summary_indicators = [
214+ "•" , # Bullet points
215+ "-" , # Dashes for lists
216+ "summary" ,
217+ "key" ,
218+ "decision" ,
219+ "action" ,
220+ "budget" ,
221+ "march" ,
222+ "250" ,
223+ ]
224+
225+ found_in_response = [ind for ind in summary_indicators if ind .lower () in final_answer .lower ()]
226+
227+ print (f"\n ✓ Found { len (found_in_response )} /{ len (summary_indicators )} summary indicators in response:" )
228+ for indicator in found_in_response [:5 ]: # Show first 5
229+ print (f" - '{ indicator } '" )
230+
231+ # Check if response is actually a summary (shorter than input)
232+ response_length = len (final_answer .split ())
233+ input_length = len (test_document .split ())
234+ compression_ratio = response_length / input_length
235+
236+ print ("\n ✓ Compression analysis:" )
237+ print (f" - Input words: { input_length } " )
238+ print (f" - Response words: { response_length } " )
239+ print (f" - Compression ratio: { compression_ratio :.2f} " )
240+
241+ if compression_ratio > 1.5 :
242+ print (" ⚠ Warning: Response is longer than input (may not be a proper summary)" )
243+ else :
244+ print (" ✓ Response is appropriately condensed" )
245+
246+ # Final verdict
247+ print ("\n " + "=" * 80 )
248+ print ("Test Results" )
249+ print ("=" * 80 )
250+
251+ success_criteria = [
252+ ("Skill folder exists" , Path (skill_path ).exists ()),
253+ ("Skills content loaded" , bool (skills_content )),
254+ ("Summarizer skill found" , "summarizer" in skills_content .lower ()),
255+ ("Agent graph created" , graph is not None ),
256+ ("Agent executed successfully" , final_output is not None ),
257+ ("Final answer received" , bool (final_answer )),
258+ ("Summary indicators present" , len (found_in_response ) >= 3 ),
259+ ]
260+
261+ print ("\n Success Criteria:" )
262+ all_passed = True
263+ for criterion , passed in success_criteria :
264+ status = "✓ PASS" if passed else "✗ FAIL"
265+ print (f" { status } : { criterion } " )
266+ if not passed :
267+ all_passed = False
268+
269+ print ("\n " + "=" * 80 )
270+ if all_passed :
271+ print ("✓ TEST PASSED: Summarizer skill loaded and used successfully!" )
272+ else :
273+ print ("✗ TEST FAILED: Some criteria not met" )
274+ print ("=" * 80 )
275+
276+ return all_passed
277+
278+
279+ async def main ():
280+ """Main test runner."""
281+ try :
282+ success = await test_skill_loading ()
283+ sys .exit (0 if success else 1 )
284+ except Exception as e :
285+ print (f"\n ✗ FATAL ERROR: { e } " )
286+ import traceback
287+
288+ traceback .print_exc ()
289+ sys .exit (1 )
290+
291+
292+ if __name__ == "__main__" :
293+ asyncio .run (main ())
294+
295+ # Made with Bob
0 commit comments