Skip to content

Commit baaf974

Browse files
committed
Bump version to 0.3.0 and fix test suite
- Bump version from 0.2.3 to 0.3.0 - Update CHANGELOG.md with new features and improvements from 11 commits - Fix test mocks to properly use AgentContext matching real Agent behavior - Update memory tool test for new search and append commands - Add skipif marker for otel test when opentelemetry not installed - Remove deprecated execute_agent tests (API now streaming-only) - All 252 tests now pass (1 skipped)
1 parent 4bc344e commit baaf974

File tree

7 files changed

+151
-80
lines changed

7 files changed

+151
-80
lines changed

picoagents/CHANGELOG.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,43 @@ All notable changes to PicoAgents will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.3.0] - 2025-11-10
9+
10+
### Added
11+
12+
- Model Context Protocol (MCP) integration with complete client implementation
13+
- MCP client, configuration, and transport layers
14+
- MCP tool wrapper for seamless integration with PicoAgents tools
15+
- Examples and comprehensive test coverage
16+
- Software Engineering (SWE) agent implementation with full documentation
17+
- Enhanced evaluation framework with comprehensive evaluation system
18+
- Expected answer generation utilities
19+
- Results tracking and visualization
20+
- Updated composite and LLM judges
21+
- YouTube caption tool for extracting transcripts
22+
- List memory example demonstrating memory management patterns
23+
- Context inspector component in Web UI for debugging agent context
24+
- Message handling and entity execution hooks in Web UI frontend
25+
- Workflow progress tracking with dedicated test coverage
26+
- Premium samples collection with documentation
27+
28+
### Changed
29+
30+
- Enhanced research tools with improved capabilities
31+
- Improved Web UI execution handling and state management
32+
- Updated agent and orchestration examples with better patterns
33+
- Refined LLM client implementations (OpenAI and Azure OpenAI) for better error handling
34+
- Improved workflow runner with enhanced progress reporting
35+
- Updated evaluation results with new metrics and visualizations
36+
- Enhanced memory tool with better examples
37+
38+
### Fixed
39+
40+
- Test mocks now properly use AgentContext matching real Agent behavior
41+
- Web UI frontend dependency updates for security and compatibility
42+
- Tool initialization and registration improvements
43+
- Message handling in agent communication
44+
845
## [0.2.3] - 2025-10-22
946

1047
### Added

picoagents/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "picoagents"
7-
version = "0.2.3"
7+
version = "0.3.0"
88
description = "A minimal multi-agent framework for educational purposes"
99
readme = "README.md"
1010
license = "MIT"

picoagents/tests/test_memory_tool.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,8 @@ async def test_tool_parameters_schema(self, memory_tool):
213213
"insert",
214214
"delete",
215215
"rename",
216+
"search", # Added in 0.3.0
217+
"append", # Added in 0.3.0
216218
}
217219

218220
@pytest.mark.asyncio

picoagents/tests/test_orchestrator.py

