From e6e27889d482b9d5016fd485e996c12a969729f5 Mon Sep 17 00:00:00 2001 From: DomPTech Date: Fri, 17 Jul 2026 13:59:24 -0400 Subject: [PATCH 1/2] feat: introduce LLM Tango device and script for running LLM agent --- asyncroscopy/mcp/llm.py | 176 +++++++++++++++++++++++++++++++++++++ configs/gemma-llm.yaml | 8 ++ startup_scripts/run_llm.py | 117 ++++++++++++++++++++++++ 3 files changed, 301 insertions(+) create mode 100644 asyncroscopy/mcp/llm.py create mode 100644 configs/gemma-llm.yaml create mode 100644 startup_scripts/run_llm.py diff --git a/asyncroscopy/mcp/llm.py b/asyncroscopy/mcp/llm.py new file mode 100644 index 0000000..fb36634 --- /dev/null +++ b/asyncroscopy/mcp/llm.py @@ -0,0 +1,176 @@ +"""Tango device wrapping a LangChain AI agent that connects to the MCP server.""" + +import asyncio +import json +import warnings + +import tango +from tango.server import Device, attribute, command, device_property + +try: + from langchain.chat_models import init_chat_model + from langchain_mcp_adapters.client import MultiServerMCPClient + from langchain_core.tools import BaseTool +except ImportError: + print("Install the optional AI extra with uv sync --extra agent") + + +class LLM(Device): + mcp_url = device_property(dtype=str, default_value="http://127.0.0.1:8000/mcp") + model_provider = device_property(dtype=str, default_value="openai") + model_name = device_property(dtype=str, default_value="gpt-4o") + api_key = device_property(dtype=str, default_value="") + local_model_path = device_property(dtype=str, default_value=None) + + def init_device(self) -> None: + """Initialize the Tango device.""" + Device.init_device(self) + self.set_state(tango.DevState.INIT) + + self._model = None + self._tools: list[BaseTool] = [] + + try: + asyncio.run(self.start_session()) + self.set_state(tango.DevState.ON) + self.info_stream("LLM device initialized") + except Exception as e: + self.set_state(tango.DevState.FAULT) + self.set_status(f"Initialization failed: {e}") + + async def start_session(self): + """Initialize MCP server connection and load model.""" + try: + client = MultiServerMCPClient( + { + "asyncroscopy": { + "url": self.mcp_url, + "transport": "streamable_http", + } + } + ) + self._tools = await client.get_tools() + + if self.local_model_path is not None: + # Load drivers and weights lazily + try: + import torch + from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline, BitsAndBytesConfig + from langchain_huggingface import ChatHuggingFace, HuggingFacePipeline + import transformers.utils.logging as transformer_logging + except ImportError: + raise ImportError("Failed to import local AI dependencies. Please run: uv sync --extra agent --extra localagent") + + transformer_logging.set_verbosity_error() + warnings.simplefilter(action='ignore', category=FutureWarning) + + quantization_config = BitsAndBytesConfig( + load_in_4bit=True, + bnb_4bit_compute_dtype=torch.float16 if torch.cuda.is_available() else torch.float32, + bnb_4bit_quant_type="nf4", + ) + + raw_path = str(self.local_model_path) + tokenizer = AutoTokenizer.from_pretrained(raw_path) + hf_model = AutoModelForCausalLM.from_pretrained( + raw_path, + device_map="auto", + quantization_config=quantization_config, + ) + + hf_pipeline = pipeline( + "text-generation", + model=hf_model, + tokenizer=tokenizer, + max_new_tokens=256, + temperature=0.2, + return_full_text=False, + ) + + self._model = ChatHuggingFace(llm=HuggingFacePipeline(pipeline=hf_pipeline)) + else: + # Initialize model with api key + self._model = init_chat_model( + model=self.model_name, + model_provider=self.model_provider, + api_key=self.api_key or None, + ) + except Exception as e: + self.error_stream(f"Failed to start session: {e}") + raise + + @attribute(dtype=str, doc="List of tools the LLM has") + def tools(self) -> str: + tool_data = [{"name": t.name, "description": t.description} for t in self._tools] + return json.dumps(tool_data) + + @command(dtype_in=str, dtype_out=str) + def Query(self, prompt: str) -> str: + """Run a query using the langchain AI agent, executing it in a synchronous event loop wrapper.""" + return asyncio.run(self._run_agent(prompt)) + + async def _run_agent(self, prompt: str) -> str: + """Run the async agent query loop to invoke tools from the MCP server.""" + if not self._model: + return "Model not initialized." + + tools_string = "\n".join([f"- Name: {t.name}\n Description: {t.description}\n" for t in self._tools]) + + agent_context = f"""You are an AI Agent with access to these tools: + {tools_string} + + To use a tool, you MUST respond using this exact format: + Action: + Arguments: + + When you have the final answer, respond with: + Final Answer: + + User Request: {prompt}""" + + for step in range(5): + if self.local_model_path is not None: + raw_response = self._model.invoke(agent_context).content + else: + raw_response = await self._model.ainvoke(agent_context) + response = raw_response.replace("", "").strip() + + # Check if the model wants to call a tool + if "Action:" in response and "Arguments:" in response: + try: + # Parse out the tool execution details + tool_name = response.split("Action:")[1].split("\n")[0].strip() + tool_args_raw = response.split("Arguments:")[1].split("\n")[0].strip() + + # Convert args to dict if it's JSON, otherwise keep as string + try: + tool_args = json.loads(tool_args_raw) + except json.JSONDecodeError: + tool_args = tool_args_raw + + # Find the matching tool object from loaded MCP tools + active_tool = next((t for t in self._tools if t.name == tool_name), None) + + if active_tool: + self.info_stream(f"[Executing Tool]: Calling {tool_name}({tool_args})...") + observation = await active_tool.ainvoke(tool_args) + self.info_stream(f"[Tool Result]: {observation}\n") + + # Feed the observation back to the model's memory context + agent_context += f"\n{response}\nObservation: {observation}" + else: + self.error_stream(f"Error: Model tried to call unknown tool '{tool_name}'\n") + break + except Exception as e: + self.error_stream(f"Parsing/Execution error: {e}") + break + + elif "Final Answer:" in response: + return response.split("Final Answer:", 1)[1].strip() + else: + self.info_stream("\nModel responded with plain text instead of using a tool format.") + return response + return "Error: Agent reached maximum steps without providing a final answer." + + if __name__ == "__main__": + LLM.run_server() \ No newline at end of file diff --git a/configs/gemma-llm.yaml b/configs/gemma-llm.yaml new file mode 100644 index 0000000..7be0280 --- /dev/null +++ b/configs/gemma-llm.yaml @@ -0,0 +1,8 @@ +# Gemma4 31B local model startup config for LLM Tango device. + +tango: + host: localhost + port: 9094 + +mcp_url: "http://127.0.0.1:8000/mcp" +local_model_path: C:\Users\Public\Desktop\Agents\Gemma4-31B \ No newline at end of file diff --git a/startup_scripts/run_llm.py b/startup_scripts/run_llm.py new file mode 100644 index 0000000..0ea862c --- /dev/null +++ b/startup_scripts/run_llm.py @@ -0,0 +1,117 @@ +import sys +import argparse +from gevent import os +import yaml +from pathlib import Path + +PROJECT_DIR = Path(__file__).resolve().parents[1] +if str(PROJECT_DIR) not in sys.path: + sys.path.insert(0, str(PROJECT_DIR)) + +import threading +import time +import tango +from asyncroscopy.mcp.llm import LLM +from tango import DeviceProxy + +DEVICE_NAME = "asyncroscopy/llm/default" +INSTANCE_NAME = "llm_instance" + +def register_device(config: dict | None): + database = tango.Database() + try: + device_info = tango.DbDevInfo() + device_info.server = f"LLM/{INSTANCE_NAME}" + device_info._class = "LLM" + device_info.name = DEVICE_NAME + database.add_device(device_info) + print(f"Registered device: {DEVICE_NAME}") + except tango.DevFailed as e: + print(f"Device already registered or error: {e}") + + if config: + # Map config keys to device properties + properties = {} + if "mcp_url" in config: + properties["mcp_url"] = [config["mcp_url"]] + if "local_model_path" in config: + properties["local_model_path"] = [config["local_model_path"]] + if "model_provider" in config: + properties["model_provider"] = [config["model_provider"]] + if "model_name" in config: + properties["model_name"] = [config["model_name"]] + if "api_key" in config: + properties["api_key"] = [config["api_key"]] + + if properties: + database.put_device_property(DEVICE_NAME, properties) + print(f"Set device properties: {properties}") + +def run_server(): + # Pass the instance name to run_server + sys.argv = ["llm.py", INSTANCE_NAME] + LLM.run_server() + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--yaml", type=Path, help="Path to YAML configuration") + parser.add_argument("--interactive", type=bool, default=False, help="Run in interactive mode") + args = parser.parse_args() + + config = None + if args.yaml: + with open(args.yaml, "r") as f: + config = yaml.safe_load(f) + print(f"Loaded config from {args.yaml}") + + os.environ['TANGO_HOST'] = f'{config["tango"]["host"]}:{config["tango"]["port"]}' + + register_device(config) + + # Start server in thread + threading.Thread(target=run_server, daemon=True).start() + + # Wait for device to be ready + print("Waiting for LLM device to start and initialize...") + proxy = None + max_wait_seconds = 120 # Increased timeout for local model loading + + for _ in range(max_wait_seconds): + try: + if proxy is None: + proxy = DeviceProxy(DEVICE_NAME) + proxy.ping() # Ensure server is reachable first + + # Check the actual device state + state = proxy.state() + if state == tango.DevState.ON: + print("Device initialized and ready.") + break + elif state == tango.DevState.FAULT: + print(f"Device initialization failed. Status: {proxy.status()}") + return + # If state is INIT, continue waiting + + except Exception: + proxy = None # Reset proxy if connection fails + + time.sleep(1) + else: + print("Timeout waiting for device to initialize.") + return + + if args.interactive: + print("Entering interactive mode. Type 'exit' to quit.") + while True: + prompt = input("LLM Prompt (or 'exit'): ") + if prompt.lower() == 'exit': + break + + try: + response = proxy.Query(prompt) + print(f"Response: {response}") + except Exception as e: + print(f"Error: {e}") + +if __name__ == "__main__": + main() From 56587a9bf57a05a7faab57635ea30187ad7b45ef Mon Sep 17 00:00:00 2001 From: DomPTech Date: Fri, 17 Jul 2026 14:45:54 -0400 Subject: [PATCH 2/2] refactor: Remade agent notebook to use new LLM device; fix `run_llm` to persist in background when not in interactive mode --- notebooks/11_Test_AI_Agent.ipynb | 347 ++++--------------------------- startup_scripts/run_llm.py | 12 +- 2 files changed, 45 insertions(+), 314 deletions(-) diff --git a/notebooks/11_Test_AI_Agent.ipynb b/notebooks/11_Test_AI_Agent.ipynb index ae3b9bf..ee61b12 100644 --- a/notebooks/11_Test_AI_Agent.ipynb +++ b/notebooks/11_Test_AI_Agent.ipynb @@ -17,14 +17,15 @@ "source": [ "## Prereqs\n", "\n", - "Start the Tango stack and MCP server first:\n", + "Start the Tango stack, MCP server, and LLM device first:\n", "\n", "```bash\n", "uv run startup_scripts/run_servers.py\n", "uv run startup_scripts/run_mcp.py --yaml configs/mcp.yaml\n", + "uv run startup_scripts/run_llm.py --yaml configs/gemma-llm.yaml\n", "```\n", "\n", - "Install the optional AI extras:\n", + "If not already done, install the optional AI extras:\n", "\n", "```bash\n", "uv sync --extra agent\n", @@ -32,375 +33,99 @@ "```" ] }, - { - "cell_type": "code", - "execution_count": 1, - "id": "76a9e540", - "metadata": {}, - "outputs": [], - "source": [ - "from pprint import pprint\n", - "\n", - "from langchain.agents import create_agent\n", - "from langchain.chat_models import init_chat_model\n", - "from langchain_mcp_adapters.client import MultiServerMCPClient\n", - "from langchain_core.tools import BaseTool" - ] - }, { "cell_type": "markdown", - "id": "d98c5fa3", + "id": "bba33b2b", "metadata": {}, "source": [ - "### Connect to the running MCP server" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "38070335", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Loaded 52 tool(s)\n", - "['get_data_from_key', 'list_devices', 'CAMERA_State', 'CAMERA_Status', 'DATA_State', 'DATA_Status', 'DATA_configure', 'DATA_get_config', 'DATA_register_path', 'DATA_register_save_path', 'DATA_start_tiled_server', 'DATA_stop_tiled_server', 'EDS_State', 'EDS_Status', 'FLUCAM_State', 'FLUCAM_Status', 'DigitalTwin_Connect', 'DigitalTwin_Disconnect', 'DigitalTwin_State', 'DigitalTwin_Status', 'DigitalTwin_acquire_camera_image', 'DigitalTwin_acquire_flucam_image', 'DigitalTwin_acquire_scanned_data_advanced', 'DigitalTwin_acquire_scanned_image', 'DigitalTwin_acquire_spectrum', 'DigitalTwin_auto_focus', 'DigitalTwin_blank_beam', 'DigitalTwin_calibrate_screen_current', 'DigitalTwin_get_beam_tilt', 'DigitalTwin_get_defocus', 'DigitalTwin_get_diffraction_shift', 'DigitalTwin_get_fov', 'DigitalTwin_get_image_data_cached', 'DigitalTwin_get_image_shift', 'DigitalTwin_get_screen_current', 'DigitalTwin_get_stage', 'DigitalTwin_get_status', 'DigitalTwin_move_stage', 'DigitalTwin_place_beam', 'DigitalTwin_place_beam_list', 'DigitalTwin_set_beam_tilt', 'DigitalTwin_set_column_valves', 'DigitalTwin_set_defocus', 'DigitalTwin_set_diffraction_shift', 'DigitalTwin_set_fov', 'DigitalTwin_set_image_shift', 'DigitalTwin_set_screen_current', 'DigitalTwin_unblank_beam', 'SCAN_State', 'SCAN_Status', 'STAGE_State', 'STAGE_Status']\n" - ] - } - ], - "source": [ - "MCP_URL = \"http://127.0.0.1:8000/mcp\"\n", - "\n", - "client = MultiServerMCPClient(\n", - " {\n", - " \"asyncroscopy\": {\n", - " \"url\": MCP_URL,\n", - " \"transport\": \"streamable_http\",\n", - " }\n", - " }\n", - ")\n", - "\n", - "tools = await client.get_tools()\n", - "print(f\"Loaded {len(tools)} tool(s)\")\n", - "print([tool.name for tool in tools])" - ] - }, - { - "cell_type": "markdown", - "id": "8b0e9baa", - "metadata": {}, - "source": [ - "### Optionally filter tools to test a few at a time" + "## Imports" ] }, { "cell_type": "code", "execution_count": null, - "id": "475a8e82", + "id": "c4d20080", "metadata": {}, "outputs": [], "source": [ - "def filter_tools(tools: list[BaseTool], tool_names: list[str]) -> list[BaseTool]:\n", - " return [tool for tool in tools if tool.name in tool_names]\n", - "\n", - "# Filter the tools so we can test a few at a time\n", - "tools = filter_tools(tools, [\"list_devices\", \"AutoScriptMicroscope_acquire_scanned_image\", \"DigitalTwin_acquire_scanned_image\", \"get_data_from_key\"])\n", - "print(f\"Filtered to {len(tools)} tool(s): {[tool.name for tool in tools]}\")" + "import os\n", + "import tango" ] }, { "cell_type": "markdown", - "id": "2f726c1f", + "id": "f0ed206e", "metadata": {}, "source": [ - "### Run cell below to use an OPENAI-COMPATIBLE model" + "## Ping Servers" ] }, { "cell_type": "code", "execution_count": null, - "id": "358a7f9f", - "metadata": {}, - "outputs": [], - "source": [ - "model = init_chat_model(\n", - " model=\"YOUR MODEL NAME\",\n", - " model_provider=\"openai, google_genai, etc.\", # Make sure to install the right provider package for the model you want to use\n", - " api_key=\"YOUR API KEY\", \n", - ")\n", - "\n", - "using_local_model = False" - ] - }, - { - "cell_type": "markdown", - "id": "9697e487", - "metadata": {}, - "source": [ - "### Run cell below to use a LOCAL model" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "8ed64650", + "id": "04f5aae1", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "CUDA Available: True\n", - "Device Count: 1\n", - "Current Device: NVIDIA GeForce RTX 5090\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "W0713 13:26:05.132000 9444 Lib\\site-packages\\torch\\utils\\flop_counter.py:29] triton not found; flop counting will not work for triton kernels\n" - ] - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "890a459fe8684c239cff16175309941b", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Loading weights: 0%| | 0/1188 [00:00\n", - " Arguments: \n", - "\n", - " When you have the final answer, respond with:\n", - " Final Answer: \n", - "\n", - " User Request: {user_prompt}\"\"\"\n", - "\n", - " print(\"Starting Local Agent...\\n\")\n", - " \n", - " for step in range(5):\n", - " if is_local_model:\n", - " raw_response = model.invoke(agent_context).content\n", - " else:\n", - " raw_response = await model.ainvoke(agent_context)\n", - " response = raw_response.replace(\"\", \"\").strip()\n", - " print(response)\n", - " \n", - " # Check if the model wants to call a tool\n", - " if \"Action:\" in response and \"Arguments:\" in response:\n", - " try:\n", - " # Parse out the tool execution details\n", - " tool_name = response.split(\"Action:\")[1].split(\"\\n\")[0].strip()\n", - " tool_args_raw = response.split(\"Arguments:\")[1].split(\"\\n\")[0].strip()\n", - " \n", - " # Convert args to dict if it's JSON, otherwise keep as string\n", - " try:\n", - " tool_args = json.loads(tool_args_raw)\n", - " except json.JSONDecodeError:\n", - " tool_args = tool_args_raw\n", - "\n", - " # Find the matching tool object from loaded MCP tools\n", - " active_tool = next((t for t in tool_list if t.name == tool_name), None)\n", - " \n", - " if active_tool:\n", - " print(f\"[Executing Tool]: Calling {tool_name}({tool_args})...\")\n", - " observation = await active_tool.ainvoke(tool_args)\n", - " print(f\"[Tool Result]: {observation}\\n\")\n", - " \n", - " # Feed the observation back to the model's memory context\n", - " agent_context += f\"\\n{response}\\nObservation: {observation}\"\n", - " else:\n", - " print(f\"Error: Model tried to call unknown tool '{tool_name}'\\n\")\n", - " break\n", - " except Exception as e:\n", - " print(f\"Parsing/Execution error: {e}\")\n", - " break\n", - " \n", - " elif \"Final Answer:\" in response:\n", - " break\n", - " else:\n", - " print(\"\\nModel responded with plain text instead of using a tool format.\")\n", - " break" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "b50a2f06", - "metadata": {}, - "outputs": [], - "source": [ - "import warnings\n", - "import transformers.utils.logging as logging\n", - "from warnings import simplefilter\n", + "os.environ[\"TANGO_HOST\"] = f\"{DB_HOST}:{DB_PORT}\"\n", "\n", - "# Ignore harmless FutureWarnings and transformers warnings\n", - "simplefilter(action='ignore', category=FutureWarning)\n", - "logging.set_verbosity_error()" - ] - }, - { - "cell_type": "markdown", - "id": "a5aeea46", - "metadata": {}, - "source": [ - "### Basic prompt" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "3fd88186", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Starting Local Agent...\n", - "\n", - "I have access to a wide range of tools categorized by their Tango Device Classes. Here is a summary of the tools available to me:\n", - "\n", - "**1. Digital Twin Control (`DigitalTwin`)**\n", - "- **Connectivity:** `DigitalTwin_Connect`, `DigitalTwin_Disconnect`, `DigitalTwin_State`, `DigitalTwin_Status`, `DigitalTwin_get_status`.\n", - "- **Acquisition:** `DigitalTwin_acquire_camera_image`, `DigitalTwin_acquire_flucam_image`, `DigitalTwin_acquire_scanned_image`, `DigitalTwin_acquire_spectrum`, `DigitalTwin_acquire_scanned_data_advanced`, `DigitalTwin_get_image_data_cached`.\n", - "- **Beam & Stage Control:** `DigitalTwin_move_stage`, `DigitalTwin_get_stage`, `DigitalTwin_place_beam`, `DigitalTwin_place_beam_list`, `DigitalTwin_blank_beam`, `DigitalTwin_unblank_beam`.\n", - "- **Optical/System Adjustments:** `DigitalTwin_auto_focus`, `DigitalTwin_set_fov`/`get_fov`, `DigitalTwin_set_defocus`/`get_defocus`, `DigitalTwin_\n", - "\n", - "Model responded with plain text instead of using a tool format.\n" - ] - } - ], - "source": [ - "prompt = \"What tools can you access?\"\n", - "await run_agent_demo(prompt, tools, is_local_model=using_local_model)" + "llm = tango.DeviceProxy(\"asyncroscopy/llm/default\")\n", + "llm.set_timeout_millis(120_000)\n", + "llm.ping()\n", + "print(llm.name(), llm.state())" ] }, { "cell_type": "markdown", - "id": "7f42ed2a", + "id": "814dc691", "metadata": {}, "source": [ - "### Test image acquisition" + "## Give a prompt" ] }, { "cell_type": "code", - "execution_count": 9, - "id": "185af0a4", + "execution_count": 3, + "id": "a1e65cdd", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Starting Local Agent...\n", - "\n", - "Action: DigitalTwin_acquire_scanned_image\n", - "Arguments: {\"detector_list\": [\"HAADF\"]}\n", - "[Executing Tool]: Calling DigitalTwin_acquire_scanned_image({'detector_list': ['HAADF']})...\n", - "[Tool Result]: [{'type': 'text', 'text': 'stem_image_HAADF_20260713T132752058144.h5', 'id': 'lc_bf3782b5-437b-4d26-b30c-d3ec8e22c7f3'}]\n", - "\n", - "Action: get_data_from_key\n", - "Arguments: {\"key\": \"stem_image_HAADF_20260713T132752058144.h5\"}\n", - "[Executing Tool]: Calling get_data_from_key({'key': 'stem_image_HAADF_20260713T132752058144.h5'})...\n", - "[Tool Result]: [{'type': 'text', 'text': '{\"key\":\"stem_image_HAADF_20260713T132752058144.h5\",\"path\":\"outputs\\\\\\\\tiled_acquisitions\\\\\\\\stem_image_HAADF_20260713T132752058144.h5\",\"size_bytes\":1054720,\"format\":\"hdf5\",\"attrs\":{},\"datasets\":[{\"name\":\"image/HAADF\",\"shape\":[512,512],\"dtype\":\"float32\",\"attrs\":{\"acquisition_type\":\"stem_image\",\"detector\":\"HAADF\"},\"preview\":[0.05876747891306877,0.059358369559049606,0.06118931993842125,0.06082397326827049,0.0564676932990551,0.04990082606673241,0.04418998211622238,0.03987928479909897,0.03610394150018692,0.035545140504837036,0.04252124950289726,0.05505423620343208,0.06561834365129471,0.07019422203302383,0.07148002833127975,0.07285333424806595,0.07347744703292847,0.07066406309604645,0.06401708722114563,0.056383710354566574,0.051454104483127594,0.05054553970694542,0.05208514258265495,0.0538441427052021,0.05422438308596611,0.05208098888397217,0.048500727862119675,0.047812726348638535,0.05187470465898514,0.05491768941283226,0.050811152905225754,0.043654605746269226,0.04326065629720688,0.05174452066421509,0.06194362789392471,0.06640271842479706,0.0635075569152832,0.05842627212405205,0.057888131588697433,0.06207282468676567,0.06557518988847733,0.06581702083349228,0.06478384137153625,0.06427361071109772,0.06426017731428146,0.06367012113332748,0.061079155653715134,0.05645532160997391,0.0517633855342865,0.0490812323987484,0.04833746701478958,0.047277651727199554,0.04368274658918381,0.03843152895569801,0.03658200800418854,0.04216315597295761,0.05241742357611656,0.060192059725522995,0.06063463166356087,0.05455124378204346,0.045965105295181274,0.0375676155090332,0.030386565253138542,0.026209501549601555]}]}', 'id': 'lc_355ccc11-36f5-4d61-a945-03bd0249ece2'}]\n", - "\n", - "Final Answer: The scanned image was successfully acquired via HAADF, resulting in the file `stem_image_HAADF_20260713T132752058144.h5`. Data retrieved from the tiled server shows the dataset `image/HAADF` with a shape of [512, 512] and a float32 data type.\n" + "The available devices are:\n", + "- asyncroscopy/camera/default\n", + "- asyncroscopy/data/default\n", + "- asyncroscopy/eds/default\n", + "- asyncroscopy/flucam/default\n", + "- asyncroscopy/instrument/default\n", + "- asyncroscopy/llm/default\n", + "- asyncroscopy/scan/default\n", + "- asyncroscopy/stage/default\n" ] } ], "source": [ - "prompt = \"Get a scanned image via HAADF and get it from the tiled server.\"\n", - "await run_agent_demo(prompt, tools, is_local_model=using_local_model)" + "prompt = \"List all of the devices.\"\n", + "response = llm.query(prompt)\n", + "print(response)" ] } ], "metadata": { "kernelspec": { - "display_name": "asyncroscopy (3.12.13.final.0)", + "display_name": "asyncroscopy (3.12.x)", "language": "python", "name": "python3" }, diff --git a/startup_scripts/run_llm.py b/startup_scripts/run_llm.py index 0ea862c..c26bf33 100644 --- a/startup_scripts/run_llm.py +++ b/startup_scripts/run_llm.py @@ -48,7 +48,6 @@ def register_device(config: dict | None): print(f"Set device properties: {properties}") def run_server(): - # Pass the instance name to run_server sys.argv = ["llm.py", INSTANCE_NAME] LLM.run_server() @@ -71,10 +70,9 @@ def main(): # Start server in thread threading.Thread(target=run_server, daemon=True).start() - # Wait for device to be ready print("Waiting for LLM device to start and initialize...") proxy = None - max_wait_seconds = 120 # Increased timeout for local model loading + max_wait_seconds = 120 for _ in range(max_wait_seconds): try: @@ -113,5 +111,13 @@ def main(): except Exception as e: print(f"Error: {e}") + else: + print("Press Ctrl+C to terminate.") + try: + while True: + time.sleep(3600) + except KeyboardInterrupt: + print("\nShutting down server...") + if __name__ == "__main__": main()