"enabled_tools": ["tool_name", ...] '
"to your config JSON to limit tools."
)
else:
lines.append(
""enabled_tools": ["tool_name", ...] '
"to your config JSON to limit tools."
)
return "".join(lines)
# ---------------------------------------------------------------------------
# MCPTool class
# ---------------------------------------------------------------------------
class MCPTool(BaseTool):
"""A kotaemon BaseTool wrapper around a single MCP server tool.
This tool holds the MCP server configuration and establishes
a connection to invoke the tool on demand.
Example usage::
tool = MCPTool(
name="search",
description="Search the web",
server_transport="stdio",
server_command="uvx",
server_args=["mcp-server-fetch"],
mcp_tool_name="fetch",
)
result = tool.run("https://example.com")
"""
name: str = ""
description: str = ""
args_schema: Optional[Type[BaseModel]] = None
# MCP server connection details
server_transport: str = "stdio"
server_command: str = ""
server_args: list[str] = []
server_env: dict[str, str] = {}
# The original MCP tool name (on the server)
mcp_tool_name: str = ""
def _run_tool(self, *args: Any, **kwargs: Any) -> str:
"""Invoke the MCP tool by establishing a session."""
return _run_async(self._arun_tool(*args, **kwargs))
async def _arun_tool(self, *args: Any, **kwargs: Any) -> str:
"""Async implementation that connects to the MCP server and calls
the tool."""
from mcp import ClientSession
from mcp.client.sse import sse_client
from mcp.client.stdio import StdioServerParameters, stdio_client
# Build tool arguments
if args and isinstance(args[0], str):
try:
tool_args = json.loads(args[0])
except json.JSONDecodeError:
# If not JSON, assume single string argument
if self.args_schema:
first_field = next(iter(self.args_schema.model_fields.keys()))
tool_args = {first_field: args[0]}
else:
tool_args = {"input": args[0]}
else:
tool_args = kwargs
if self.server_transport == "stdio":
cmd = self.server_command
cmd_args = self.server_args
# Auto-split if full command string with no separate args
if not cmd_args and " " in cmd:
parts = shlex.split(cmd)
cmd = parts[0]
cmd_args = parts[1:]
server_params = StdioServerParameters(
command=cmd,
args=cmd_args,
env=self.server_env if self.server_env else None,
)
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
result = await session.call_tool(self.mcp_tool_name, tool_args)
return self._format_result(result)
elif self.server_transport == "sse":
async with sse_client(url=self.server_command) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
result = await session.call_tool(self.mcp_tool_name, tool_args)
return self._format_result(result)
else:
return f"Unsupported transport: {self.server_transport}"
def _format_result(self, result: Any) -> str:
"""Format MCP CallToolResult into a string."""
if result.isError:
return f"MCP Tool Error: {result.content}"
parts = []
for content in result.content:
if hasattr(content, "text"):
parts.append(content.text)
elif hasattr(content, "data"):
parts.append(f"[Binary data: {content.mimeType}]")
else:
parts.append(str(content))
return "\n".join(parts)
================================================
FILE: libs/kotaemon/kotaemon/agents/tools/wikipedia.py
================================================
from typing import Any, AnyStr, Optional, Type, Union
from pydantic import BaseModel, Field
from kotaemon.base import Document
from .base import BaseTool
class Wiki:
"""Wrapper around wikipedia API."""
def __init__(self) -> None:
"""Check that wikipedia package is installed."""
try:
import wikipedia # noqa: F401
except ImportError:
raise ValueError(
"Could not import wikipedia python package. "
"Please install it with `pip install wikipedia`."
)
def search(self, search: str) -> Union[str, Document]:
"""Try to search for wiki page.
If page exists, return the page summary, and a PageWithLookups object.
If page does not exist, return similar entries.
"""
import wikipedia
try:
page_content = wikipedia.page(search).content
url = wikipedia.page(search).url
result: Union[str, Document] = Document(
text=page_content, metadata={"page": url}
)
except wikipedia.PageError:
result = f"Could not find [{search}]. Similar: {wikipedia.search(search)}"
except wikipedia.DisambiguationError:
result = f"Could not find [{search}]. Similar: {wikipedia.search(search)}"
return result
class WikipediaArgs(BaseModel):
query: str = Field(..., description="a search query as input to wkipedia")
class WikipediaTool(BaseTool):
"""Tool that adds the capability to query the Wikipedia API."""
name: str = "wikipedia"
description: str = (
"Search engine from Wikipedia, retrieving relevant wiki page. "
"Useful when you need to get holistic knowledge about people, "
"places, companies, historical events, or other subjects. "
"Input should be a search query."
)
args_schema: Optional[Type[BaseModel]] = WikipediaArgs
doc_store: Any = None
def _run_tool(self, query: AnyStr) -> AnyStr:
if not self.doc_store:
self.doc_store = Wiki()
tool = self.doc_store
evidence = tool.search(query)
return evidence
================================================
FILE: libs/kotaemon/kotaemon/agents/utils.py
================================================
from kotaemon.base import Document
def get_plugin_response_content(output) -> str:
"""
Wrapper for AgentOutput content return
"""
if isinstance(output, Document):
return output.text
else:
return str(output)
def calculate_cost(model_name: str, prompt_token: int, completion_token: int) -> float:
"""
Calculate the cost of a prompt and completion.
Returns:
float: Cost of the provided model name with provided token information
"""
# TODO: to be implemented
return 0.0
================================================
FILE: libs/kotaemon/kotaemon/base/__init__.py
================================================
from .component import BaseComponent, Node, Param, lazy
from .schema import (
AIMessage,
BaseMessage,
Document,
DocumentWithEmbedding,
ExtractorOutput,
HumanMessage,
LLMInterface,
RetrievedDocument,
StructuredOutputLLMInterface,
SystemMessage,
)
__all__ = [
"BaseComponent",
"Document",
"DocumentWithEmbedding",
"BaseMessage",
"SystemMessage",
"AIMessage",
"HumanMessage",
"RetrievedDocument",
"LLMInterface",
"StructuredOutputLLMInterface",
"ExtractorOutput",
"Param",
"Node",
"lazy",
]
================================================
FILE: libs/kotaemon/kotaemon/base/component.py
================================================
from abc import abstractmethod
from typing import Any, AsyncGenerator, Iterator, Optional
from theflow import Function, Node, Param, lazy
from kotaemon.base.schema import Document
class BaseComponent(Function):
"""A component is a class that can be used to compose a pipeline.
!!! tip "Benefits of component"
- Auto caching, logging
- Allow deployment
!!! tip "For each component, the spirit is"
- Tolerate multiple input types, e.g. str, Document, List[str], List[Document]
- Enforce single output type. Hence, the output type of a component should be
as generic as possible.
"""
inflow = None
def flow(self):
if self.inflow is None:
raise ValueError("No inflow provided.")
if not isinstance(self.inflow, BaseComponent):
raise ValueError(
f"inflow must be a BaseComponent, found {type(self.inflow)}"
)
return self.__call__(self.inflow.flow())
def set_output_queue(self, queue):
self._queue = queue
for name in self._ff_nodes:
node = getattr(self, name)
if isinstance(node, BaseComponent):
node.set_output_queue(queue)
def report_output(self, output: Optional[Document]):
if self._queue is not None:
self._queue.put_nowait(output)
def invoke(self, *args, **kwargs) -> Document | list[Document] | None:
...
async def ainvoke(self, *args, **kwargs) -> Document | list[Document] | None:
...
def stream(self, *args, **kwargs) -> Iterator[Document] | None:
...
def astream(self, *args, **kwargs) -> AsyncGenerator[Document, None] | None:
...
@abstractmethod
def run(
self, *args, **kwargs
) -> Document | list[Document] | Iterator[Document] | None | Any:
"""Run the component."""
...
__all__ = ["BaseComponent", "Param", "Node", "lazy"]
================================================
FILE: libs/kotaemon/kotaemon/base/schema.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Literal, Optional, TypeVar
from langchain.schema.messages import AIMessage as LCAIMessage
from langchain.schema.messages import HumanMessage as LCHumanMessage
from langchain.schema.messages import SystemMessage as LCSystemMessage
from llama_index.core.bridge.pydantic import Field
from llama_index.core.schema import Document as BaseDocument
if TYPE_CHECKING:
from haystack.schema import Document as HaystackDocument
from openai.types.chat.chat_completion_message_param import (
ChatCompletionMessageParam,
)
IO_Type = TypeVar("IO_Type", "Document", str)
SAMPLE_TEXT = "A sample Document from kotaemon"
class Document(BaseDocument):
"""
Base document class, mostly inherited from Document class from llama-index.
This class accept one positional argument `content` of an arbitrary type, which will
store the raw content of the document. If specified, the class will use
`content` to initialize the base llama_index class.
Attributes:
content: raw content of the document, can be anything
source: id of the source of the Document. Optional.
channel: the channel to show the document. Optional.:
- chat: show in chat message
- info: show in information panel
- index: show in index panel
- debug: show in debug panel
"""
content: Any = None
source: Optional[str] = None
channel: Optional[Literal["chat", "info", "index", "debug", "plot"]] = None
def __init__(self, content: Optional[Any] = None, *args, **kwargs):
if content is None:
if kwargs.get("text", None) is not None:
kwargs["content"] = kwargs["text"]
elif kwargs.get("embedding", None) is not None:
kwargs["content"] = kwargs["embedding"]
# default text indicating this document only contains embedding
kwargs["text"] = "', # noqa ) text = text.replace("
This is a test.
This is bold,= italic, and underlined.=
asdakl fskljf sklf jkslaf; djks dlkfa sk sdjkl ksjkl jsjk skdjjks i w ie sjkfksd fjisdf jks fjs kdj fsk dfjskd fjskd fjsd kfjsk f jskdf jskd fjsk dfjskdf jsifj sifj sk fjks fjksd fjskdf kjs jdfksk fdjs fksj fks dfjs dfks fdjsk fjskdfjskdf <= span class=3DSpellE>sjkf skjf sjkdf skfjsfjk s
The end.
細則 本社編(情報システム部) 分類番号 157300
2020.2
1. スパットくん紛失・盗難時の取扱
スパットくんの紛失・盗難の際は、速やかに停止依頼処理を入力するとともに、報告書を起票します。
NO | 項目 | 内容 |
1 | 対象のスパット くん確認 | 紛失・盗難に気づいた時には、対象のスパットくんの端末識別番号を確認します。 ※盗難の場合は警察への届出も必要です。 ※紛失・盗難の場合は盗難・紛失事故報告も必要です。 あいリクエスト(総務室(大阪)) 『盗難・紛失事故兼個人情報等事故報告』 |
2 | 報告書の起票 | あいリクエスト(システム業務室)-『モバイル決済端末(スパットくん)紛失・盗難報告書』を起票します。 |
3 | 停止依頼入力 | モバイル端末管理ウェブより停止依頼処理を入力します。 ※停止依頼入力により該当スパットくんは使用不可となります。 |
4 | 報告書の承認と 担当室への報告 | 所属長はあいリクエストにて申請・送付された報告書を確認・承認します。承認後、報告書をあいリクエスト(総務室(大阪))-『紛失・盗難事故 兼 個人情報等事故報告』に添付し報告します。(関連細則:本支社編11110/210060「紛失・盗難事故等の被害報告」を参照) ※日計処理の実施が「無」の場合本社担当室(システム業務室、 契約審査室、収納サービス室、損保サービス室)へ連絡する。 |
【関連マニュアル】 スパットくん・モバイル管理ウェブ操作マニュアル
<スパットくん再発見時の対応>
NO | 項目 | 内容 |
1 | 端末設置組織の 確認 | スパットくんの設置状況照会を行い、紛失・盗難となったスパットくんであるか確認します。 |
2 | 端末停止解除 入力 | モバイル端末管理ウェブより停止解除処理を入力します。 ※スパットくんの利用再開は、支社にて停止解除入力から2営業日以上経過してから利用下さい。 |
【関連マニュアル】 スパットくん・モバイル管理ウェブ操作マニュアル
2. スパットくん故障時の取扱
スパットくんが故障した場合には、代替機への交換と故障機の返却を行います。
NO | 項目 | 内容 |
1 | 故障内容の確認と報告書の起票 | スパットくんが故障した場合は、あいリクエスト(システム業務室)-『モバイル決済端末(スパットくん)故障報告書』を起票します。 |
2 | 修理依頼入力
| 『モバイル端末管理ウェブ』より修理依頼処理を入力します。修理 依頼処理の入力により、代替機が送付されます。 |
3 | 報告書の承認と担当室への報告 | 所属長はあいリクエストにて申請・送付された報告書を確認・承認し、システム業務室に報告します。 |
4 | 梱包 | 以下をセットで梱包します。 ・故障したスパットくん本体 ・付属品(充電アダプタ、コード、タッチペン、ストラップ) ・モバイル決済端末修理依頼書 ※各所属にて保管のスパットくん送付箱に梱包します
|
5 | 代替機の受取と 故障機の送付
| 入力後数日でスパットくんの代替機が到着します。 受け取ると同時に、配送業者(日本通運)に故障したスパットくん、および「モバイル決済端末修理依頼書」を渡します。 ※「モバイル決済端末修理依頼書」を忘れずに同梱下さい。
|
6 | 代替機の端末受取入力 | モバイル端末管理ウェブより代替機の端末受取処理を入力します。 |
【関連マニュアル】 スパットくん・モバイル管理ウェブ操作マニュアル
- 1 -