From: ggori Date: Thu, 25 Sep 2025 08:04:18 +0000 (+0000) Subject: Integration of DSPy with tm LLM agent X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?a=commitdiff_plain;h=8c8d449fe67b7b111c8d86e6b2d9590eb4622329;p=aiml-fw%2Fawmf%2Ftm.git Integration of DSPy with tm LLM agent - Dockerfile Add build-essential to Dockerfile for native extention compilation - service Implement the DSPy-based LLM agent as singleton class that initializes with API key and the model, and processes user requests in natural language. - controller Recieves API requests, forwards them to service agent, and returns the processed results. Issue-Id: AIMLFW-249 Change-Id: I92ed260ef216de2e272e202c4584da75c77e5d0f Signed-off-by: ggori --- diff --git a/Dockerfile b/Dockerfile index 77eee87..a6ad69b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ WORKDIR /app # Install dependencies RUN apt-get update && \ - apt-get install -y --no-install-recommends python3-pip apt-utils git && \ + apt-get install -y --no-install-recommends python3-pip apt-utils git build-essential && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* diff --git a/requirements.txt b/requirements.txt index f9fd5d0..c1f10ef 100644 --- a/requirements.txt +++ b/requirements.txt @@ -32,3 +32,4 @@ marshmallow==3.26.1 psycopg2-binary==2.9.10 marshmallow-sqlalchemy flask-marshmallow +dspy==3.0.3 diff --git a/tox.ini b/tox.ini index 240a2e6..f1b0c32 100644 --- a/tox.ini +++ b/tox.ini @@ -47,6 +47,7 @@ deps= marshmallow-sqlalchemy flask-marshmallow psycopg2-binary==2.9.10 + dspy==3.0.3 setenv = cd = {toxinidir}/tests commands = diff --git a/trainingmgr/controller/agent_controller.py b/trainingmgr/controller/agent_controller.py index d9b5fc1..e7e69c3 100644 --- a/trainingmgr/controller/agent_controller.py +++ b/trainingmgr/controller/agent_controller.py @@ -16,14 +16,21 @@ # # ================================================================================== from flask import Blueprint, request, jsonify +from trainingmgr.service.agent_service import AgentClient agent_controller = Blueprint("agent_controller", __name__) +# Singleton instance of AgentClient +_agent_client = AgentClient() + +# Initialize the agent once when the module is loaded +_agent_client.initialize_agent() + @agent_controller.route("/modelInfo", methods=["GET"]) def model_info(): return jsonify({ "llm": { - "model": "" + "model": "", } }), 200 @@ -31,17 +38,38 @@ def model_info(): def generate_content(): body = request.get_json(silent=True) or {} text = body.get("text") + if not isinstance(text, str) or not text.strip(): return jsonify({ "title": "Bad Request", "status": 400, "detail": "The 'text' field is required and must be a non-empty string." }), 400 - dry_run = bool(body.get("dry_run", True)) - return jsonify({ - "action": "noop", - "request": {"text": text, "dry_run": dry_run}, - "response": {"note": "Received successfully"}, - "status": "ok", - "error_message": None - }), 200 \ No newline at end of file + + try: + result = _agent_client.process_user_request(text) + if result['success']: + return jsonify({ + "action": "completed", + "request": {"text": text}, + "response": {"result": result['result']}, + "status": "ok", + "error_message": None + }), 200 + else: + return jsonify({ + "action": "failed", + "request": {"text": text}, + "response": {"error": result['error']}, + "status": "error", + "error_message": result['error'] + }), 500 + + except Exception as err: + return jsonify({ + "action": "failed", + "request": {"text": text}, + "response": {"error": str(err)}, + "status": "error", + "error_message": str(err) + }), 500 \ No newline at end of file diff --git a/trainingmgr/service/agent_service.py b/trainingmgr/service/agent_service.py new file mode 100644 index 0000000..d1aacc8 --- /dev/null +++ b/trainingmgr/service/agent_service.py @@ -0,0 +1,103 @@ +# ================================================================================== +# +# Copyright (c) 2025 Taeil Jung All Rights Reserved. +# +# 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. +# +# ================================================================================== +import dspy +import os +from trainingmgr.common.trainingmgr_config import TrainingMgrConfig +from trainingmgr.common.exceptions_utls import TMException +from threading import Lock + +LOGGER = TrainingMgrConfig().logger + +# Define the agent signature +class AgentSignature(dspy.Signature): + """A signature for the DSPy agent.""" + query: str = dspy.InputField(desc= "The user's natural language request for creating feature group or registering model") + final: str = dspy.OutputField(desc= "Message that summarize the process result") + + +class AgentClient: + """Encapsulates the DSPy agent. Implements Singleton pattern if desired.""" + + _instance = None + _lock = Lock() + + def __new__(cls, *args, **kwargs): + """Singleton: ensure only one instance is created.""" + if cls._instance is None: + with cls._lock: + if cls._instance is None: + cls._instance = super().__new__(cls, *args, **kwargs) + return cls._instance + + def __init__(self): + self._agent = None + self._initialized = False + + def initialize_agent(self) -> bool: + """Initialize the DSPy agent with tools.""" + if self._initialized: + return True + + try: + agent_model = os.getenv("LLM_AGENT_MODEL_FOR_TM") + agent_token = os.getenv("LLM_AGENT_MODEL_TOKEN_FOR_TM") + + if not agent_model: + LOGGER.error("LLM_AGENT_MODEL_FOR_TM not specified") + return False + elif not agent_token: + LOGGER.error("LLM_AGENT_MODEL_TOKEN_FOR_TM not found") + return False + + # LM configuration + lm = dspy.LM(agent_model, api_key=agent_token) + dspy.configure(lm=lm) + + # Agent configuration + self._agent = dspy.ReAct( + AgentSignature, + tools=[], + max_iters=6 + ) + + self._initialized = True + LOGGER.info("Agent initialized successfully.") + return True + + except Exception as err: + raise TMException(f"fail to initialize agent exception : {str(err)}") + + def process_user_request(self, user_text_request): + """Process user request with agent tools.""" + if not self._initialized or self._agent is None: + raise TMException("Agent not initialized") + + try: + result = self._agent(query=user_text_request) + response = result.final + return { + 'success': True, + 'result': response, + } + except Exception as err: + LOGGER.error(f"Error processing user request: {str(err)}") + return { + 'success': False, + 'error': str(err), + } +