以下の内容はhttps://end0tknr.hateblo.jp/entry/20250428/1745815982より取得しました。


ollama for win + qwen2.5:14b + langchain for python によるローカルllmでのmcp

ollama for win + qwen2.5:14b + mcphost for go によるローカルllmでのmcp - end0tknr's kipple - web写経開発

先日の上記entryにある mcphost for go では上手く動作しませんでしたので、 今回は langchain-mcp-adapters for python を試します。

ちょうどよいタイミングで以下の参考urlを見つけ、全くの写経です

参考url

step1 langchain-mcp-adapters for python 等のinstall

CONDA> pip install langchain langchain-mcp-adapters langchain-ollama langgraph

step2 edit python mcp client

import asyncio
import os
import sys
import json
from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.prebuilt import create_react_agent
from langchain_core.messages import SystemMessage
from langchain_ollama import ChatOllama
import sys

llm = ChatOllama(model="qwen2.5:14b",
                 temperature=0,
                 base_url="http://localhost:11434" )

conf_path = "c:\\Users\\end0t\\.mcp.json"
with open(conf_path, "r", encoding="utf-8") as f:
    conf = json.load(f)
mcp_conf = conf.get("mcpServers", {})

sys_prompt = """\
あなたはMCPサーバーを使用するAIアシスタントです。
MCPサーバーのToolの結果を優先し回答してください。
回答は日本語でお願いします。
"""
user_prompt = """\
日本の首都はどこですか?
回答は file-system ツールを利用し、
c:/Users/end0t/tmp/capitals.txt のテキストファイルへ記入して下さい
"""

def print_messages_by_type(result):
    messages = result.get("messages", result)
    if not isinstance(messages, list):
        print("messagesがリストではありません")
        return
    for msg in messages:
        # クラス名またはtype属性で判定
        msg_type = msg.get("type") if isinstance(msg, dict) else type(msg).__name__
        if not msg_type and hasattr(msg, "__class__"):
            msg_type = msg.__class__.__name__
        print(f"\n--- {msg_type} ---")
        if isinstance(msg, dict):
            for k, v in msg.items():
                print(f"{k}: {v}")
        else:
            print(msg)
            
async def main():

    async with MultiServerMCPClient( mcp_conf ) as client:
        sys_message = SystemMessage( content=sys_prompt )

        agent = create_react_agent(llm,
                                   client.get_tools(),
                                   prompt=sys_message )
        
        result = await agent.ainvoke({ "messages":user_prompt})
        for message in result["messages"]:
            print( f"{message}" )
        
if __name__ == "__main__":
    asyncio.run(main())

step3 test

「ollama serve」コマンド等で、ollama を起動した上で、以下を実行。

回答は、なぜかファイルではなく、sqlite へ記入されましたが、 前回よりは前進した気がしますので、今回は、これでokとします。

Exception ignored や Traceback が表示されましたが、原因等については、 後日、気が向いたら調べます

