Skip to content

Commit f90a98a

Browse files
committed
feat: Refactor Agent Workbench to Agent Fabric and enhance tool metadata handling
Signed-off-by: Andre Bossard <anbossar@microsoft.com>
1 parent 175268f commit f90a98a

12 files changed

Lines changed: 403 additions & 81 deletions

File tree

backend/agent_workbench/service.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,13 +110,21 @@ def llm(self) -> Any:
110110
# Tool introspection
111111
# ------------------------------------------------------------------
112112

113-
def list_tools(self) -> list[dict[str, str]]:
113+
def list_tools(self) -> list[dict[str, Any]]:
114114
"""Return metadata about all registered tools."""
115-
result = []
115+
result: list[dict[str, Any]] = []
116116
for t in self._registry.available_tools():
117+
input_schema: dict[str, Any] = {"type": "object", "properties": {}}
118+
args_schema = getattr(t, "args_schema", None)
119+
if args_schema and hasattr(args_schema, "model_json_schema"):
120+
try:
121+
input_schema = args_schema.model_json_schema()
122+
except Exception:
123+
input_schema = {"type": "object", "properties": {}}
117124
result.append({
118125
"name": t.name,
119126
"description": (t.description or "")[:200],
127+
"input_schema": input_schema,
120128
})
121129
return result
122130

backend/app.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030

3131
# Import unified operation system
3232

33-
# Agent Workbench
33+
# Agent Fabric
3434
from agent_workbench import (
3535
AgentDefinitionCreate,
3636
AgentDefinitionUpdate,
@@ -191,7 +191,7 @@ async def rest_run_agent():
191191

192192

193193
# ============================================================================
194-
# AGENT WORKBENCH ENDPOINTS
194+
# AGENT FABRIC ENDPOINTS
195195
# ============================================================================
196196

197197
_WORKBENCH_UI_OPERATION_NAMES = [
@@ -212,7 +212,7 @@ async def rest_run_agent():
212212

213213
@app.route("/api/workbench/ui-config", methods=["GET"])
214214
async def workbench_ui_config():
215-
"""Expose UI-friendly endpoint metadata and enums for Agent Workbench."""
215+
"""Expose UI-friendly endpoint metadata and enums for Agent Fabric."""
216216
endpoints: list[dict] = []
217217
for op_name in _WORKBENCH_UI_OPERATION_NAMES:
218218
op = get_operation(op_name)
@@ -227,7 +227,7 @@ async def workbench_ui_config():
227227
})
228228

