Skip to content

Commit 158dee8

Browse files
authored
feat: capture skill metadata for Agno and Google ADK (#238)
2 parents d6baaec + 183332a commit 158dee8

14 files changed

Lines changed: 383 additions & 31 deletions

File tree

.github/workflows/generate_workflows.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,9 @@
2525
batch_size=4,
2626
parallelism=4,
2727
)
28-
generate_misc_workflow(tox_ini_path, workflows_directory_path)
28+
generate_misc_workflow(
29+
tox_ini_path,
30+
workflows_directory_path,
31+
runner=arc_runner_label,
32+
)
2933
generate_contrib_workflow(workflows_directory_path)

.github/workflows/generate_workflows_lib/src/generate_workflows_lib/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,11 +356,13 @@ def generate_contrib_workflow(
356356
def generate_misc_workflow(
357357
tox_ini_path: Path,
358358
workflow_directory_path: Path,
359+
runner="ubuntu-latest",
359360
) -> None:
360361
_generate_workflow(
361362
get_misc_job_datas(get_tox_envs(tox_ini_path)),
362363
"misc",
363364
workflow_directory_path,
365+
runner=runner,
364366
)
365367

366368

@@ -445,6 +447,7 @@ def generate_extension_misc_workflow(
445447
tox_ini_path: Path,
446448
workflow_directory_path: Path,
447449
additional_config_path: Path,
450+
runner="ubuntu-latest",
448451
) -> None:
449452
loongsuite_envs = get_loongsuite_tox_envs(additional_config_path)
450453
if not loongsuite_envs:
@@ -455,6 +458,7 @@ def generate_extension_misc_workflow(
455458
"loongsuite_misc",
456459
"loongsuite_misc",
457460
workflow_directory_path,
461+
runner=runner,
458462
)
459463

460464

.github/workflows/generate_workflows_lib/src/generate_workflows_lib/loongsuite_misc.yml.j2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ jobs:
3636

3737
{{ job_data }}:
3838
name: LoongSuite {{ job_data }}
39-
runs-on: ubuntu-latest
39+
runs-on: {{ runner }}
4040
timeout-minutes: 30
4141
{%- if job_data == "generate-workflows" %}
4242
if: |

.github/workflows/generate_workflows_lib/src/generate_workflows_lib/misc.yml.j2

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@ name: Misc {{ file_number }}
55

66
on:
77
push:
8-
branches-ignore:
9-
- 'release/*'
10-
- 'otelbot/*'
8+
branches:
9+
- main
1110
pull_request:
1211

1312
permissions:
@@ -36,7 +35,7 @@ jobs:
3635

3736
{{ job_data }}:
3837
name: {{ job_data }}
39-
runs-on: ubuntu-latest
38+
runs-on: {{ "ubuntu-latest" if job_data == "docker-tests" else runner }}
4039
timeout-minutes: 30
4140
{%- if job_data == "generate-workflows" %}
4241
if: |

.github/workflows/generate_workflows_loongsuite.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,8 @@
2929
control_runner=arc_runner_label,
3030
)
3131
generate_extension_misc_workflow(
32-
tox_ini_path, workflows_directory_path, tox_loongsuite_ini_path
32+
tox_ini_path,
33+
workflows_directory_path,
34+
tox_loongsuite_ini_path,
35+
runner=arc_runner_label,
3336
)

.github/workflows/loongsuite_misc_0.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ jobs:
3535

3636
check-license-header:
3737
name: LoongSuite check-license-header
38-
runs-on: ubuntu-latest
38+
runs-on: loongsuite-python-arc
3939
timeout-minutes: 30
4040
steps:
4141
- name: Checkout repo @ SHA - ${{ github.sha }}
@@ -54,7 +54,7 @@ jobs:
5454

5555
generate-loongsuite:
5656
name: LoongSuite generate-loongsuite
57-
runs-on: ubuntu-latest
57+
runs-on: loongsuite-python-arc
5858
timeout-minutes: 30
5959
steps:
6060
- name: Checkout repo @ SHA - ${{ github.sha }}

