Skip to content

Commit 534cd1e

Browse files
committed
fix(showcase): D5 integration fixes across 12 frameworks
Per-framework fixes to pass D5 e2e-deep probes: - agno: deduplicate agent_server routes - claude-sdk-python: handle ParsedContentBlockStopEvent (SDK v0.97+) - claude-sdk-typescript: remove orphan tool-rendering page - crewai-crews: add backend tool_rendering agent + shared_state fix - google-adk: add AGUIToolset to all ADK agents for frontend tools - langgraph-typescript: remove stale import - langroid: emit ToolCallResultEvent for backend tools + fix adapter - llamaindex: v2 provider import, book_call stub, PYTHONPATH fix - ms-agent-python: disable Responses API store for aimock compat - pydantic-ai: simplify gen-ui page component - spring-ai: raise tool iteration cap (1→5) + fix connection pooling - strands: shared tools symlink + requirements update
1 parent 8d0bc3c commit 534cd1e

212 files changed

Lines changed: 10658 additions & 396 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

showcase/integrations/ag2/tools

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"""Barrel exports for all shared showcase tool implementations."""
2+
3+
from .types import (
4+
SalesStage,
5+
SalesTodo,
6+
Flight,
7+
WeatherResult,
8+
)
9+
from .get_weather import get_weather_impl
10+
from .query_data import query_data_impl
11+
from .sales_todos import (
12+
INITIAL_TODOS,
13+
manage_sales_todos_impl,
14+
get_sales_todos_impl,
15+
)
16+
from .search_flights import search_flights_impl
17+
from .generate_a2ui import (
18+
RENDER_A2UI_TOOL_SCHEMA,
19+
generate_a2ui_impl,
20+
build_a2ui_operations_from_tool_call,
21+
)
22+
from .schedule_meeting import schedule_meeting_impl
23+
24+
__all__ = [
25+
# Types
26+
"SalesStage",
27+
"SalesTodo",
28+
"Flight",
29+
"WeatherResult",
30+
# Weather
31+
"get_weather_impl",
32+
# Query data
33+
"query_data_impl",
34+
# Sales todos
35+
"INITIAL_TODOS",
36+
"manage_sales_todos_impl",
37+
"get_sales_todos_impl",
38+
# Flight search (fixed-schema A2UI)
39+
"search_flights_impl",
40+
# Dynamic A2UI
41+
"RENDER_A2UI_TOOL_SCHEMA",
42+
"generate_a2ui_impl",
43+
"build_a2ui_operations_from_tool_call",
44+
# Schedule meeting (HITL)
45+
"schedule_meeting_impl",
46+
]
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
"""Dynamic A2UI tool: LLM-generated UI from conversation context.
2+
3+
This module provides the data preparation for a secondary LLM call that
4+
generates v0.9 A2UI components. The actual LLM call is made by the
5+
framework-specific wrapper (LangGraph, CrewAI, etc.) since each framework
6+
has its own way of invoking LLMs.
7+
"""
8+
9+
from __future__ import annotations
10+
11+
import logging
12+
from typing import Any, Optional
13+
14+
_logger = logging.getLogger(__name__)
15+
16+
CUSTOM_CATALOG_ID = "copilotkit://app-dashboard-catalog"
17+
18+
# The render_a2ui tool schema that the secondary LLM is bound to.
19+
RENDER_A2UI_TOOL_SCHEMA = {
20+
"name": "render_a2ui",
21+
"description": (
22+
"Render a dynamic A2UI v0.9 surface.\n\n"
23+
"Args:\n"
24+
" surfaceId: Unique surface identifier.\n"
25+
" catalogId: The catalog ID (use \"copilotkit://app-dashboard-catalog\").\n"
26+
" components: A2UI v0.9 component array (flat format). "
27+
"The root component must have id \"root\".\n"
28+
" data: Optional initial data model for the surface."
29+
),
30+
"parameters": {
31+
"type": "object",
32+
"properties": {
33+
"surfaceId": {"type": "string", "description": "Unique surface identifier."},
34+
"catalogId": {"type": "string", "description": "The catalog ID."},
35+
"components": {
36+
"type": "array",
37+
"items": {"type": "object"},
38+
"description": "A2UI v0.9 component array (flat format).",
39+
},
40+
"data": {
41+
"type": "object",
42+
"description": "Optional initial data model for the surface.",
43+
},
44+
},
45+
"required": ["surfaceId", "catalogId", "components"],
46+
},
47+
}
48+
49+
50+
def generate_a2ui_impl(
51+
messages: list[dict[str, Any]],
52+
context_entries: Optional[list[dict[str, Any]]] = None,
53+
) -> dict[str, Any]:
54+
"""Prepare inputs for a secondary LLM call that generates A2UI components.
55+
56+
Returns a dict with:
57+
- system_prompt: The system prompt for the secondary LLM (built from context)
58+
- tool_schema: The render_a2ui tool schema to bind to the LLM
59+
- tool_choice: The tool name to force
60+
- messages: The conversation messages to pass through
61+
- catalog_id: The default catalog ID
62+
63+
The framework wrapper should:
64+
1. Make an LLM call with these inputs
65+
2. Extract the tool call args (surfaceId, catalogId, components, data)
66+
3. Build a2ui_operations from the args and return them
67+
"""
68+
context_text = ""
69+
if context_entries:
70+
context_text = "\n\n".join(
71+
entry.get("value", "")
72+
for entry in context_entries
73+
if isinstance(entry, dict) and entry.get("value")
74+
)
75+
76+
return {
77+
"system_prompt": context_text,
78+
"tool_schema": RENDER_A2UI_TOOL_SCHEMA,
79+
"tool_choice": "render_a2ui",
80+
"messages": messages,
81+
"catalog_id": CUSTOM_CATALOG_ID,
82+
}
83+
84+
85+
def build_a2ui_operations_from_tool_call(args: dict[str, Any]) -> dict[str, Any]:
86+
"""Build a2ui_operations dict from the secondary LLM's tool call args.
87+
88+
Call this after the framework wrapper extracts the tool call arguments.
89+
"""
90+
surface_id = args.get("surfaceId", "dynamic-surface")
91+
catalog_id = args.get("catalogId", CUSTOM_CATALOG_ID)
92+
components = args.get("components", [])
93+
if not components:
94+
_logger.warning("build_a2ui_operations_from_tool_call received empty components list")
95+
data = args.get("data")
96+
97+
ops = [
98+
{"type": "create_surface", "surfaceId": surface_id, "catalogId": catalog_id},
99+
{"type": "update_components", "surfaceId": surface_id, "components": components},
100+
]
101+
if data:
102+
ops.append({"type": "update_data_model", "surfaceId": surface_id, "data": data})
103+
104+
return {"a2ui_operations": ops}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"""Mock weather data tool implementation."""
2+
3+
import random
4+
from .types import WeatherResult
5+
6+
_CONDITIONS = [
7+
"Sunny",
8+
"Partly Cloudy",
9+
"Cloudy",
10+
"Overcast",
11+
"Light Rain",
12+
"Heavy Rain",
13+
"Thunderstorm",
14+
"Snow",
15+
"Foggy",
16+
"Windy",
17+
]
18+
19+
20+
def get_weather_impl(city: str) -> WeatherResult:
21+
"""Return mock weather data for the given city.
22+
23+
Uses a seeded random based on the city name so repeated calls
24+
for the same city return consistent results within a session.
25+
"""
26+
rng = random.Random(city.lower())
27+
temperature = rng.randint(20, 95)
28+
humidity = rng.randint(30, 90)
29+
wind_speed = rng.randint(2, 30)
30+
feels_like = temperature + rng.randint(-5, 5)
31+
conditions = rng.choice(_CONDITIONS)
32+
33+
return WeatherResult(
34+
city=city,
35+
temperature=temperature,
36+
humidity=humidity,
37+
wind_speed=wind_speed,
38+
feels_like=feels_like,
39+
conditions=conditions,
40+
)
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
"""Query data tool implementation — reads db.csv at module load time."""
2+
3+
from __future__ import annotations
4+
5+
import csv
6+
import logging
7+
from pathlib import Path
8+
from typing import Any
9+
10+
_logger = logging.getLogger(__name__)
11+
12+
_csv_path = Path(__file__).resolve().parent.parent / "data" / "db.csv"
13+
14+
_MOCK_DATA = [
15+
{
16+
"date": "2026-01-05",
17+
"category": "Revenue",
18+
"subcategory": "Enterprise Subscriptions",
19+
"amount": "28000",
20+
"type": "income",
21+
"notes": "3 new enterprise customers",
22+
},
23+
{
24+
"date": "2026-01-10",
25+
"category": "Expenses",
26+
"subcategory": "Engineering Salaries",
27+
"amount": "42000",
28+
"type": "expense",
29+
"notes": "7 engineers + 2 contractors",
30+
},
31+
{
32+
"date": "2026-02-03",
33+
"category": "Revenue",
34+
"subcategory": "Pro Tier Upgrades",
35+
"amount": "22500",
36+
"type": "income",
37+
"notes": "31 upgrades + reduced churn",
38+
},
39+
]
40+
41+
try:
42+
with open(_csv_path) as _f:
43+
_cached_data: list[dict[str, Any]] = list(csv.DictReader(_f))
44+
if not _cached_data:
45+
_logger.warning("CSV at %s is empty, falling back to mock data", _csv_path)
46+
_cached_data = _MOCK_DATA
47+
except (FileNotFoundError, OSError) as exc:
48+
_logger.warning("Could not load CSV at %s (%s), falling back to mock data", _csv_path, exc)
49+
_cached_data = _MOCK_DATA
50+
51+
52+
def query_data_impl(query: str) -> list[dict[str, Any]]:
53+
"""Query the database. Takes natural language.
54+
55+
Always call before showing a chart or graph. Returns the full
56+
dataset as a list of dicts (rows from the CSV).
57+
"""
58+
return _cached_data
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""Sales todos tool implementation."""
2+
3+
from __future__ import annotations
4+
5+
import uuid
6+
from typing import Optional
7+
8+
from .types import SalesTodo
9+
10+
INITIAL_TODOS: list[SalesTodo] = [
11+
SalesTodo(
12+
id="st-001",
13+
title="Follow up with Acme Corp on enterprise proposal",
14+
stage="proposal",
15+
value=85000,
16+
dueDate="2026-04-15",
17+
assignee="Sarah Chen",
18+
completed=False,
19+
),
20+
SalesTodo(
21+
id="st-002",
22+
title="Qualify lead from TechFlow demo request",
23+
stage="prospect",
24+
value=42000,
25+
dueDate="2026-04-18",
26+
assignee="Mike Johnson",
27+
completed=False,
28+
),
29+
SalesTodo(
30+
id="st-003",
31+
title="Send contract to DataViz Inc for final review",
32+
stage="negotiation",
33+
value=120000,
34+
dueDate="2026-04-20",
35+
assignee="Sarah Chen",
36+
completed=False,
37+
),
38+
]
39+
40+
41+
def manage_sales_todos_impl(todos: list[dict]) -> list[SalesTodo]:
42+
"""Assign UUIDs to any todos missing an ID, then return the updated list."""
43+
result: list[SalesTodo] = []
44+
for todo in todos:
45+
result.append(SalesTodo(
46+
id=todo.get("id") or str(uuid.uuid4()),
47+
title=todo.get("title", ""),
48+
stage=todo.get("stage", "prospect"),
49+
value=todo.get("value", 0),
50+
dueDate=todo.get("dueDate", ""),
51+
assignee=todo.get("assignee", ""),
52+
completed=todo.get("completed", False),
53+
))
54+
return result
55+
56+
57+
def get_sales_todos_impl(current_todos: Optional[list[dict]] = None) -> list[SalesTodo]:
58+
"""Return current todos or initial defaults if none provided."""
59+
if current_todos is not None:
60+
return manage_sales_todos_impl(current_todos)
61+
return list(INITIAL_TODOS)
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"""Schedule meeting tool implementation.
2+
3+
The HITL gating happens on the frontend via useHumanInTheLoop.
4+
This tool just returns a pending approval status for the framework
5+
wrapper to surface.
6+
"""
7+
8+
from __future__ import annotations
9+
10+
from typing import Any
11+
12+
13+
def schedule_meeting_impl(
14+
reason: str,
15+
duration_minutes: int = 30,
16+
) -> dict[str, Any]:
17+
"""Schedule a meeting (requires human approval).
18+
19+
Returns a pending_approval status. The actual gating is done by the
20+
frontend's useHumanInTheLoop hook — the agent pauses until the user
21+
approves or rejects.
22+
"""
23+
return {
24+
"status": "pending_approval",
25+
"reason": reason,
26+
"duration_minutes": duration_minutes,
27+
"message": (
28+
f"Meeting request: {reason} ({duration_minutes} min). "
29+
"Awaiting human approval."
30+
),
31+
}

0 commit comments

Comments
 (0)