229229
return jsonify({
230-
"module": "agent_workbench",
230+
"module": "agent_fabric",
231231
"version": "1",
232232
"criteria_types": [criteria.value for criteria in CriteriaType],
233233
"run_statuses": [status.value for status in RunStatus],

backend/operations.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -325,18 +325,18 @@ async def op_csv_sla_breach_tickets(
325325

326326
@operation(
327327
name="workbench_list_tools",
328-
description="List tools available for Agent Workbench definitions",
328+
description="List tools available for Agent Fabric definitions",
329329
http_method="GET",
330330
http_path="/api/workbench/tools",
331331
)
332-
async def op_workbench_list_tools() -> list[dict[str, str]]:
332+
async def op_workbench_list_tools() -> list[dict[str, Any]]:
333333
"""Return all registered tool metadata from the workbench registry."""
334334
return _get_workbench_service().list_tools()
335335

336336

337337
@operation(
338338
name="workbench_list_agents",
339-
description="List all Agent Workbench agent definitions",
339+
description="List all Agent Fabric agent definitions",
340340
http_method="GET",
341341
http_path="/api/workbench/agents",
342342
)
@@ -348,7 +348,7 @@ async def op_workbench_list_agents() -> list[dict[str, Any]]:
348348

349349
@operation(
350350
name="workbench_create_agent",
351-
description="Create a new Agent Workbench agent definition",
351+
description="Create a new Agent Fabric agent definition",
352352
http_method="POST",
353353
http_path="/api/workbench/agents",
354354
)
@@ -360,7 +360,7 @@ async def op_workbench_create_agent(data: AgentDefinitionCreate) -> dict[str, An
360360

361361
@operation(
362362
name="workbench_get_agent",
363-
description="Get one Agent Workbench agent definition by id",
363+
description="Get one Agent Fabric agent definition by id",
364364
http_method="GET",
365365
http_path="/api/workbench/agents/{agent_id}",
366366
)
@@ -372,7 +372,7 @@ async def op_workbench_get_agent(agent_id: str) -> dict[str, Any] | None:
372372

373373
@operation(
374374
name="workbench_update_agent",
375-
description="Update an Agent Workbench agent definition",
375+
description="Update an Agent Fabric agent definition",
376376
http_method="PUT",
377377
http_path="/api/workbench/agents/{agent_id}",
378378
)
@@ -387,7 +387,7 @@ async def op_workbench_update_agent(
387387

388388
@operation(
389389
name="workbench_delete_agent",
390-
description="Delete an Agent Workbench agent definition",
390+
description="Delete an Agent Fabric agent definition",
391391
http_method="DELETE",
392392
http_path="/api/workbench/agents/{agent_id}",
393393
)
@@ -398,7 +398,7 @@ async def op_workbench_delete_agent(agent_id: str) -> bool:
398398

399399
@operation(
400400
name="workbench_run_agent",
401-
description="Run an Agent Workbench agent with a prompt",
401+
description="Run an Agent Fabric agent with a prompt",
402402
http_method="POST",
403403
http_path="/api/workbench/agents/{agent_id}/runs",
404404
)
@@ -410,7 +410,7 @@ async def op_workbench_run_agent(agent_id: str, data: AgentRunCreate) -> dict[st
410410

411411
@operation(
412412
name="workbench_list_agent_runs",
413-
description="List Agent Workbench runs for a specific agent",
413+
description="List Agent Fabric runs for a specific agent",
414414
http_method="GET",
415415
http_path="/api/workbench/agents/{agent_id}/runs",
416416
)
@@ -426,7 +426,7 @@ async def op_workbench_list_agent_runs(
426426

427427
@operation(
428428
name="workbench_list_runs",
429-
description="List Agent Workbench runs, optionally filtered by agent id",
429+
description="List Agent Fabric runs, optionally filtered by agent id",
430430
http_method="GET",
431431
http_path="/api/workbench/runs",
432432
)
@@ -442,7 +442,7 @@ async def op_workbench_list_runs(
442442

443443
@operation(
444444
name="workbench_get_run",
445-
description="Get one Agent Workbench run by id",
445+
description="Get one Agent Fabric run by id",
446446
http_method="GET",
447447
http_path="/api/workbench/runs/{run_id}",
448448
)
@@ -454,7 +454,7 @@ async def op_workbench_get_run(run_id: str) -> dict[str, Any] | None:
454454

455455
@operation(
456456
name="workbench_evaluate_run",
457-
description="Evaluate an Agent Workbench run against its success criteria",
457+
description="Evaluate an Agent Fabric run against its success criteria",
458458
http_method="POST",
459459
http_path="/api/workbench/runs/{run_id}/evaluate",
460460
)
@@ -466,7 +466,7 @@ async def op_workbench_evaluate_run(run_id: str) -> dict[str, Any]:
466466

467467
@operation(
468468
name="workbench_get_evaluation",
469-
description="Get evaluation for an Agent Workbench run",
469+
description="Get evaluation for an Agent Fabric run",
470470
http_method="GET",
471471
http_path="/api/workbench/runs/{run_id}/evaluation",
472472
)

backend/tests/test_workbench_integration_e2e.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,18 @@ async def test_create_run_and_evaluate_agent_with_csv_tool(self) -> None:
8989
tools_payload = await tools_resp.get_json()
9090
tool_names = [tool["name"] for tool in tools_payload["tools"]]
9191
self.assertIn("csv_ticket_stats", tool_names)
92+
self.assertIn("csv_ticket_fields", tool_names)
93+
self.assertFalse(any(name.startswith("list_task") for name in tool_names))
94+
self.assertFalse(any(name.startswith("create_task") for name in tool_names))
9295
self.assertFalse(any(name.startswith("workbench_") for name in tool_names))
9396

97+
list_tickets_tool = next(
98+
item for item in tools_payload["tools"] if item["name"] == "csv_list_tickets"
99+
)
100+
input_props = list_tickets_tool.get("input_schema", {}).get("properties", {})
101+
self.assertIn("status", input_props)
102+
self.assertIn("limit", input_props)
103+
94104
create_payload = {
95105
"name": "CSV stats verifier",
96106
"description": "E2E check for workbench integration",

backend/usecase_demo.py

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,8 @@
1818
from typing import Any
1919
from uuid import uuid4
2020

21-
from pydantic import BaseModel, Field, field_validator
22-
2321
from agents import AgentRequest, agent_service
22+
from pydantic import BaseModel, Field, field_validator
2423

2524
USECASE_DEMO_AGENT_TIMEOUT_SECONDS = float(
2625
os.getenv("USECASE_DEMO_AGENT_TIMEOUT_SECONDS", "300")
@@ -134,6 +133,11 @@ def _extract_columns(rows: list[dict[str, Any]]) -> list[str]:
134133
return columns
135134

136135

136+
def _is_sla_breach_prompt(prompt: str) -> bool:
137+
normalized = prompt.lower()
138+
return "csv_sla_breach_tickets" in normalized or "sla breach" in normalized
139+
140+
137141
class UsecaseDemoRunService:
138142
"""In-memory run orchestration with polling-friendly status updates."""
139143

@@ -188,17 +192,28 @@ async def _execute_run(self, run_id: str) -> None:
188192
error=None,
189193
)
190194

191-
# Enforce a predictable output block for table rendering.
192-
structured_prompt = (
193-
f"{run.prompt}\n\n"
194-
"Antwortformat:\n"
195-
"- Führe die Anfrage mit möglichst wenigen Tool-Aufrufen aus.\n"
196-
"- Nutze kompakte fields und sinnvolle limits.\n"
197-
"- Fordere notes/resolution nur bei explizitem Bedarf an.\n"
198-
"- Gib einen JSON-Codeblock mit {\"rows\": [...]} zurück.\n"
199-
"- Falls keine sinnvollen Zeilen existieren, gib {\"rows\": []} zurück.\n"
200-
"- Optional danach: kurze Zusammenfassung in 2-4 Stichpunkten."
201-
)
195+
if _is_sla_breach_prompt(run.prompt):
196+
structured_prompt = (
197+
f"{run.prompt}\n\n"
198+
"Antwortformat für SLA-Breach Usecase:\n"
199+
"- Rufe csv_sla_breach_tickets als primäre Quelle auf.\n"
200+
"- Bevorzuge einen einzelnen Tool-Aufruf; keine unnötigen Tool-Schleifen.\n"
201+
"- Liefere ausschließlich kurze Next-Actions als Markdown (max. 6 Bullet Points).\n"
202+
"- Keine JSON-Blöcke zurückgeben.\n"
203+
"- Fokus: Priorisierung, Verantwortliche Gruppen, sofortige Eskalationsschritte."
204+
)
205+
else:
206+
# Enforce a predictable output block for table rendering.
207+
structured_prompt = (
208+
f"{run.prompt}\n\n"
209+
"Antwortformat:\n"
210+
"- Führe die Anfrage mit möglichst wenigen Tool-Aufrufen aus.\n"
211+
"- Nutze kompakte fields und sinnvolle limits.\n"
212+
"- Fordere notes/resolution nur bei explizitem Bedarf an.\n"
213+
"- Gib einen JSON-Codeblock mit {\"rows\": [...]} zurück.\n"
214+
"- Falls keine sinnvollen Zeilen existieren, gib {\"rows\": []} zurück.\n"
215+
"- Optional danach: kurze Zusammenfassung in 2-4 Stichpunkten."
216+
)
202217

203218
try:
204219
response = await asyncio.wait_for(

backend/workbench_integration.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""
22
Workbench Integration
33
4-
Wires the project's tools into the Agent Workbench and exposes a
4+
Wires the project's tools into the Agent Fabric module and exposes a
55
singleton WorkbenchService ready to use in app.py.
66
77
Separation of concerns:
@@ -28,18 +28,18 @@ def _build_registry() -> ToolRegistry:
2828
2929
Sources:
3030
1. All @operation-decorated functions via api_decorators.get_langchain_tools()
31-
This includes: task management + csv_* ticket operations
31+
Exposed to Agent Fabric: csv_* ticket operations only.
3232
3333
The registry is built once at startup and shared with WorkbenchService.
3434
"""
3535
registry = ToolRegistry()
3636
try:
3737
all_tools = get_langchain_tools()
38-
user_tools = [
38+
ticket_tools = [
3939
tool for tool in all_tools
40-
if not getattr(tool, "name", "").startswith("workbench_")
40+
if getattr(tool, "name", "").startswith("csv_")
4141
]
42-
registry.register_all(user_tools)
42+
registry.register_all(ticket_tools)
4343
except Exception as exc:
4444
import logging
4545
logging.getLogger(__name__).warning("Could not load langchain tools: %s", exc)

frontend/src/App.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ export default function App() {
8181
...usecaseTabs,
8282
{ value: 'kitchensink', label: 'Kitchen Sink', icon: <DataHistogram24Regular />, path: '/kitchensink', testId: 'tab-kitchensink' },
8383
{ value: 'fields', label: 'Fields', icon: <Info24Regular />, path: '/fields', testId: 'tab-fields' },
84-
{ value: 'workbench', label: 'Workbench', icon: <Wrench24Regular />, path: '/workbench', testId: 'tab-workbench' },
84+
{ value: 'workbench', label: 'Agent Fabric', icon: <Wrench24Regular />, path: '/workbench', testId: 'tab-workbench' },
8585
{ value: 'agent', label: 'Agent', icon: <Bot24Regular />, path: '/agent', testId: 'tab-agent' },
8686
]
8787
const activeTab = tabs.find((tab) => location.pathname.startsWith(tab.path))?.value ?? 'csvtickets'

frontend/src/features/usecase-demo/demoDefinitions.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@ Nutze ausschließlich CSV-Daten und nenne die verwendeten Ticket-IDs in Fließte
1717

1818
const SLA_BREACH_DEFAULT_PROMPT = `Call csv_sla_breach_tickets with default parameters (unassigned_only=true, include_ok=false).
1919
20-
Using the returned report, write ONLY a short markdown summary (max 200 words):
21-
1. State the reference_timestamp used
22-
2. Group ticket counts by breach_status and assigned_group, and confirm assignee is empty
23-
3. Recommend actions for the most critical breaches
20+
Then provide only a concise markdown "next actions" commentary:
21+
1. Mention the reference_timestamp used.
22+
2. Identify highest-risk assigned groups (breached first, then at_risk).
23+
3. Give concrete next actions for the next 30-60 minutes.
24+
4. Add one short escalation recommendation for unresolved breached tickets.
2425
25-
Do NOT output a JSON block — the frontend fetches and renders the ticket table directly from the API.`;
26+
Do NOT output a JSON block — the frontend already renders the SLA table from the API.`;
2627

2728
/**
2829
* Add new demos here to create additional pages without duplicating UI logic.
@@ -116,14 +117,13 @@ export const USECASE_DEMO_DEFINITIONS = [
116117
defaultPrompt: SLA_BREACH_DEFAULT_PROMPT,
117118
runHistoryLimit: 25,
118119
pollIntervalMs: 2000,
119-
resultViews: ["sla-breach", "markdown"],
120+
resultViews: ["sla-breach", "sla-next-actions"],
120121
resultSectionTitle: "SLA Breach Results",
121122
resultSectionDescription:
122123
"Tickets at risk or already past their SLA threshold, sorted by severity.",
123124
ticketIdFields: ["ticket_ids", "ticket_id", "ticketIds"],
124-
// Tickets are shown inline in the sla-breach result view; disable the separate card.
125125
matchingTickets: {
126-
enabled: true,
126+
enabled: false,
127127
title: "Affected Tickets (Group-Assigned, No Individual Assignee)",
128128
description:
129129
'These tickets are routed to a support group but no individual has picked them up. The "Assigned Group" column shows the responsible team; "Assignee" is empty for all.',

frontend/src/features/usecase-demo/resultViews.jsx

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
import {
2-
Badge,
3-
Button,
4-
Checkbox,
5-
Spinner,
6-
Text,
7-
ToolbarButton,
8-
Tooltip,
9-
makeStyles,
10-
tokens
2+
Badge,
3+
Button,
4+
Checkbox,
5+
Spinner,
6+
Text,
7+
ToolbarButton,
8+
Tooltip,
9+
makeStyles,
10+
tokens
1111
} from '@fluentui/react-components'
1212
import {
13-
ArrowUp24Regular,
14-
CheckmarkCircle24Regular,
15-
DismissCircle24Regular,
16-
Mail24Regular,
17-
SelectAllOn24Regular,
18-
Warning24Regular,
13+
ArrowUp24Regular,
14+
CheckmarkCircle24Regular,
15+
DismissCircle24Regular,
16+
Mail24Regular,
17+
SelectAllOn24Regular,
18+
Warning24Regular,
1919
} from '@fluentui/react-icons'
2020
import { useCallback, useEffect, useMemo, useState } from 'react'
2121
import ReactMarkdown from 'react-markdown'
@@ -387,6 +387,11 @@ export const RESULT_VIEW_REGISTRY = {
387387
description: 'Human-readable summary from the run output.',
388388
render: (props) => <ResultMarkdownView {...props} />,
389389
},
390+
'sla-next-actions': {
391+
title: 'Next Actions',
392+
description: 'Agent commentary on immediate follow-up actions based on SLA breach data.',
393+
render: (props) => <ResultMarkdownView {...props} />,
394+
},
390395
'sla-breach': {
391396
title: 'SLA Breach Overview',
392397
description: 'Unassigned tickets color-coded by SLA status. Select tickets to send reminders or escalate.',

0 commit comments

Comments
 (0)