.github/workflows/misc_0.yml

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@ name: Misc 0
55

66
on:
77
push:
8-
branches-ignore:
9-
- 'release/*'
10-
- 'otelbot/*'
8+
branches:
9+
- main
1110
pull_request:
1211

1312
permissions:
@@ -35,7 +34,7 @@ jobs:
3534

3635
spellcheck:
3736
name: spellcheck
38-
runs-on: ubuntu-latest
37+
runs-on: loongsuite-python-arc
3938
timeout-minutes: 30
4039
steps:
4140
- name: Checkout repo @ SHA - ${{ github.sha }}
@@ -73,7 +72,7 @@ jobs:
7372

7473
docs:
7574
name: docs
76-
runs-on: ubuntu-latest
75+
runs-on: loongsuite-python-arc
7776
timeout-minutes: 30
7877
if: |
7978
github.event.pull_request.user.login != 'otelbot[bot]' && github.event_name == 'pull_request'
@@ -94,7 +93,7 @@ jobs:
9493

9594
generate:
9695
name: generate
97-
runs-on: ubuntu-latest
96+
runs-on: loongsuite-python-arc
9897
timeout-minutes: 30
9998
steps:
10099
- name: Checkout repo @ SHA - ${{ github.sha }}
@@ -116,7 +115,7 @@ jobs:
116115

117116
generate-workflows:
118117
name: generate-workflows
119-
runs-on: ubuntu-latest
118+
runs-on: loongsuite-python-arc
120119
timeout-minutes: 30
121120
if: |
122121
!contains(github.event.pull_request.labels.*.name, 'Skip generate-workflows')
@@ -141,7 +140,7 @@ jobs:
141140

142141
shellcheck:
143142
name: shellcheck
144-
runs-on: ubuntu-latest
143+
runs-on: loongsuite-python-arc
145144
timeout-minutes: 30
146145
steps:
147146
- name: Checkout repo @ SHA - ${{ github.sha }}
@@ -160,7 +159,7 @@ jobs:
160159

161160
precommit:
162161
name: precommit
163-
runs-on: ubuntu-latest
162+
runs-on: loongsuite-python-arc
164163
timeout-minutes: 30
165164
steps:
166165
- name: Checkout repo @ SHA - ${{ github.sha }}
@@ -179,7 +178,7 @@ jobs:
179178

180179
typecheck:
181180
name: typecheck
182-
runs-on: ubuntu-latest
181+
runs-on: loongsuite-python-arc
183182
timeout-minutes: 30
184183
steps:
185184
- name: Checkout repo @ SHA - ${{ github.sha }}

instrumentation-loongsuite/loongsuite-instrumentation-agno/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
### Added
11+
12+
- Capture `gen_ai.skill.*` attributes on Agno skill tool spans such as
13+
`get_skill_instructions`, `get_skill_reference`, and `get_skill_script`.
14+
1015
## Version 0.7.0 (2026-07-03)
1116

1217
### Removed

instrumentation-loongsuite/loongsuite-instrumentation-agno/src/opentelemetry/instrumentation/agno/utils.py

Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
import json
1818
from dataclasses import asdict, is_dataclass
19-
from typing import Any, Mapping, Sequence
19+
from typing import Any, Dict, Mapping, Optional, Sequence
2020

2121
from opentelemetry.util.genai.extended_semconv.gen_ai_extended_attributes import (
2222
GEN_AI_SESSION_ID,
@@ -39,6 +39,11 @@
3939
)
4040

4141
_PROVIDER = "agno"
42+
_SKILL_TOOL_NAMES = {
43+
"get_skill_instructions",
44+
"get_skill_reference",
45+
"get_skill_script",
46+
}
4247

4348

4449
def _json_default(value: Any) -> Any:
@@ -82,6 +87,95 @@ def _to_dict(value: Any) -> dict[str, Any] | None:
8287
return None
8388

8489

