Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions python/packages/core/agent_framework/_skills.py
Original file line number Diff line number Diff line change
Expand Up @@ -2134,9 +2134,7 @@ async def _run_script(
),
FunctionTool(
name="read_skill_resource",
description=(
"Reads a resource associated with a skill, such as references, assets, or dynamic data."
),
description=("Reads a resource associated with a skill, such as references, assets, or dynamic data."),
func=_read_resource,
input_model={
"type": "object",
Expand Down Expand Up @@ -2173,8 +2171,7 @@ async def _run_script(
"type": "object",
"additionalProperties": True,
"description": (
"Named arguments as key-value pairs "
'(e.g. {"length": 24, "uppercase": true}).'
'Named arguments as key-value pairs (e.g. {"length": 24, "uppercase": true}).'
),
},
{
Expand Down
8 changes: 4 additions & 4 deletions python/packages/core/tests/core/test_skills.py
Original file line number Diff line number Diff line change
Expand Up @@ -4086,8 +4086,8 @@ async def test_full_skill_content_contains_scripts(self) -> None:

async def test_content_is_cached(self) -> None:
skill = _MinimalClassSkill()
content1 = (await skill.get_content())
content2 = (await skill.get_content())
content1 = await skill.get_content()
content2 = await skill.get_content()
assert content1 is content2

def test_resources_are_lazy_cached(self) -> None:
Expand Down Expand Up @@ -5587,8 +5587,8 @@ class TestInlineSkillContentCaching:
async def test_content_cached_after_first_access(self) -> None:
"""InlineSkill.content returns the same object on subsequent accesses."""
skill = InlineSkill(frontmatter=SkillFrontmatter(name="test-skill", description="Test"), instructions="Body")
first = (await skill.get_content())
second = (await skill.get_content())
first = await skill.get_content()
second = await skill.get_content()
assert first is second # Same object (cached)
assert "<name>test-skill</name>" in first

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,87 @@ def mock_on(handler: Any) -> Any:
assert responses[3].role == "assistant"
assert responses[3].contents[0].type == "text"

async def test_run_streaming_multiple_tool_calls_with_json_results(
self,
mock_client: MagicMock,
mock_session: MagicMock,
session_idle_event: SessionEvent,
) -> None:
"""Test that multiple tool calls with JSON results are all projected correctly."""
json_payloads = [
'{"users": [{"id": "u1", "name": "Alice"}, {"id": "u2", "name": "Bob"}]}',
'{"tickets": [{"id": "INC001", "state": "open", "short_description": "VPN issue"}]}',
'{"groups": [{"id": "g1", "displayName": "IT Admins", "members": 42}]}',
]
tool_names = ["msgraph_list_users", "sn_query_table", "msgraph_list_groups"]

events: list[SessionEvent] = []
for i, (name, payload) in enumerate(zip(tool_names, json_payloads)):
call_id = f"call_{i:03d}"

# Tool start
start_data = MagicMock()
start_data.tool_call_id = call_id
start_data.tool_name = name
start_data.arguments = {"query": f"test_{i}"}
events.append(
SessionEvent(
data=start_data,
id=uuid4(),
timestamp=datetime.now(timezone.utc),
type=SessionEventType.TOOL_EXECUTION_START,
)
)

# Tool complete
complete_data = MagicMock()
complete_data.tool_call_id = call_id
complete_data.result = ToolExecutionCompleteResult(content=payload)
complete_data.success = True
complete_data.error = None
events.append(
SessionEvent(
data=complete_data,
id=uuid4(),
timestamp=datetime.now(timezone.utc),
type=SessionEventType.TOOL_EXECUTION_COMPLETE,
)
)

events.append(session_idle_event)

def mock_on(handler: Any) -> Any:
for event in events:
handler(event)
return lambda: None

mock_session.on = mock_on

agent = GitHubCopilotAgent(client=mock_client)
responses: list[AgentResponseUpdate] = []
async for update in agent.run("List users, tickets, and groups", stream=True):
responses.append(update)

# Should have 3 tool calls + 3 tool results = 6 updates
assert len(responses) == 6

# Verify each pair: function_call followed by function_result
for i in range(3):
call_update = responses[i * 2]
result_update = responses[i * 2 + 1]
call_id = f"call_{i:03d}"

assert call_update.role == "assistant"
assert call_update.contents[0].type == "function_call"
assert call_update.contents[0].call_id == call_id
assert call_update.contents[0].name == tool_names[i]

assert result_update.role == "tool"
assert result_update.contents[0].type == "function_result"
assert result_update.contents[0].call_id == call_id
assert result_update.contents[0].result == json_payloads[i]
assert result_update.contents[0].exception is None


class TestGitHubCopilotAgentSessionManagement:
"""Test cases for session management."""
Expand Down
5 changes: 4 additions & 1 deletion python/samples/02-agents/harness/harness_research.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,10 @@ async def main() -> None:
print(f"\n [calling tool: {content.name}]", flush=True)
print(" ", end="", flush=True)
# Show web search activity when the result arrives with action details.
elif content.type in ("search_tool_call", "search_tool_result") and getattr(content, "tool_name", None) == "web_search":
elif (
content.type in ("search_tool_call", "search_tool_result")
and getattr(content, "tool_name", None) == "web_search"
):
action = None
if content.type == "search_tool_result" and isinstance(content.result, dict):
action = content.result.get("action", {})
Expand Down
2 changes: 1 addition & 1 deletion python/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading