Resolve "(CTTC) TFS Agentic module"

Closes #427 (closed)

Merge request reports

Loading
+5 −1
Changes for deploy/all.sh: 5 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -73,9 +73,13 @@ export TFS_COMPONENTS=${TFS_COMPONENTS:-"context device pathcomp service slice n
#export TFS_COMPONENTS="${TFS_COMPONENTS} pluggables"

# Uncomment to activate MCP Server.
# Keep it last so it is deployed after optional components it can expose.
# Keep it near the end so it is deployed after optional components it can expose.
#export TFS_COMPONENTS="${TFS_COMPONENTS} mcp_server"

# Uncomment to activate Agentic Module.
# Keep it after MCP Server so it can discover the local TFS MCP tool catalogue.
#export TFS_COMPONENTS="${TFS_COMPONENTS} agentic"

# If not already set, set the tag you want to use for your images.
export TFS_IMAGE_TAG=${TFS_IMAGE_TAG:-"dev"}

+157 −0
Changes for manifests/agenticservice.yaml: 157 added lines, 0 removed lines.
Original line number Diff line number Diff line
# Copyright 2022-2026 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

apiVersion: v1
kind: ConfigMap
metadata:
  name: agentic-config
data:
  ADK_MODEL: "openai/gpt-4.1-mini"
  ADK_AGENT_GRAPH: "single"
  ADK_DOMAIN_ID: "A"
  ADK_DOMAIN_NAME: "domain-a"
  ADK_PEERS: ""
  ADK_SESSION_DB_PATH: "/var/lib/tfs-agentic/adk_sessions.sqlite"
  ADK_SPECTRUM_DB_PATH: "/var/lib/tfs-agentic/adk_spectrum.sqlite"
  ADK_AGENT_RUN_TIMEOUT_SECONDS: "120"
  ADK_MCP_STARTUP_WARMUP_TIMEOUT_SECONDS: "10"
  ADK_MCP_TOOL_TIMEOUT_SECONDS: "30"
  ADK_AGENT_STARTUP_WARMUP_ENABLED: "1"
  ADK_AGENT_STARTUP_WARMUP_TIMEOUT_SECONDS: "20"
  ADK_AGENT_STARTUP_WARMUP_PROMPTS: "list existing devices"
  TFS_MCP_URL: "http://mcp-serverservice:3002/mcp/sse"
  TFS_DEFAULT_CONTEXT_UUID: "admin"
---
apiVersion: v1
kind: Secret
metadata:
  name: agentic-secrets
type: Opaque
stringData:
  OPENAI_API_KEY: ""
  TFS_MCP_AUTH_TOKEN: ""
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: agentic-data
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: agenticservice
spec:
  selector:
    matchLabels:
      app: agenticservice
  replicas: 1
  template:
    metadata:
      labels:
        app: agenticservice
    spec:
      terminationGracePeriodSeconds: 5
      containers:
        - name: server
          image: labs.etsi.org:5050/tfs/controller/agentic:latest
          imagePullPolicy: Always
          ports:
            - containerPort: 8800
          env:
            - name: LOG_LEVEL
              value: "INFO"
            - name: LITELLM_LOCAL_MODEL_COST_MAP
              value: "true"
            - name: LITELLM_MODEL_COST_MAP_URL
              value: ""
            - name: ADK_DISABLE_JSON_SCHEMA_FOR_FUNC_DECL
              value: "1"
          envFrom:
            - configMapRef:
                name: agentic-config
            - secretRef:
                name: agentic-secrets
          volumeMounts:
            - name: agentic-data
              mountPath: /var/lib/tfs-agentic
          readinessProbe:
            httpGet:
              path: /health
              port: 8800
            initialDelaySeconds: 10
            periodSeconds: 10
            failureThreshold: 12
          livenessProbe:
            httpGet:
              path: /health
              port: 8800
            initialDelaySeconds: 20
            periodSeconds: 20
            failureThreshold: 6
          resources:
            requests:
              cpu: 250m
              memory: 512Mi
            limits:
              cpu: 2000m
              memory: 2Gi
      volumes:
        - name: agentic-data
          persistentVolumeClaim:
            claimName: agentic-data
---
apiVersion: v1
kind: Service
metadata:
  name: agenticservice
  labels:
    app: agenticservice
spec:
  type: ClusterIP
  selector:
    app: agenticservice
  ports:
    - name: http
      protocol: TCP
      port: 8800
      targetPort: 8800
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: tfs-ingress-agentic
  annotations:
    nginx.ingress.kubernetes.io/limit-rps: "20"
    nginx.ingress.kubernetes.io/limit-connections: "20"
    nginx.ingress.kubernetes.io/proxy-connect-timeout: "60"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
    nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
  rules:
    - http:
        paths:
          - path: /agentic(/|$)(.*)
            pathType: ImplementationSpecific
            backend:
              service:
                name: agenticservice
                port:
                  number: 8800
+19 −0
Changes for scripts/show_logs_agentic.sh: 19 added lines, 0 removed lines.
Original line number Diff line number Diff line
#!/bin/bash
# Copyright 2022-2026 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# If not already set, set the name of the Kubernetes namespace to deploy to.
export TFS_K8S_NAMESPACE=${TFS_K8S_NAMESPACE:-"tfs"}

kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/agenticservice -c server
+212 −0
Changes for src/agentic/service/agents/__init__.py: 212 added lines, 0 removed lines.
Original line number Diff line number Diff line
# Copyright 2022-2026 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Agent package exports with lazy loading."""

__all__ = [
    "connection_header_query_agent",
    "cross_domain_connection_retrieval_agent",
    "cross_domain_device_retrieval_agent",
    "cross_domain_optical_link_retrieval_agent",
    "cross_domain_optical_create_agent",
    "cross_domain_optical_delete_agent",
    "cross_domain_packet_link_retrieval_agent",
    "cross_domain_retrieval_agent",
    "cross_domain_service_retrieval_agent",
    "detailed_connection_query_agent",
    "detailed_device_query_agent",
    "detailed_optical_link_query_agent",
    "detailed_packet_link_query_agent",
    "detailed_service_query_agent",
    "device_mutation_agent",
    "device_header_query_agent",
    "inventory_agent",
    "inventory_service_root_agent",
    "local_optical_create_agent",
    "local_optical_delete_agent",
    "local_connection_retrieval_agent",
    "local_device_retrieval_agent",
    "local_optical_link_retrieval_agent",
    "local_packet_link_retrieval_agent",
    "local_retrieval_agent",
    "local_service_retrieval_agent",
    "l2vpn_service_mutation_agent",
    "l3vpn_service_mutation_agent",
    "link_mutation_agent",
    "mutation_router_agent",
    "optical_service_mutation_agent",
    "optical_link_header_query_agent",
    "packet_link_header_query_agent",
    "retrieval_router_agent",
    "root_agent",
    "service_agent",
    "service_header_query_agent",
    "service_mutation_agent",
]


def __getattr__(name):
    if name == "root_agent":
        from .granular_root import root_agent

        return root_agent
    if name == "inventory_service_root_agent":
        from .root import inventory_service_root_agent

        return inventory_service_root_agent
    if name == "inventory_agent":
        from .inventory import inventory_agent

        return inventory_agent
    if name == "service_agent":
        from .service import service_agent

        return service_agent
    if name == "retrieval_router_agent":
        from .retrieval import retrieval_router_agent

        return retrieval_router_agent
    if name == "local_retrieval_agent":
        from .retrieval import local_retrieval_agent

        return local_retrieval_agent
    if name == "cross_domain_retrieval_agent":
        from .retrieval import cross_domain_retrieval_agent

        return cross_domain_retrieval_agent
    if name == "local_device_retrieval_agent":
        from .retrieval import local_device_retrieval_agent

        return local_device_retrieval_agent
    if name == "cross_domain_device_retrieval_agent":
        from .retrieval import cross_domain_device_retrieval_agent

        return cross_domain_device_retrieval_agent
    if name == "local_packet_link_retrieval_agent":
        from .retrieval import local_packet_link_retrieval_agent

        return local_packet_link_retrieval_agent
    if name == "cross_domain_packet_link_retrieval_agent":
        from .retrieval import cross_domain_packet_link_retrieval_agent

        return cross_domain_packet_link_retrieval_agent
    if name == "local_optical_link_retrieval_agent":
        from .retrieval import local_optical_link_retrieval_agent

        return local_optical_link_retrieval_agent
    if name == "cross_domain_optical_link_retrieval_agent":
        from .retrieval import cross_domain_optical_link_retrieval_agent

        return cross_domain_optical_link_retrieval_agent
    if name == "local_service_retrieval_agent":
        from .retrieval import local_service_retrieval_agent

        return local_service_retrieval_agent
    if name == "cross_domain_service_retrieval_agent":
        from .retrieval import cross_domain_service_retrieval_agent

        return cross_domain_service_retrieval_agent
    if name == "local_connection_retrieval_agent":
        from .retrieval import local_connection_retrieval_agent

        return local_connection_retrieval_agent
    if name == "cross_domain_connection_retrieval_agent":
        from .retrieval import cross_domain_connection_retrieval_agent

        return cross_domain_connection_retrieval_agent
    if name == "mutation_router_agent":
        from .mutation import mutation_router_agent

        return mutation_router_agent
    if name == "service_mutation_agent":
        from .mutation import service_mutation_agent

        return service_mutation_agent
    if name == "optical_service_mutation_agent":
        from .mutation import optical_service_mutation_agent

        return optical_service_mutation_agent
    if name == "l2vpn_service_mutation_agent":
        from .mutation import l2vpn_service_mutation_agent

        return l2vpn_service_mutation_agent
    if name == "l3vpn_service_mutation_agent":
        from .mutation import l3vpn_service_mutation_agent

        return l3vpn_service_mutation_agent
    if name == "device_mutation_agent":
        from .mutation import device_mutation_agent

        return device_mutation_agent
    if name == "link_mutation_agent":
        from .mutation import link_mutation_agent

        return link_mutation_agent
    if name == "local_optical_create_agent":
        from .optical_create import local_optical_create_agent

        return local_optical_create_agent
    if name == "cross_domain_optical_create_agent":
        from .optical_create import cross_domain_optical_create_agent

        return cross_domain_optical_create_agent
    if name == "local_optical_delete_agent":
        from .optical_delete import local_optical_delete_agent

        return local_optical_delete_agent
    if name == "cross_domain_optical_delete_agent":
        from .optical_delete import cross_domain_optical_delete_agent

        return cross_domain_optical_delete_agent
    if name == "device_header_query_agent":
        from .inventory_granular import device_header_query_agent

        return device_header_query_agent
    if name == "detailed_device_query_agent":
        from .inventory_granular import detailed_device_query_agent

        return detailed_device_query_agent
    if name == "packet_link_header_query_agent":
        from .inventory_granular import packet_link_header_query_agent

        return packet_link_header_query_agent
    if name == "detailed_packet_link_query_agent":
        from .inventory_granular import detailed_packet_link_query_agent

        return detailed_packet_link_query_agent
    if name == "optical_link_header_query_agent":
        from .inventory_granular import optical_link_header_query_agent

        return optical_link_header_query_agent
    if name == "detailed_optical_link_query_agent":
        from .inventory_granular import detailed_optical_link_query_agent

        return detailed_optical_link_query_agent
    if name == "service_header_query_agent":
        from .inventory_granular import service_header_query_agent

        return service_header_query_agent
    if name == "detailed_service_query_agent":
        from .inventory_granular import detailed_service_query_agent

        return detailed_service_query_agent
    if name == "connection_header_query_agent":
        from .inventory_granular import connection_header_query_agent

        return connection_header_query_agent
    if name == "detailed_connection_query_agent":
        from .inventory_granular import detailed_connection_query_agent

        return detailed_connection_query_agent
    raise AttributeError(name)
+50 −0
Changes for src/agentic/service/agents/granular_root.py: 50 added lines, 0 removed lines.
Original line number Diff line number Diff line
# Copyright 2022-2026 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Granular root router agent."""

from __future__ import annotations

from google.adk.agents.llm_agent import Agent
from google.adk.models.lite_llm import LiteLlm

from agentic.service.agents.mutation import mutation_router_agent
from agentic.service.agents.retrieval import retrieval_router_agent
from agentic.service.llm import build_litellm


GRANULAR_ROOT_ROUTER_INSTRUCTION = """
You are the first TFS Agentic request router.

Choose only between:
- retrieval_router_agent for read-only retrieval, listing, inspection,
  location, details, links, services, connections, topology, or inventory.
- mutation_router_agent for create, delete, remove, provision, teardown,
  reserve, update, or configure requests.

Do not answer directly and do not call tools. Preserve the user's names,
request IDs, bandwidth, and spectrum wording when delegating.
"""

LiteLlm.model_fields["llm_client"].exclude = True
LiteLlm.model_rebuild(force=True)


root_agent = Agent(
    model=build_litellm(),
    name="tfs_agentic_granular_root_router",
    description="Minimal TFS Agentic root router for specialist agents.",
    instruction=GRANULAR_ROOT_ROUTER_INSTRUCTION,
    sub_agents=[retrieval_router_agent, mutation_router_agent],
)
Loading
Loading