content='日本の首都はどこですか?\n回答は file-system ツールを利用し、\nc:/Users/end0t/tmp/capitals.txt のテキストファイルへ記入して下さい\n' additional_kwargs={} response_metadata={} id='69eafda2-5579-4cd4-936b-e489dbcc8e0e'
content='' additional_kwargs={} response_metadata={'model': 'qwen2.5:14b', 'created_at': '2025-04-28T04:46:11.8772824Z', 'done': True, 'done_reason': 'stop', 'total_duration': 3200511600, 'load_duration': 81585400, 'prompt_eval_count': 2048, 'prompt_eval_duration': 698845800, 'eval_count': 161, 'eval_duration': 2367378000, 'model_name': 'qwen2.5:14b'} id='run-a4d5cde6-08ec-405a-a876-ce303673e91e-0' tool_calls=[{'name': 'list_allowed_directories', 'args': {}, 'id': 'b75609eb-d22d-43ad-a6fa-d6b6cd1f9ae9', 'type': 'tool_call'}] usage_metadata={'input_tokens': 2048, 'output_tokens': 161, 'total_tokens': 2209}
content='Allowed directories:\nC:\\Users\\end0t\\tmp' name='list_allowed_directories' id='89fcf9eb-4b95-46fb-a56f-373ddd595575' tool_call_id='b75609eb-d22d-43ad-a6fa-d6b6cd1f9ae9'
content='The server is configured to allow access to the following directory:\n\n- `C:\\Users\\end0t\\tmp`\n\nThis means you can perform file operations within this directory and its subdirectories. If you need to work with files in other directories, please let me know so I can assist further or escalate the request for additional permissions.' additional_kwargs={} response_metadata={'model': 'qwen2.5:14b', 'created_at': '2025-04-28T04:46:13.4472951Z', 'done': True, 'done_reason': 'stop', 'total_duration': 1563930700, 'load_duration': 42028900, 'prompt_eval_count': 2048, 'prompt_eval_duration': 460481100, 'eval_count': 68, 'eval_duration': 1026382500, 'model_name': 'qwen2.5:14b'} id='run-187f20ae-4c9a-44e6-a4b8-11d0173006e8-0' usage_metadata={'input_tokens': 2048, 'output_tokens': 68, 'total_tokens': 2116}
Exception ignored in: <function BaseSubprocessTransport.__del__ at 0x000001D18F7A5440>
Traceback (most recent call last):
  File "C:\Users\end0t\miniconda3\Lib\asyncio\base_subprocess.py", line 126, in __del__
    self.close()
  File "C:\Users\end0t\miniconda3\Lib\asyncio\base_subprocess.py", line 104, in close
    proto.pipe.close()
  File "C:\Users\end0t\miniconda3\Lib\asyncio\proactor_events.py", line 109, in close
    self._loop.call_soon(self._call_connection_lost, None)
  File "C:\Users\end0t\miniconda3\Lib\asyncio\base_events.py", line 794, in call_soon
    self._check_closed()
  File "C:\Users\end0t\miniconda3\Lib\asyncio\base_events.py", line 540, in _check_closed
    raise RuntimeError('Event loop is closed')
RuntimeError: Event loop is closed
Exception ignored in: <function _ProactorBasePipeTransport.__del__ at 0x000001D18F7A6C00>
Traceback (most recent call last):
  File "C:\Users\end0t\miniconda3\Lib\asyncio\proactor_events.py", line 116, in __del__
    _warn(f"unclosed transport {self!r}", ResourceWarning, source=self)
                               ^^^^^^^^
  File "C:\Users\end0t\miniconda3\Lib\asyncio\proactor_events.py", line 80, in __repr__
    info.append(f'fd={self._sock.fileno()}')
                      ^^^^^^^^^^^^^^^^^^^
  File "C:\Users\end0t\miniconda3\Lib\asyncio\windows_utils.py", line 102, in fileno
    raise ValueError("I/O operation on closed pipe")
ValueError: I/O operation on closed pipe
Exception ignored in: <function _ProactorBasePipeTransport.__del__ at 0x000001D18F7A6C00>
Traceback (most recent call last):
  File "C:\Users\end0t\miniconda3\Lib\asyncio\proactor_events.py", line 116, in __del__
    _warn(f"unclosed transport {self!r}", ResourceWarning, source=self)
                               ^^^^^^^^
  File "C:\Users\end0t\miniconda3\Lib\asyncio\proactor_events.py", line 80, in __repr__
    info.append(f'fd={self._sock.fileno()}')
                      ^^^^^^^^^^^^^^^^^^^
  File "C:\Users\end0t\miniconda3\Lib\asyncio\windows_utils.py", line 102, in fileno
    raise ValueError("I/O operation on closed pipe")
ValueError: I/O operation on closed pipe
Exception ignored in: <function BaseSubprocessTransport.__del__ at 0x000001D18F7A5440>
Traceback (most recent call last):
  File "C:\Users\end0t\miniconda3\Lib\asyncio\base_subprocess.py", line 125, in __del__
    _warn(f"unclosed transport {self!r}", ResourceWarning, source=self)
                               ^^^^^^^^
  File "C:\Users\end0t\miniconda3\Lib\asyncio\base_subprocess.py", line 78, in __repr__
    info.append(f'stdout={stdout.pipe}')
                         ^^^^^^^^^^^^^
  File "C:\Users\end0t\miniconda3\Lib\asyncio\proactor_events.py", line 80, in __repr__
    info.append(f'fd={self._sock.fileno()}')
                      ^^^^^^^^^^^^^^^^^^^
  File "C:\Users\end0t\miniconda3\Lib\asyncio\windows_utils.py", line 102, in fileno
    raise ValueError("I/O operation on closed pipe")
ValueError: I/O operation on closed pipe



以上の内容はhttps://end0tknr.hateblo.jp/entry/20250428/1745815982より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14