90+
def _mapping_from_any(value: Any) -> Dict[str, Any]:
91+
data = _to_dict(value)
92+
if data is not None:
93+
return data
94+
if isinstance(value, str):
95+
try:
96+
loaded = json.loads(value)
97+
except Exception:
98+
return {}
99+
return loaded if isinstance(loaded, dict) else {}
100+
return {}
101+
102+
103+
def _first_text(*values: Any) -> Optional[str]:
104+
for value in values:
105+
if value is None:
106+
continue
107+
text = str(value).strip()
108+
if text:
109+
return text
110+
return None
111+
112+
113+
def _extract_skill_metadata(value: Any) -> Dict[str, Any]:
114+
data = _mapping_from_any(value)
115+
if not data:
116+
return {}
117+
frontmatter = _mapping_from_any(data.get("frontmatter"))
118+
metadata = _mapping_from_any(data.get("metadata")) or _mapping_from_any(
119+
frontmatter.get("metadata")
120+
)
121+
return {
122+
"name": _first_text(
123+
data.get("skill_name"),
124+
data.get("skill"),
125+
data.get("name"),
126+
frontmatter.get("name"),
127+
),
128+
"id": _first_text(
129+
data.get("skill_id"),
130+
data.get("id"),
131+
metadata.get("id"),
132+
),
133+
"description": _first_text(
134+
data.get("skill_description"),
135+
data.get("description"),
136+
frontmatter.get("description"),
137+
),
138+
"version": _first_text(
139+
data.get("skill_version"),
140+
data.get("version"),
141+
metadata.get("version"),
142+
),
143+
}
144+
145+
146+
def _apply_skill_metadata(
147+
invocation: ExecuteToolInvocation, metadata: Mapping[str, Any]
148+
) -> None:
149+
name = _first_text(metadata.get("name"))
150+
if name:
151+
invocation.skill_name = invocation.skill_name or name
152+
invocation.skill_id = (
153+
invocation.skill_id or _first_text(metadata.get("id")) or name
154+
)
155+
elif metadata.get("id"):
156+
invocation.skill_id = invocation.skill_id or _first_text(
157+
metadata.get("id")
158+
)
159+
160+
description = _first_text(metadata.get("description"))
161+
if description:
162+
invocation.skill_description = (
163+
invocation.skill_description or description
164+
)
165+
166+
version = _first_text(metadata.get("version"))
167+
if version:
168+
invocation.skill_version = invocation.skill_version or version
169+
170+
171+
def _apply_agno_skill_tool_metadata(
172+
invocation: ExecuteToolInvocation, data: Any
173+
) -> None:
174+
if invocation.tool_name not in _SKILL_TOOL_NAMES:
175+
return
176+
_apply_skill_metadata(invocation, _extract_skill_metadata(data))
177+
178+
85179
def _get_value(value: Any, name: str, default: Any = None) -> Any:
86180
if isinstance(value, Mapping):
87181
return value.get(name, default)
@@ -555,14 +649,16 @@ def update_llm_invocation_from_response(
555649

556650
def create_tool_invocation(function_call: Any) -> ExecuteToolInvocation:
557651
function = getattr(function_call, "function", None)
558-
return ExecuteToolInvocation(
652+
invocation = ExecuteToolInvocation(
559653
tool_name=getattr(function, "name", None) or "unknown_tool",
560654
provider=_PROVIDER,
561655
tool_call_id=getattr(function_call, "call_id", None),
562656
tool_description=getattr(function, "description", None),
563657
tool_type="function",
564658
tool_call_arguments=getattr(function_call, "arguments", None),
565659
)
660+
_apply_agno_skill_tool_metadata(invocation, invocation.tool_call_arguments)
661+
return invocation
566662

567663

568664
def update_tool_invocation_from_response(
@@ -572,3 +668,4 @@ def update_tool_invocation_from_response(
572668
invocation.tool_call_result = (
573669
result if isinstance(result, str) else _stringify(result)
574670
)
671+
_apply_agno_skill_tool_metadata(invocation, result)

0 commit comments

Comments
 (0)