Lines changed: 58 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from picoagents._cancellation_token import CancellationToken
1111
from picoagents.agents import BaseAgent
12+
from picoagents.context import AgentContext
1213
from picoagents.messages import AssistantMessage, Message, UserMessage
1314
from picoagents.orchestration import (
1415
MaxMessageTermination,
@@ -47,23 +48,27 @@ async def run(
4748
# Simulate agent processing
4849
await asyncio.sleep(0.01)
4950

50-
# Create response messages - preserve full context plus new response
51+
# Create context with messages (matching real Agent behavior)
52+
context = AgentContext()
53+
54+
# Add input messages to context
5155
if isinstance(task, list):
52-
# Return full context plus our response
53-
context_messages = task.copy()
56+
for msg in task:
57+
context.add_message(msg)
5458
elif isinstance(task, str):
55-
context_messages = [UserMessage(content=task, source="user")]
59+
context.add_message(UserMessage(content=task, source="user"))
5660
else:
57-
context_messages = [task]
61+
context.add_message(task)
5862

63+
# Add assistant response
5964
assistant_message = AssistantMessage(
6065
content=self.response_text, source=self.name
6166
)
62-
all_messages = cast(List[Message], context_messages + [assistant_message])
67+
context.add_message(assistant_message)
6368

6469
return AgentResponse(
70+
context=context,
6571
source=self.name,
66-
messages=all_messages,
6772
usage=Usage(duration_ms=10, llm_calls=1, tokens_input=10, tokens_output=5),
6873
finish_reason="stop",
6974
)
@@ -83,28 +88,33 @@ async def run_stream(
8388
# Simulate agent processing
8489
await asyncio.sleep(0.01)
8590

86-
# Create response messages - preserve full context plus new response
91+
# Create context with messages (matching real Agent behavior)
92+
context = AgentContext()
93+
94+
# Add input messages to context and yield them
8795
if isinstance(task, list):
88-
# Return full context plus our response
89-
context_messages = task.copy()
96+
for msg in task:
97+
context.add_message(msg)
98+
yield msg
9099
elif isinstance(task, str):
91-
context_messages = [UserMessage(content=task, source="user")]
100+
msg = UserMessage(content=task, source="user")
101+
context.add_message(msg)
102+
yield msg
92103
else:
93-
context_messages = [task]
104+
context.add_message(task)
105+
yield task
94106

107+
# Add and yield assistant response
95108
assistant_message = AssistantMessage(
96109
content=self.response_text, source=self.name
97110
)
98-
all_messages = cast(List[Message], context_messages + [assistant_message])
99-
100-
# Yield all messages
101-
for message in all_messages:
102-
yield message
111+
context.add_message(assistant_message)
112+
yield assistant_message
103113

104-
# Yield final response
114+
# Yield final response with context
105115
yield AgentResponse(
116+
context=context,
106117
source=self.name,
107-
messages=all_messages,
108118
usage=Usage(duration_ms=10, llm_calls=1, tokens_input=10, tokens_output=5),
109119
finish_reason="stop",
110120
)
@@ -313,28 +323,32 @@ async def run(
313323

314324
await asyncio.sleep(0.01)
315325

316-
# Analyze the context to count how many turns have occurred
326+
# Create context with messages (matching real Agent behavior)
327+
context = AgentContext()
328+
329+
# Analyze the task to count how many turns have occurred
330+
response_count = 0
317331
if isinstance(task, str):
318332
# Count occurrences of "Iteration" in the context (indicating previous agent responses)
319333
response_count = task.count("Iteration")
320-
context_messages = [UserMessage(content=task, source="user")]
334+
context.add_message(UserMessage(content=task, source="user"))
321335
elif isinstance(task, list):
322336
response_count = len(
323337
[msg for msg in task if isinstance(msg, AssistantMessage)]
324338
)
325-
context_messages = task.copy()
339+
for msg in task:
340+
context.add_message(msg)
326341
else:
327-
response_count = 0
328-
context_messages = [UserMessage(content=str(task), source="user")]
342+
context.add_message(UserMessage(content=str(task), source="user"))
329343

330344
assistant_message = AssistantMessage(
331345
content=f"Iteration {response_count + 1}", source=self.name
332346
)
333-
all_messages = cast(List[Message], context_messages + [assistant_message])
347+
context.add_message(assistant_message)
334348

335349
return AgentResponse(
350+
context=context,
336351
source=self.name,
337-
messages=all_messages,
338352
usage=Usage(duration_ms=10, llm_calls=1),
339353
finish_reason="stop",
340354
)
@@ -351,33 +365,39 @@ async def run_stream(
351365

352366
await asyncio.sleep(0.01)
353367

354-
# Analyze the context to count how many turns have occurred
368+
# Create context with messages (matching real Agent behavior)
369+
context = AgentContext()
370+
371+
# Analyze the task to count how many turns have occurred
372+
response_count = 0
355373
if isinstance(task, str):
356374
# Count occurrences of "Iteration" in the context (indicating previous agent responses)
357375
response_count = task.count("Iteration")
358-
context_messages = [UserMessage(content=task, source="user")]
376+
msg = UserMessage(content=task, source="user")
377+
context.add_message(msg)
378+
yield msg
359379
elif isinstance(task, list):
360380
response_count = len(
361381
[msg for msg in task if isinstance(msg, AssistantMessage)]
362382
)
363-
context_messages = task.copy()
383+
for msg in task:
384+
context.add_message(msg)
385+
yield msg
364386
else:
365-
response_count = 0
366-
context_messages = [UserMessage(content=str(task), source="user")]
387+
msg = UserMessage(content=str(task), source="user")
388+
context.add_message(msg)
389+
yield msg
367390

368391
assistant_message = AssistantMessage(
369392
content=f"Iteration {response_count + 1}", source=self.name
370393
)
371-
all_messages = cast(List[Message], context_messages + [assistant_message])
372-
373-
# Yield all messages
374-
for message in all_messages:
375-
yield message
394+
context.add_message(assistant_message)
395+
yield assistant_message
376396

377-
# Yield final response
397+
# Yield final response with context
378398
yield AgentResponse(
399+
context=context,
379400
source=self.name,
380-
messages=all_messages,
381401
usage=Usage(duration_ms=10, llm_calls=1),
382402
finish_reason="stop",
383403
)

picoagents/tests/test_otel.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@
1111
from picoagents._otel import OTelMiddleware, _is_enabled, auto_instrument
1212
from picoagents.context import AgentContext
1313

14+
# Check if opentelemetry is available
15+
try:
16+
import opentelemetry # noqa: F401
17+
HAS_OPENTELEMETRY = True
18+
except ImportError:
19+
HAS_OPENTELEMETRY = False
20+
1421

1522
class TestOTelConfig:
1623
"""Test OpenTelemetry configuration."""
@@ -104,6 +111,7 @@ def test_auto_instrument_patches_agent_when_enabled(self):
104111
class TestIntegration:
105112
"""Integration tests with mock tracer."""
106113

114+
@pytest.mark.skipif(not HAS_OPENTELEMETRY, reason="opentelemetry package not installed")
107115
@pytest.mark.asyncio
108116
async def test_end_to_end_with_mock_tracer(self):
109117
"""Test full middleware flow with mocked OTel."""

picoagents/tests/test_termination.py

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
# Additional imports for integration tests
1313
from picoagents.agents import BaseAgent
14+
from picoagents.context import AgentContext
1415
from picoagents.messages import AssistantMessage, Message, ToolMessage, UserMessage
1516
from picoagents.orchestration import RoundRobinOrchestrator
1617
from picoagents.termination import (
@@ -398,23 +399,28 @@ async def run(
398399
task: Union[str, UserMessage, List[Message]],
399400
cancellation_token: Optional[CancellationToken] = None,
400401
) -> AgentResponse:
401-
"""Return only the user message + one new assistant message."""
402-
# Always return exactly one new message (no context duplication)
402+
"""Return response with proper AgentContext."""
403+
# Create context with messages (matching real Agent behavior)
404+
context = AgentContext()
405+
406+
# Add input messages to context
403407
if isinstance(task, list):
404-
context_messages = task.copy()
408+
for msg in task:
409+
context.add_message(msg)
405410
elif isinstance(task, str):
406-
context_messages = [UserMessage(content=task, source="user")]
411+
context.add_message(UserMessage(content=task, source="user"))
407412
else:
408-
context_messages = [task]
413+
context.add_message(task)
409414

415+
# Add assistant response
410416
assistant_message = AssistantMessage(
411417
content=self.response_text, source=self.name
412418
)
413-
all_messages = context_messages + [assistant_message]
419+
context.add_message(assistant_message)
414420

415421
return AgentResponse(
422+
context=context,
416423
source=self.name,
417-
messages=all_messages,
418424
usage=Usage(duration_ms=10, llm_calls=1),
419425
finish_reason="stop",
420426
)
@@ -428,15 +434,37 @@ async def run_stream(
428434
) -> AsyncGenerator[
429435
Union[Message, AgentEvent, AgentResponse, ChatCompletionChunk], None
430436
]:
431-
"""Stream the same result as run()."""
432-
result = await self.run(task, cancellation_token)
437+
"""Stream messages with proper AgentContext."""
438+
# Create context with messages (matching real Agent behavior)
439+
context = AgentContext()
440+
441+
# Add input messages to context and yield them
442+
if isinstance(task, list):
443+
for msg in task:
444+
context.add_message(msg)
445+
yield msg
446+
elif isinstance(task, str):
447+
msg = UserMessage(content=task, source="user")
448+
context.add_message(msg)
449+
yield msg
450+
else:
451+
context.add_message(task)
452+
yield task
433453

434-
# Yield each message individually
435-
for message in result.messages:
436-
yield message
454+
# Add and yield assistant response
455+
assistant_message = AssistantMessage(
456+
content=self.response_text, source=self.name
457+
)
458+
context.add_message(assistant_message)
459+
yield assistant_message
437460

438-
# Yield the final result
439-
yield result
461+
# Yield final response with context
462+
yield AgentResponse(
463+
context=context,
464+
source=self.name,
465+
usage=Usage(duration_ms=10, llm_calls=1),
466+
finish_reason="stop",
467+
)
440468

441469

442470
@pytest.mark.asyncio

picoagents/tests/webui/test_execution.py

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -90,33 +90,9 @@ def execution_engine():
9090
return ExecutionEngine(session_manager)
9191

9292

93-
@pytest.mark.asyncio
94-
async def test_execute_agent(execution_engine):
95-
"""Test executing an agent (non-streaming)."""
96-
agent = MockAgent("TestAgent")
97-
messages = [
98-
create_chat_message("user", "Hello"),
99-
]
100-
101-
response = await execution_engine.execute_agent(agent, messages)
102-
103-
assert isinstance(response, AgentResponse)
104-
assert len(response.messages) > 0
105-
assert response.usage.duration_ms > 0
106-
assert response.source == "TestAgent"
107-
108-
109-
@pytest.mark.asyncio
110-
async def test_execute_agent_with_session_id(execution_engine):
111-
"""Test executing agent with existing session ID."""
112-
agent = MockAgent("TestAgent")
113-
messages = [create_chat_message("user", "Hello")]
114-
session_id = "existing_session"
115-
116-
response = await execution_engine.execute_agent(agent, messages, session_id)
117-
118-
assert isinstance(response, AgentResponse)
119-
assert response.source == "TestAgent"
93+
# NOTE: Non-streaming execute_agent method was removed in 0.3.0
94+
# The API now only supports streaming execution via execute_agent_stream
95+
# See test_execute_agent_stream for the replacement functionality
12096

12197

12298
@pytest.mark.asyncio

0 commit comments

Comments
 (0)