#Embedded Python

0 フォロワー · 75 投稿

Embedded Python は InterSystems IRIS カーネルへ Python プログラミング言語を組み込み、開発者が Python を使用してデータを処理し、サーバーサイドアプリケーションのビジネスロジックを開発できるようにするものです。

ドキュメントはこちらです

記事 Hiroshi Sato · 6月 22, 2025 1m read

.pyファイルの中でIRISのEmbedded Pythonを動作させる際にirispythonコマンドで実行する方法はドキュメント上で紹介されていましたので、以前より使用していました。

しかし、普通のpythonコマンドを使用するとうまく実行できなかったのですが、最近その謎(原因)が解けたので紹介します。

これは、Mac特有の問題である可能性が高くWindowsやLinuxでは何の問題もなく実行できるのかもしれません。

エラーは以下のようなエラーです。

  File "/opt/iris/lib/python/iris.py", line 34, in <module>
    from pythonint import *
ImportError: IrisSecureStart failed: IRIS_ATTACH (-21)

 

このエラーの原因は、シェルの実行ユーザーとirisのオーナーが異なることが原因とのことです。

Macで普通にIRISをインストールするとそのオーナーはrootです。

従ってpython3コマンドを実行する時にsudoコマンドでrootになる必要があるということです。

そして以下のような環境変数の設定も必要です。

3
0 78
記事 Toshihiko Minamoto · 10月 7, 2025 9m read

コミュニティの皆さん、こんにちは。
この記事では、私のアプリケーションである iris-AgenticAI をご紹介します。

エージェンティック AI の登場により、人工知能が世界とやりとりする方法に変革的な飛躍をもたらし、静的なレスポンスが動的な目標主導の問題解決にシフトしています。 OpenAI の Agentic SDK を搭載した OpenAI Agents SDK を使用すると、抽象化をほとんど行わずに軽量で使いやすいパッケージでエージェンティック AI アプリを構築できます。 これは Swarm という前回のエージェントの実験を本番対応にアップグレードしたものです。
このアプリケーションは、人間のような適応性で複雑なタスクの推論、コラボレーション、実行を行える次世代の自律 AI システムを紹介しています。

アプリケーションの機能

  • エージェントループ  🔄 ツールの実行を自律的に管理し、結果を LLM に送信して、タスクが完了するまで反復処理するビルトインのループ。
  • Python-First 🐍 ネイティブの Python 構文(デコレーター、ジェネレーターなど)を利用して、外部の DSL を使用せずにエージェントのオーケストレーションとチェーンを行います。
  • ハンドオフ 🤝 専門化されたエージェント間でタスクを委任することで、マルチエージェントワークフローをシームレスに調整します。
  • 関数ツール ⚒️ @tool で Python 関数をデコレートすることで、エージェントのツールキットに即座に統合させます。
  • ベクトル検索(RAG) 🧠 RAG 検索のためのベクトルストアのネイティブ統合。
  • トレース 🔍 リアルタイムでエージェントワークフローの可視化、デバッグ、監視を行うためのビルトインのトレース機能(LangSmith の代替サービスとして考えられます)。
  • MCP サーバー 🌐 stdio と HTTP によるモデルコンテキストプロトコル(MCP)で、クロスプロセスエージェント通信を可能にします。
  • Chainlit UI 🖥️ 最小限のコードで対話型チャットインターフェースを構築するための統合 Chainlit フレームワーク。
  • ステートフルメモリ 🧠 継続性を実現し、長時間実行するタスクに対応するために、セッション間でチャット履歴、コンテキスト、およびエージェントの状態を保持します。

エージェント

エージェントは、アプリの主要な構成要素です。 エージェントは大規模言語モデル(LLM)で、instructions と tools で構成されています。 基本的な構成 以下は、構成されるエージェントの最も一般的なプロパティです。

Instructions: 開発者メッセージまたはシステムプロンプトとも呼ば出る指示。
model: LLM が使用するモデル。オプションとして model_settings を使用して、temperature や top_p など、モデルのチューニングパラメーターを構成できます。
tools: タスクを達成するためにエージェントが使用できるツール。

from agents import Agent, ModelSettings, function_tool

@function_tooldefget_weather(city: str) -> str:returnf"The weather in {city} is sunny"
agent = Agent(
    name="Haiku agent",
    instructions="Always respond in haiku form",
    model="o3-mini",
    tools=[get_weather],
)

エージェントの実行

Runner クラスを使ってエージェントを実行できます。 これには 3 つのオプションがあります。

  1. Runner.run(): 非同期で実行し、RunResult を返します。
  2. Runner.run_sync(): 非同期メソッドで、内部で .run() を実行します。
  3. Runner.run_streamed(): 非同期で実行し、RunResultStreaming を返します。 ストリーミングモードで LLM を呼び出し、イベントを受け取るたびにユーザーにストリーミングします。
from agents import Agent, Runner

asyncdefmain(): agent = Agent(name="Assistant", instructions="You are a helpful assistant")

result = <span class="hljs-keyword">await</span> Runner.run(agent, <span class="hljs-string">"Write a haiku about recursion in programming."</span>)
print(result.final_output)
<span class="hljs-comment"># Code within the code,</span>
<span class="hljs-comment"># Functions calling themselves,</span>
<span class="hljs-comment"># Infinite loop's dance.</span></code></pre>


エージェントアーキテクチャ

アプリケーションは 7 つの専門化されたエージェントで構成されています。

  1. Triage エージェント 🤖
    • 機能: ユーザー入力を受け取り、ハンドオフでタスクを委任する主要ルーター
    • : 「Show production errors(プロダクションエラーを表示)」は IRIS プロダクションエージェントに転送されます。
  2. ベクトル検索エージェント 🤖
    • 機能: IRIS 2025.1 リリースノートの内容を提供します(RAG 機能)
    • : 「Provide me summary of Release Notes(リリースノートの要約を提供)」はベクトル検索エージェントに転送されます。
  3. IRIS Dashboard エージェント 🤖
    • 機能: リアルタイムの管理ポータルメトリクスを提供します: plaintext Copy
      ApplicationErrors, CSPSessions, CacheEfficiency, DatabaseSpace, DiskReads,  
      DiskWrites, ECPAppServer, ECPDataServer, GloRefs, JournalStatus,  
      LicenseCurrent, LockTable, Processes, SystemUpTime, WriteDaemon, [...]
  4. IRIS 実行プロセスエージェント 🤖
    • 機能: アクティブなプロセスを次の詳細とともに監視します。
      • Process ID | Namespace | Routine | State | PidExternal
  5. IRIS Production エージェント 🤖
    • 機能: プロダクションの開始と停止の機能とともにプロダクションの詳細を提供します。
  6. WebSearch エージェント 🤖
    • 機能: API 統合によりコンテキストウェブ検索を実行します。
  7. Order エージェント 🤖
    • 機能: 注文 ID を使用して注文のステータスを取得します。


ハンドオフ

ハンドオフによって、タスクを別のエージェントに委任することができます。 これは特に、それぞれのエージェントが異なる分野に特化している場合に役立ちます。 たとえば、カスタマーサポートアプリには、注文ステータス、返金、FAQ などのそれぞれのタスクを専門的に処理するエージェントが実装されている場合があります。

Triage エージェントはこのアプリケーションのメインエージェントで、ユーザー入力に基づいて別のエージェントにタスクを委任するエージェントです。

#TRIAGE AGENT, Main agent receives user input and delegates to other agent by using handoffs
    triage_agent = Agent(
        name="Triage agent",
        instructions=(
            "Handoff to appropriate agent based on user query.""if they ask about Release Notes, handoff to the vector_search_agent.""If they ask about production, handoff to the production agent.""If they ask about dashboard, handoff to the dashboard agent.""If they ask about process, handoff to the processes agent.""use the WebSearchAgent tool to find information related to the user's query and do not use this agent is query is about Release Notes.""If they ask about order, handoff to the order_agent."
        ),
        handoffs=[vector_search_agent,production_agent,dashboard_agent,processes_agent,order_agent,web_search_agent]
    )


トレース

Agents SDK には、トレース機能が組み込まれており、エージェントの実行中にLLM の生成、ツールの呼び出し、ハンドオフ、ガードレール、カスタムイベントの発生など、イベントの包括的な記録を収集できます。 Traces ダッシュボードを使用すると、開発中と本番稼動時にワークフローのデバッグ、可視化、監視を行えます。
https://platform.openai.com/logs

image

 


アプリケーションのインターフェース

アプリケーションのワークフロープロセス
ベクトル検索エージェント

ベクトル検索エージェントは、「New in InterSystems IRIS 2025.1」のテキスト情報のデータがまだ存在しない場合に、そのデータを一度だけ自動的に IRIS Vector Store に取り込みます。  


以下のクエリを使用してデータを検索しましょう

SELECTid, embedding, document, metadata
FROM SQLUser.AgenticAIRAG

Triage エージェントはユーザー入力を受け取って、質問をベクトル検索エージェントに転送します。

IRIS Dashboard エージェント

Triage エージェントはユーザー入力を受け取って、質問を IRIS Dashboard エージェントに転送します。

IRIS Processes エージェント

Triage エージェントはユーザー入力を受け取って、質問を IRIS Processes エージェントに転送します。

IRIS Production エージェント

Production エージェントを使用して、プロダクションの開始と停止を行います。

Production エージェントを使用して、プロダクションの詳細を取得します。

Local エージェント

Triage エLocal ージェントはユーザー入力を受け取って、質問を Local Order エージェントに転送します。

WebSearch エージェント

ここでは、Triage エージェントは 2 つの質問を受け取って、WebSearch エージェントに転送します。

MCP Server アプリケーション

MCP Server は https://localhost:8000/sse で実行しています。

image
以下のコードで MCP Server を起動しています。

import os
import shutil
import subprocess
import time
from typing import Any
from dotenv import load_dotenv

load_dotenv()

#Get OPENAI Key, if not fond in .env then get the GEIMINI API KEY#IF Both defined then take OPENAI Key openai_api_key = os.getenv("OPENAI_API_KEY") ifnot openai_api_key: raise ValueError("OPENAI_API_KEY is not set. Please ensure to defined in .env file.")

ifname == "main": # Let's make sure the user has uv installedifnot shutil.which("uv"): raise RuntimeError( "uv is not installed. Please install it: https://docs.astral.sh/uv/getting-started/installation/" )

<span class="hljs-comment"># We'll run the SSE server in a subprocess. Usually this would be a remote server, but for this</span>
<span class="hljs-comment"># demo, we'll run it locally at http://localhost:8000/sse</span>
process: subprocess.Popen[Any] | <span class="hljs-keyword">None</span> = <span class="hljs-keyword">None</span>
<span class="hljs-keyword">try</span>:
    this_dir = os.path.dirname(os.path.abspath(__file__))
    server_file = os.path.join(this_dir, <span class="hljs-string">"MCPserver.py"</span>)

    print(<span class="hljs-string">"Starting SSE server at http://localhost:8000/sse ..."</span>)

    <span class="hljs-comment"># Run `uv run server.py` to start the SSE server</span>
    process = subprocess.Popen([<span class="hljs-string">"uv"</span>, <span class="hljs-string">"run"</span>, server_file])
    <span class="hljs-comment"># Give it 3 seconds to start</span>
    time.sleep(<span class="hljs-number">3</span>)

    print(<span class="hljs-string">"SSE server started. Running example...\n\n"</span>)
<span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
    print(<span class="hljs-string">f"Error starting SSE server: <span class="hljs-subst">{e}</span>"</span>)
    exit(<span class="hljs-number">1</span>)

 

MCP Server には次のツールが備わっています。

  • IRIS 2025.1 リリースノートの詳細を提供(ベクトル検索)
  • IRIS 情報ツール
  • 天気チェックツール
  • シークレットワードの検索ツール(ローカル関数)
  • 加算ツール(ローカル関数)

MCP アプリケーションは  http://localhost:8001で実行しています。

 

MCP Server ベクトル検索(RAG)機能

MCP Server には InterSystems IRIS ベクトル検索インジェスト機能と検索拡張生成(RAG)機能が備わっています。


MCP Server の他の機能

MCP Server は、ユーザー入力に基づいて、適切なツールに動的にタスクを委任します。


詳細については、iris-AgenticAI の Open Exchange アプリケーションページをご覧ください。

以上です

0
0 26
記事 Toshihiko Minamoto · 10月 1, 2025 6m read

コミュニティの皆さん、こんにちは。
従来のキーワードベースの検索では、ニュアンスのあるドメイン固有のクエリには対応できません。 ベクトル検索であれば、セマンティック認識を利用して、キーワードだけでなくコンテキストにも基づいたレスポンスを AI エージェントで検索して生成することができます。
この記事では、エージェンティック AI RAG(検索拡張生成)アプリケーションを作成手順を紹介します。

実装手順:

  1. エージェントツールを作成する
    • インジェスト機能の追加: ドキュメント(例: InterSystems IRIS 2025.1 リリースノート)を自動的にインジェストしてインデックス作成を行います。
    • ベクトル検索機能の実装
  2. ベクトル検索エージェントを作成する
  3. Triage(メインエージェント)に渡す
  4. エージェントを実行する

1. エージェントツールを作成する
1.1 - 
ドキュメントの取り込みを実装します。ドキュメントの取り込みとインデックス作成を自動化します。

インジェストツールは以下のコードで実装します。

defingestDoc(self):#Check if document is defined, by selecting from table#If not defined then INGEST document, Otherwise back
        embeddings = OpenAIEmbeddings()	
        #Load the document based on the fle type
        loader = TextLoader("/irisdev/app/docs/IRIS2025-1-Release-Notes.txt", encoding='utf-8')      
    documents = loader.load()        
    text_splitter = RecursiveCharacterTextSplitter(chunk_size=<span class="hljs-number">400</span>, chunk_overlap=<span class="hljs-number">0</span>)
    
    texts = text_splitter.split_documents(documents)
                   
    <span class="hljs-comment">#COLLECTION_NAME = "rag_document"</span>
    db = IRISVector.from_documents(
        embedding=embeddings,
        documents=texts,
        collection_name = self.COLLECTION_NAME,
        connection_string=self.CONNECTION_STRING,
    )

    db = IRISVector.from_documents(embedding=embeddings,documents=texts, collection_name = self.COLLECTION_NAME, connection_string=self.CONNECTION_STRING,)</code></pre>

ベクトル検索エージェントは、指定されたリポジトリフォルダから「New in InterSystems IRIS 2025.1」を IRIS Vector Store に自動的に取り込んでインデックスを作成します。この操作はそのデータがまだ存在しない場合にのみ実行されます。


以下のクエリを実行して、ベクトルストアから必要なデータを取得します。

SELECTid, embedding, document, metadata
FROM SQLUser.AgenticAIRAG


1.2 - ベクトル検索機能の実装
以下のコードはエージェントの検索機能を実装します。

defragSearch(self,prompt):#Check if collections are defined or ingested done.# if not then call ingest method
        embeddings = OpenAIEmbeddings()	
        db2 = IRISVector (
            embedding_function=embeddings,    
            collection_name=self.COLLECTION_NAME,
            connection_string=self.CONNECTION_STRING,
        )
        docs_with_score = db2.similarity_search_with_score(prompt)
        relevant_docs = ["".join(str(doc.page_content)) + " "for doc, _ in docs_with_score]
    <span class="hljs-comment">#Generate Template</span>
    template = <span class="hljs-string">f"""
    Prompt: <span class="hljs-subst">{prompt}</span>
    Relevant Docuemnts: <span class="hljs-subst">{relevant_docs}</span>
    """</span>
    <span class="hljs-keyword">return</span> template</code></pre>


Triage エージェントは、受信したユーザークエリを処理し、それを Vector Search Agent に委任します。このエージェントは、セマンティック検索を実行して、最も関連性の高い情報を取得します。


2 - ベクトルストアエージェントを作成する

以下のコードは、以下の要素を含む vector_search_agent を実装します。

  • エージェントを調整するためのカスタム handoff_descriptions
  • 明確な演算 instructions
  • iris_RAG_search ツール(ドキュメントの取り込みとベクトル検索操作に irisRAG.py を使用)
@function_tool    @cl.step(name = "Vector Search Agent (RAG)", type="tool", show_input = False)asyncdefiris_RAG_search():"""Provide IRIS Release Notes details,IRIS 2025.1 Release Notes, IRIS Latest Release Notes, Release Notes"""ifnot ragOprRef.check_VS_Table():
                 #Ingest the document first
                 msg = cl.user_session.get("ragclmsg")
                 msg.content = "Ingesting Vector Data..."await msg.update()
                 ragOprRef.ingestDoc()
        <span class="hljs-keyword">if</span> ragOprRef.check_VS_Table():
             msg = cl.user_session.get(<span class="hljs-string">"ragclmsg"</span>)
             msg.content = <span class="hljs-string">"Searching Vector Data..."</span>
             <span class="hljs-keyword">await</span> msg.update()                 
             <span class="hljs-keyword">return</span> ragOprRef.ragSearch(cl.user_session.get(<span class="hljs-string">"ragmsg"</span>))   
        <span class="hljs-keyword">else</span>:
             <span class="hljs-keyword">return</span> <span class="hljs-string">"Error while getting RAG data"</span>
vector_search_agent = Agent(
        name=<span class="hljs-string">"RAGAgent"</span>,
        handoff_description=<span class="hljs-string">"Specialist agent for Release Notes"</span>,
        instructions=<span class="hljs-string">"You provide assistance with Release Notes. Explain important events and context clearly."</span>,
        tools=[iris_RAG_search]
)</code></pre>


3 - Triage(メインエージェント)に渡す
以下のコードは、処理済みのクエリを Triage エージェント(メインコーディネーター)に渡すためのハンドオフプロトコルを実装します。

 triage_agent = Agent(
        name="Triage agent",
        instructions=(
            "Handoff to appropriate agent based on user query.""if they ask about Release Notes, handoff to the vector_search_agent.""If they ask about production, handoff to the production agent.""If they ask about dashboard, handoff to the dashboard agent.""If they ask about process, handoff to the processes agent.""use the WebSearchAgent tool to find information related to the user's query and do not use this agent is query is about Release Notes.""If they ask about order, handoff to the order_agent."
        ),
        handoffs=[vector_search_agent,production_agent,dashboard_agent,processes_agent,order_agent,web_search_agent]
    )


4 - エージェントを実行する

以下のコードは次の内容を実行します。

  1. ユーザー入力を受け取る
  2. triage_agent  を呼び出す
  3. クエリを Vector_Search_Agent に送信して処理する
@cl.on_message
async def main(message: cl.Message):
    """Process incoming messages and generate responses."""
    # Send a thinking message
    msg = cl.Message(content="Thinking...")
    await msg.send()
agent: Agent = cast(Agent, cl.user_session.get(<span class="hljs-string">"agent"</span>))
config: RunConfig = cast(RunConfig, cl.user_session.get(<span class="hljs-string">"config"</span>))

# Retrieve the chat history from the session.
history = cl.user_session.get(<span class="hljs-string">"chat_history"</span>) or []

# Append the user'<span class="hljs-keyword">s</span> message to the history.
history.append({<span class="hljs-string">"role"</span>: <span class="hljs-string">"user"</span>, <span class="hljs-string">"content"</span>: message.content})

# Used by RAG agent
cl.user_session.<span class="hljs-keyword">set</span>(<span class="hljs-string">"ragmsg"</span>, message.content)
cl.user_session.<span class="hljs-keyword">set</span>(<span class="hljs-string">"ragclmsg"</span>, msg)

<span class="hljs-keyword">try</span>:
    <span class="hljs-keyword">print</span>(<span class="hljs-string">"\n[CALLING_AGENT_WITH_CONTEXT]\n"</span>, history, <span class="hljs-string">"\n"</span>)
    result = Runner.run_sync(agent, history, run_config=config)
           
    response_content = result.final_output
    
    # Update the thinking message with the actual response
    msg.content = response_content
    await msg.update()

    # Append the assistant'<span class="hljs-keyword">s</span> response to the history.
    history.append({<span class="hljs-string">"role"</span>: <span class="hljs-string">"developer"</span>, <span class="hljs-string">"content"</span>: response_content})
    # NOTE: Here we are appending the response to the history <span class="hljs-keyword">as</span> a developer message.
    # This is a BUG in the agents library.
    # The expected behavior is to append the response to the history <span class="hljs-keyword">as</span> an assistant message.

    # Update the session with the <span class="hljs-keyword">new</span> history.
    cl.user_session.<span class="hljs-keyword">set</span>(<span class="hljs-string">"chat_history"</span>, history)
    
    # Optional: Log the interaction
    <span class="hljs-keyword">print</span>(f<span class="hljs-string">"User: {message.content}"</span>)
    <span class="hljs-keyword">print</span>(f<span class="hljs-string">"Assistant: {response_content}"</span>)
    
except Exception <span class="hljs-keyword">as</span> e:
    msg.content = f<span class="hljs-string">"Error: {str(e)}"</span>
    await msg.update()
    <span class="hljs-keyword">print</span>(f<span class="hljs-string">"Error: {str(e)}"</span>)</code></pre>


実際の動作をご覧ください

詳細については、iris-AgenticAI の Open Excahnge アプリケーションページをご覧ください。
以上です

0
0 17
記事 Toshihiko Minamoto · 9月 30, 2025 6m read

この連載記事を終えていなかったことに気付きました!

GIF de Shame On You Meme | Tenor

今日の記事では、フロントエンドから最適なオプションを選択できるように、テキストに最も類似する ICD-10 診断を抽出するプロダクションプロセスについて説明します。

診断の類似度検索:

アプリケーション内で、HL7 で受け取った診断リクエストを示す画面から、医療従事者が入力したテキストに最も近い ICD-10 診断を検索できます。

検索プロセスを高速化するために、HL7 メッセージを取得する際に受信した診断をベクトル化したテキストをデータベースに保存しました。 これを行うために、メッセージから診断コードを抽出し、ベクトルを生成するメソッドにそれを送信する単純な BPL を実装しました。

受信した診断をベクトル化するコードは以下のようになります。

ClassMethod GetEncoding(sentence As %String) As %String [ Language = python ]
{
        import sentence_transformers
        # create the model and form the embeddings
        model = sentence_transformers.SentenceTransformer('/iris-shared/model/')
        embeddings = model.encode(sentence, normalize_embeddings=True).tolist() # Convert search phrase into a vector# convert the embeddings to a stringreturn str(embeddings)
}

こうすることで、診断をベクトル化し、検索するたびにもう一度ベクトル化する必要がなくなります。 このとおり、ダウンロードしたモデルでベクトルを生成するために  sentence_transformer  ライブラリを使用しています。

受信した診断がすべてベクトル化されてデータベースに保存されているため、SELECT クエリを実行するだけで、受信した診断に最も近い ICD-10 診断を抽出できます。

では、ウェブサービスに公開されている、最も類似する 25 件の診断を返すメソッドのコードを見てみましょう。

ClassMethod GetCodeOptions(idRequest As%String) As%Status
{
	set ret = $$$OKtry {
        set sql = 
            "SELECT TOP 25 * FROM (SELECT C.CodeId, C.Description, VECTOR_DOT_PRODUCT(C.VectorDescription, R.VectorDescription) AS Similarity FROM ENCODER_Object.Codes C, ENCODER_Object.CodeRequests R WHERE R.ID = ?) WHERE Similarity > 0.5 ORDER BY Similarity DESC"set statement = ##class(%SQL.Statement).%New()
		$$$ThrowOnError(statement.%Prepare(sql))
        set rs = statement.%Execute(idRequest)

        set array = []
        while rs.%Next() {
            do array.%Push({
                    "CodeId": (rs.%Get("CodeId")),
                    "Description": (rs.%Get("Description")),
                    "Similarity": (rs.%Get("Similarity"))
                })
        }
        set%response.Status = ..#HTTP200OK
        write array.%ToJSON()

    } catch ex {
        set%response.Status = ..#HTTP400BADREQUEST
        return ex.DisplayString()
    }
    quit ret
}

クエリを見てみると、検索を 25 件に制限し、類似度が 0.5 を超える診断に限定しているのがわかります。 類似度計算には、VECTOR_COSINE を使用することも可能ですが、VECTOR_DOT_PRODUCT  メソッドを選択しました。 この場合、今のところこれらに大きな違いは見つかっていません。

検索結果を以下に示します。

LLM を使って診断を特定する

ここまでは、完璧に特定される診断の単純な検索しか行っていませんが、 自由記述形式のテキストから直接診断を特定することは可能でしょうか?

では試してみましょう!

この機能では、選択した LLM モデルに質問を送信する API を提供する Ollama を使用します。 docker-compose.yml ファイルを見ると、Ollama コンテナーの宣言があります。

## llm locally installed  ollama:    build:      context:.      dockerfile:ollama/Dockerfile    container_name:ollama    volumes:    -./ollama/shared:/ollama-shared    ports:      -"11434:11434"

まず、コンテナーデプロイに LLM llama3.2 がダウンロードされることが定義されています。 その理由は? テストでは最も良いパフォーマンスを見せると思ったからです。

これが、コンテナーがデプロイされたときに実行する entrypoint.sh  ファイルのコンテンツとなります。

#!/bin/bash
echo "Starting Ollama server..."
ollama serve &
SERVE_PID=$!

echo "Waiting for Ollama server to be active..."
while ! ollama list | grep -q 'NAME'; do
  sleep 1
done

ollama pull llama3.2

wait $SERVE_PID

Ollama の機能を活用するために、自由形式のテキストを分析する画面を、LLM を使って入力されたテキストから診断を抽出するように変更しました。

また、llama3.2 が診断を直接抽出するために必要なプロンプトを作成するメソッドを追加して、ビジネスプロセスを変更しました。

Method AnalyzeText(text As %String, analysisId As %String, language As %String) As %String [ Language = python ]
{
    import sentence_transformers
    import iris
    import requests
<span class="hljs-keyword">try</span>:
    url = <span class="hljs-string">"http://ollama:11434/api/generate"</span>
    data = {
        <span class="hljs-string">"model"</span>: <span class="hljs-string">"llama3.2"</span>,
        <span class="hljs-string">"prompt"</span>: <span class="hljs-string">"Extrae únicamente los diagnósticos del siguiente texto separándolos por , y sin añadir interpretaciones: "</span>+text,
        <span class="hljs-string">"stream"</span>: <span class="hljs-keyword">False</span>
    }
    response = requests.post(url, json=data)
    analyzedText = response.json()
    
    model = sentence_transformers.SentenceTransformer(<span class="hljs-string">'/iris-shared/model/'</span>)
    phrases = analyzedText[<span class="hljs-string">'response'</span>].split(<span class="hljs-string">","</span>)
    sqlsentence = <span class="hljs-string">""</span>
    <span class="hljs-comment"># iris.cls("Ens.Util.Log").LogInfo("ENCODER.BP.AnalyzeTextProcess", "AnalyzeText", "Starting process")</span>
    <span class="hljs-keyword">for</span> phraseToAnalyze <span class="hljs-keyword">in</span> phrases :
        <span class="hljs-keyword">if</span> phraseToAnalyze != <span class="hljs-string">""</span>:
            embedding = model.encode(phraseToAnalyze, normalize_embeddings=<span class="hljs-keyword">True</span>).tolist()
            sqlsentence = <span class="hljs-string">"INSERT INTO ENCODER_Object.TextMatches (CodeId, Description, Similarity, AnalysisId, RawText) SELECT TOP 50 * FROM (SELECT CodeId, Description, VECTOR_DOT_PRODUCT(VectorDescription, TO_VECTOR('"</span>+str(embedding)+<span class="hljs-string">"', DECIMAL)) AS Similarity, '"</span>+analysisId+<span class="hljs-string">"', '"</span>+phraseToAnalyze+<span class="hljs-string">"' FROM ENCODER_Object.Codes) ORDER BY Similarity DESC"</span>
            stmt = iris.sql.prepare(<span class="hljs-string">"INSERT INTO ENCODER_Object.TextMatches (CodeId, Description, Similarity, AnalysisId, RawText) SELECT TOP 50 * FROM (SELECT CodeId, Description, VECTOR_DOT_PRODUCT(VectorDescription, TO_VECTOR(?, DECIMAL)) AS Similarity, ?, ? FROM ENCODER_Object.Codes) WHERE Similarity &gt; 0.65 ORDER BY Similarity DESC"</span>)                    
            rs = stmt.execute(str(embedding), analysisId, phraseToAnalyze)        
<span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> err:
    iris.cls(<span class="hljs-string">"Ens.Util.Log"</span>).LogInfo(<span class="hljs-string">"ENCODER.BP.AnalyzeTextProcess"</span>, <span class="hljs-string">"AnalyzeText"</span>, repr(err))
    <span class="hljs-keyword">return</span> repr(err)

<span class="hljs-keyword">return</span> <span class="hljs-string">"Success"</span>

}

このプロンプトは非常に単純なものであるため、必要に応じて改善し、調整することも可能ですが、その選択はユーザーにお任せします。 このメソッドはカンマ区切りで LLM レスポンスを取得し、見つかった診断をベクトル化してデータベースに保存します。 以下は結果の例です。

右下の方に、左下のテキストに関して LLM が見つけたものがすべて表示されています。

これで、診断をコーディングするのに役立つ、LLM モデルを使ったアプリケーションが完成しました。 ここで見たように、実装はまったく複雑ではなく、より複雑で包括的なソリューションを構築するための十分な基礎となります。

お読みいただきありがとうございました!

0
0 20
記事 Mihoko Iijima · 8月 7, 2025 4m read

開発者の皆さん、こんにちは!

Windows 版 IRIS/IRIS for Health 2025.1 以降で Embedded Python をご利用いただく場合、Windows に Python のインストールが必要になりました。

以前のバージョンから Embedded Python をご利用いただいている場合は、新しいバージョンにアップグレードした後、Python のインストールと IRIS 側に必要な設定がありますのでご注意ください。

※ 2024.1 以前のバージョンでは、IRIS インストールと同時にインストールされる Python ご利用いただく必要があったため、Windows への Python インストールは不要でした。

補足:フレキシブル Python ランタイム機能の導入により、OS にインストールされた Python のバージョンを IRIS 側で指定できるようになりました。

詳細はドキュメントもご参照ください:フレキシブル Python ランタイム機能の概要

Embedded Python 利用までの手順は以下の通りです。

1) サポートする Python のバージョンを確認

サポートしている Python のバージョンを確認します。(Windows は、Python 3.9以降)

0
3 72
記事 Mihoko Iijima · 7月 3, 2025 4m read

これは InterSystems FAQ サイトの記事です。

InterSystems 製品では、ファイルオープン時に文字コードを指定すれば指定の文字コードで正しくファイルの中身を処理できます。

文字コードを指定しない場合、InterSystems 製品をインストールした OS に合わせて設定されたファイル I/O 用文字コードを利用してファイルをオープンします(Linux 系は UTF8、Windows は SJIS)。

また、文字列については文字コードが判明していれば $ZCONVERT() 関数を使用して指定文字コードで文字列を処理することができます。

 例)$ZCONVERT(文字列,"I","IRIS内文字コード")

文字コードが不明な場合、残念ながら InterSystems 製品だけでそのコードを判別することができないため、例えば Embedded Python で Python の chardet パッケージを使用して文字コード判別し、IRIS 内文字コードを取得しファイルオープン、文字列の文字コード変換をすることができます。

chardetパッケージについては、外部サイトですが参考となります。ぜひご参照ください。

参考ページ:[解決!Python]テキストファイルのエンコーディングを調べて、その内容を読み込むには(chardetパッケージ)

0
1 92
InterSystems公式 Masahito Miura · 3月 27, 2025

インターシステムズは、InterSystems IRIS®data platformInterSystems IRIS® for HealthTM、および HealthShare® Health Connect の 2025.1 リリースを一般提供 (GA) したことを発表しました。2025.1 は、拡張メンテナンス(EM)リリースです。
リリースハイライト
今回のリリースには、以下のような数々の興味深いアップデートが含まれます:

0
0 120
記事 Megumi Kakechi · 5月 31, 2022 4m read

InterSystems IRIS 2021.2 のバージョンより、Embedded Python を使用できるようになりました。

Embedded Python で Excel のデータを IRIS グローバルに格納する方法 では pandas.DataFrame のデータを InterSystems IRIS グローバルに保存する方法をご紹介しました。
こちらの記事では、その逆の「InterSystems IRIS グローバル($LB) を pandas.DataFrame に変換する」方法をご紹介します。

以下のようなグローバルを、Embedded Python を使用して DataFrame に変換します。

USER>zwrite^ISJ^ISJ=4^ISJ(1)=$lb("Name","Age","Address")
^ISJ(2)=$lb("佐藤","50","東京")
^ISJ(3)=$lb("加藤","40","大阪")
^ISJ(4)=$lb("伊藤","30","京都")


%Library.GlobalクラスのGetクエリ を使用して取得し、iris.sql.execを使用して DataFrame に格納する方法があります。
ただし、こちらの方法はリスト形式($LB)のまま DataFrame に変換します。

1
0 449
記事 Mihoko Iijima · 1月 21, 2025 16m read

開発者の皆さん、こんにちは。

Teams ワークフロー Webhook を用意すると、curl コマンドや REST クライアントを利用して Teams チャネルに任意メッセージを簡単に送信できるので、IRIS や IRIS の Interoperability を使って自動的に何か情報を入手+必要なときだけ Teams チャネル通知ができたら面白いな、と思い試してみた内容をご紹介します。

以下、Teamsワークフローの作成例です。

Teams ワークフローの仕様に合わせたメッセージ用 JSON が用意できれば、こんなメッセージを出すことができます。

Teams チャネルにメッセージを通知するには「Teams ワークフローの Webhook」の用意が必要です。(この用意によってアクセスするために必要なURLが生成されます)詳しくは、「Microsoft Teamsのワークフローを使用して受信 Webhook を作成する」をご参照ください。

通知までの設定などについては、こちらのページを参考にさせていただきました:Teams チャネルへメッセージ送信する方法

以下、試した順でご紹介します。

1. curl コマンドでまずは実行してみる

2. シンプルにターミナルから試す

3. 通知メッセージのJSONを変えてみる

4. メッセージのJSONをJSONテンプレートエンジンで作ってみる

0
0 95
記事 Hiroshi Sato · 1月 17, 2025 2m read

Mac版IRISでは、現状Pythonのバージョンが固定(3.11)なのですが、これに付随する他製品との相性問題に遭遇しましたので報告します。

今までEmbedded Pythonは調子よく動作していたのですが、ある時から急に動作しなくなりました。

原因を調べてみると、Python3.13がインストールされ、それがデフォルトとして上書きされたため、irispythonコマンドを発行すると、それが内部で3.13を呼ぶ様になってしまったためでした。

ちなみにiris session でログインし、そこからEmbedded Pythonを実行する場合は、問題ありません。

あくまでもirispythonコマンドで直接.pyファイルを実行する場合に発生する問題です。

そしてとりあえずの対処法は、python3をpython3.11で置き換える方法です。

以下のような感じです。(どのMacでもbrewコマンドでインストールした場合、ディレクトリ構造は同じだと思いますが、違う可能性もゼロではありません)

cp /opt/homebrew/bin/python3.11 /opt/homebrew/bin/python3

さて、ところで何で3.13がインストールされていたのかというと、

IRIS SQLのLOADコマンドを動作させるためには、JDKまたはJREが必要なのでそれをインストールしました。

0
0 44
記事 Toshihiko Minamoto · 12月 4, 2024 5m read

前回の記事では、ICD-10 による診断のコーディングをサポートできるように開発された d[IA]gnosis アプリケーションを紹介しました。 この記事では、InterSystems IRIS for Health が、事前トレーニングされた言語モデル、そのストレージ、およびその後の生成されたすべてのベクトルの類似性の検索を通じて ICD-10 コードのリストからベクトルを生成するために必要なツールをどのように提供するかを見ていきます。

はじめに

AI モデルの開発に伴って登場した主な機能の 1 つは、RAG(検索拡張生成)という、コンテキストをモデルに組み込むことで LLM モデルの結果を向上させることができる機能です。 この例では、コンテキストは ICD-10 診断のセットによって提供されており、これらを使用するには、まずこれらをベクトル化する必要があります。

診断リストをベクトル化するにはどうすればよいでしょうか?

SentenceTransformers と Embedded Python

ベクトルを生成するために、トレーニング済みのモデルからの自由テキストのベクトル化を大幅に容易にする SentenceTransformers という Python ライブラリを使用しました。 そのウェブサイトでは以下のように説明されています。

Sentence Transformers(別名: SBERT)は、最先端のテキスト画像埋め込みモデルへのアクセス、使用、およびトレーニングに使用される一般的な Python モジュールです。 SentenceTransformer モデルを使って埋め込みを計算(クイックスタート)するか、Cross-Encoder モデルを使って類似性スコアを計算(クイックスタート)するために使用できます。 これにより、セマンティック検索セマンティックテキスト類似性パラフレーズマイニングなどの広範なアプリケーションが可能になります。

SentenceTransformers コミュニティが開発した全モデルの中で、786 の次元ベクトルを生成する BioLORD-2023-M というトレーニング済みモデルを見つけました。

このモデルは、臨床文章や生物医学的概念の意味のある表現を生成するための新しい事前トレーニング戦略である BioLORD を使用してトレーニングされました。

最先端の方法は、同じ概念を指す名前の表現の類似性を最大化し、対照学習を通じて崩壊を防ぐことによって機能します。 ただし、生物医学的名前は必ずしも自明ではないため、非意味的な表現になることがあります。

BioLORD は、定義を使用した概念表現と、生物医学オントロジーで構成されるマルチリレーショナルナレッジグラフから得られる短い説明を基礎にすることで、この問題を克服しています。 この基礎により、このモデルは、オントロジーの階層構造により密接に一致する、より意味論的な概念表現を生成します。 BioLORD-2023 は、臨床文章(MedSTS)と生物医学概念(EHR-Rel-B)の両方でテキスト類似性の新たな最先端を確立しています。

この定義でわかるように、このモデルは、ICD-10 コードと自由テキストの両方をベクトル化するときに役立つ医療概念で事前トレーニングされています。

このプロジェクトでは、このモデルをダウンロードして、ベクトルの作成を高速化します。

if not os.path.isdir('/shared/model/'):
    model = sentence_transformers.SentenceTransformer('FremyCompany/BioLORD-2023-M')            
    model.save('/shared/model/')

ダウンロードしたら、ベクトル化するテキストをリストに入力して、プロセスを高速化します。以前に ENCODER.Object.Codes クラスに記録した ICD-10 コードをベクトル化する方法を見てみましょう。

st = iris.sql.prepare("SELECT TOP 50 CodeId, Description FROM ENCODER_Object.Codes WHERE VectorDescription is null ORDER BY ID ASC ")
resultSet = st.execute()
df = resultSet.dataframe()

if (df.size > 0): model = sentence_transformers.SentenceTransformer("/shared/model/") embeddings = model.encode(df['description'].tolist(), normalize_embeddings=True)

df[<span class="hljs-string">'vectordescription'</span>] = embeddings.tolist()

stmt = iris.sql.prepare(<span class="hljs-string">"UPDATE ENCODER_Object.Codes SET VectorDescription = TO_VECTOR(?,DECIMAL) WHERE CodeId = ?"</span>)
<span class="hljs-keyword">for</span> index, row <span class="hljs-keyword">in</span> df.iterrows():
    rs = stmt.execute(str(row[<span class="hljs-string">'vectordescription'</span>]), row[<span class="hljs-string">'codeid'</span>])

else: flagLoop = False

ご覧のとおり、CSV ファイルから抽出した後に前のステップで登録した、まだベクトル化されていないコードを先に抽出し、次に、ベクトル化する記述のリストを抽出します。Python の sentence_transformers ライブラリを使用してモデルを復元し、関連する埋め込みを生成します。

最後に、UPDATE を実行して、ベクトル化された記述で ICD-10 コードを更新します。 ご覧のように、モデルが返した結果をベクトル化するコマンドは、IRIS における SQL コマンドの TO_VECTOR です。

IRIS で使用する

Python コードの準備ができたので、Ens.BusinessProcess を拡張するクラスにラップして、プロダクションに含めましょう。次に、CSV ファイルを取得するビジネスサービスに接続すれば、完成です!

プロダクションでこのコードがどのように見えるかを確認しましょう。

ご覧のように、EnsLib.File.InboundAdapter アダプターを備えたビジネス サービスにより、コードファイルを収集し、それをすべてのベクトル化とストレージ操作を実行するビジネスプロセスにリダイレクトできます。すると、以下のようなレコードセットが得られます。

これで、アプリケーションは、送信するテキストに一致する可能性のある項目を検索できるようになりました!

次回の記事では...

次回の記事では、Angular 17 で開発されたアプリケーションのフロントエンドが IRIS for Health のプロダクションとどのように統合されるか、および IRIS が分析するテキストをどのように受け取り、それらをベクトル化し、ICD-10 コードテーブルで類似性を検索するかを説明します。

お見逃しなく!

0
0 83
記事 Hiroshi Sato · 12月 4, 2024 1m read

Embedded Pythonの導入に伴い、1つ嬉しいことは、直接エクセルファイルの読み書きができるようになったことです。

それでこの機能を使って、ミニ業務改革をちょこちょこ行なっているのですが、そこでちょっとハマったことを共有します。

実際この内容は実はIRISとは全く関係ないのですが、このDCコミュニティの皆さんが同じことでハマった場合に少しでも早く問題解決できるように情報共有します。

エクセルの計算式の入ったセルの値を読み取るときには、そのファイルのオープンの際にdata_only=Trueというフラグをつけないといけないのですが、そのファイルがopenpyxlで読み書きしたファイルの場合に、その計算式のセルの値(Noneとなる)がうまく読めないことがあります。

その場合には、そのファイルをエクセルで開いて再保存することで、正しく読むことができます。
以下にそのあたりの内容を説明したページがあります。

元ネタ

0
0 384
質問 Akio Hashimoto · 12月 4, 2024

Pythonでは、拡張モジュールのバージョン違いが混同されないように、venv を使って仮想環境に拡張モジュールをインストールする事を推奨されてたりしますが、IRISから使用する場合に、このvenvで作られた仮想環境を利用する事は可能でしょうか?

PythonPathに仮想環境のパスを指定してはみましたが、プロジェクトから、あるいはimportで、その場所を指定する等出来るでしょうか?

何方か御存知の方がおられましたら、その方法などを教えて下さい。

4
0 251
記事 Toshihiko Minamoto · 11月 26, 2024 5m read

ベクトルデータ型と Vector Search 機能が IRIS に導入されたことにより、アプリケーションの開発に多数の可能性が開かれました。こういったアプリケーションの例として、バレンシア保健省が AI モデルを使用した ICD-10 コーディング支援ツールを要求した公募で出品されたアプリケーションが最近私の目に留まりました。

要求されたツールのようなアプリケーションをどのように実装できるでしょうか? 必要なものを確認しましょう。

  1. ICD-10 コードのリスト。自由テキスト内で診断を検索するための RAG アプリケーションのコンテキストとして使用します。
  2. ICD-10 コード内で相当するものを検索するためにテキストをベクトル化するトレーニング済みモデル。
  3. ICD-10 コードとテキストの取り込みとベクトル化を行うために必要な Python ライブラリ。
  4. 可能性のある診断を見つけるためのテキストを受け入れる使いやすいフロントエンド。
  5. フロントエンドから受信するリクエストのオーケストレーション。

これらのニーズに対応するために、IRIS は何を提供できるでしょうか?

  1. CSV インポート。RecordMapper 機能を使うか、Embedded Python を直接使用します。
  2. Embedded Python によって、選択されたモデルを使ってベクトルを生成するために必要な Python コードを実装できます。
  3. フロントエンドアプリケーションから呼び出される REST API の公開。
  4. IRIS 内で情報を追跡できる相互運用性プロダクション。

では、開発済みの例を確認しましょう。

d[IA]gnosis

この記事に関連して、開発済みのアプリケーションにアクセスできます。次の記事では、モデルの使用、ベクトルのストレージ、ベクトル検索の使用に至るまで、各機能をどのように実装するかを詳しく説明します。

アプリケーションを確認しましょう。

ICD-10 コードをインポートする

構成画面に、CSV ファイルのフォーマットはインポートしようとしている ICD-10 コードに準拠している必要があると表示されます。 読み取りとベクトル化のプロセスは多数のリソースを使用し、長時間かかるため、Docker コンテナーのデプロイによって、Docker が使用できる RAM メモリだけでなく、要件が割り当てられた RAM を超過する場合に備えてディスクメモリも構成されます。

# iris  iris:    init:true    container_name:iris    build:      context:.      dockerfile:iris/Dockerfile    ports:      -52774:52773      -51774:1972    volumes:    -./shared:/shared    environment:    -ISC_DATA_DIRECTORY=/shared/durable    command:--check-capsfalse--ISCAgentfalse    mem_limit:30G    memswap_limit:32G

ICD-10 コードを含むファイルは、プロジェクトパス /shared/cie10/icd10.csv にあります。100% に達すスト、アプリケーションを使用できます。

私たちのアプリケーションでは、診断コーディング用に 2 つの異なる機能を定義しました。1 つはシステムで受信した HL7 メッセージに基づいたもので、もう 1 つは自由テキストに基づいたものです。

HL7 による診断のキャプチャ

プロジェクトにはテスト用に準備された HL7 メッセージが含まれており、/shared/hl7/messagesa01_en.hl7 ファイルを /shared/HL7In フォルダにコピーすれば、関連付けられたプロダクションが診断を抽出してウェブアプリケーションに表示します。

診断リクエスト画面では、HL7 メッセージングで受信したすべての診断を確認できます。 これを ICD-10 にコーディングするには、虫眼鏡をクリックするだけで、受信した診断に最も近い ICD-10 コードのリストを表示することができます。

選択すると、リストに診断とそれに関連付けられた ICD-10 コードが表示されます。 封筒のアイコンが付いたボタンをクリックすると、元のメッセージと、診断セグメント内で選択した新しいメッセージによって、メッセージが生成されます。

MSH|^~\&|HIS|HULP|EMPI||||ADT^A08|592956|P|2.5.1
EVN|A01|
PID|||1556655212^^^SERMAS^SN~922210^^^HULP^PI||GARCÍA PÉREZ^JUAN^^^||20150403|M|||PASEO PEDRO ÁLVAREZ 1951 CENTRO^^LEGANÉS^MADRID^28379^SPAIN||555283055^PRN^^JUAN.GARCIA@YAHOO.COM|||||||||||||||||N|
PV1||N
DG1|1||O10.91^Unspecified pre-existing hypertension complicating pregnancy^CIE10-ES|Gestational hypertension||A||

このメッセージは /shared/HL7Out パスにあります。

自由テキストによる診断のキャプチャ

テキストアナライザーオプションでは、分析プロセスが実行される自由テキストを含めることができます。 アプリケーションは、見出し語化された 3 つの単語のタプルを検索します(冠詞、代名詞、およびその他の関連性の低い単語は削除されます)。 分析が完了すると、システムは関連する下線付きのテキストと、考えられる診断を表示します。

分析の実行が完了したら、その内容は分析履歴からいつでも参照できます。

分析履歴

実行されたすべての分析は記録され、いつでも参照でき、利用可能なすべての ICD-10 コードを表示できます。

次回の記事では...

Embedded Python を使用して、コンテキストとして使用する ICD-10 コードと自由テキストの両方のベクトル化に特定の LLM モデルを使用する方法を見ていきます。

ご質問やご提案がありましたら、ぜひこの記事のコメントでお知らせください。

0
0 63
記事 Toshihiko Minamoto · 11月 21, 2024 6m read

image
コミュニティの皆さん、こんにちは。
この記事では、iris-RAG-Gen という私のアプリケーションをご紹介します。

iris-RAG-Gen は、IRIS Vector Search の機能を使用して、Streamlit ウェブフレームワーク、LangChain、および OpenAI で ChatGPT をパーソナライズするジェネレーティブ AI 検索拡張生成(RAG: Retrieval-Augmented Generation)アプリケーションです。 このアプリケーションは IRIS をベクトルストアとして使用します。
image

アプリケーションの機能

  • ドキュメント(PDF または TXT)を IRIS に取り込む
  • 選択されたドキュメントの取り込みを使ってチャットする
  • ドキュメントの取り込みを削除する
  • OpenAI ChatGPT

ドキュメント(PDF または TXT)を IRIS に取り込む

以下の手順に従って、ドキュメントを取り込みます。

  • OpenAI キーを入力します。
  • ドキュメント(PDF または TXT)を選択します。
  • ドキュメントの説明を入力します。
  • 「Ingest Document」ボタンをクリックします。

image
 

ドキュメントの取り込み機能は、ドキュメントの詳細を rag_documents テーブルに挿入し、ベクトルデータを保存する 'rag_document + id'(rag_documents の ID)テーブルを作成します。

image

以下の Python コードは選択されたドキュメントをベクトルに保存します。

from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.document_loaders import PyPDFLoader, TextLoader
from langchain_iris import IRISVector
from langchain_openai import OpenAIEmbeddings
from sqlalchemy import create_engine,text

classRagOpr:#Ingest document. Parametres contains file path, description and file type defingestDoc(self,filePath,fileDesc,fileType): embeddings = OpenAIEmbeddings() #Load the document based on the file typeif fileType == "text/plain": loader = TextLoader(filePath)
elif fileType == "application/pdf": loader = PyPDFLoader(filePath)

    <span class="hljs-comment">#load data into documents</span>
    documents = loader.load()        
    
    text_splitter = RecursiveCharacterTextSplitter(chunk_size=<span class="hljs-number">400</span>, chunk_overlap=<span class="hljs-number">0</span>)
    <span class="hljs-comment">#Split text into chunks</span>
    texts = text_splitter.split_documents(documents)
    
    <span class="hljs-comment">#Get collection Name from rag_doucments table. </span>
    COLLECTION_NAME = self.get_collection_name(fileDesc,fileType)
           
    <span class="hljs-comment"># function to create collection_name table and store vector data in it.</span>
    db = IRISVector.from_documents(
        embedding=embeddings,
        documents=texts,
        collection_name = COLLECTION_NAME,
        connection_string=self.CONNECTION_STRING,
    )

<span class="hljs-comment">#Get collection name</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_collection_name</span><span class="hljs-params">(self,fileDesc,fileType)</span>:</span>
    <span class="hljs-comment"># check if rag_documents table exists, if not then create it </span>
    <span class="hljs-keyword">with</span> self.engine.connect() <span class="hljs-keyword">as</span> conn:
        <span class="hljs-keyword">with</span> conn.begin():     
            sql = text(<span class="hljs-string">"""
                SELECT *
                FROM INFORMATION_SCHEMA.TABLES
                WHERE TABLE_SCHEMA = 'SQLUser'
                AND TABLE_NAME = 'rag_documents';
                """</span>)
            result = []
            <span class="hljs-keyword">try</span>:
                result = conn.execute(sql).fetchall()
            <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> err:
                print(<span class="hljs-string">"An exception occurred:"</span>, err)               
                <span class="hljs-keyword">return</span> <span class="hljs-string">''</span>
            <span class="hljs-comment">#if table is not created, then create rag_documents table first</span>
            <span class="hljs-keyword">if</span> len(result) == <span class="hljs-number">0</span>:
                sql = text(<span class="hljs-string">"""
                    CREATE TABLE rag_documents (
                    description VARCHAR(255),
                    docType VARCHAR(50) )
                    """</span>)
                <span class="hljs-keyword">try</span>:    
                    result = conn.execute(sql) 
                <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> err:
                    print(<span class="hljs-string">"An exception occurred:"</span>, err)                
                    <span class="hljs-keyword">return</span> <span class="hljs-string">''</span>
    <span class="hljs-comment">#Insert description value </span>
    <span class="hljs-keyword">with</span> self.engine.connect() <span class="hljs-keyword">as</span> conn:
        <span class="hljs-keyword">with</span> conn.begin():     
            sql = text(<span class="hljs-string">"""
                INSERT INTO rag_documents 
                (description,docType) 
                VALUES (:desc,:ftype)
                """</span>)
            <span class="hljs-keyword">try</span>:    
                result = conn.execute(sql, {<span class="hljs-string">'desc'</span>:fileDesc,<span class="hljs-string">'ftype'</span>:fileType})
            <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> err:
                print(<span class="hljs-string">"An exception occurred:"</span>, err)                
                <span class="hljs-keyword">return</span> <span class="hljs-string">''</span>
            <span class="hljs-comment">#select ID of last inserted record</span>
            sql = text(<span class="hljs-string">"""
                SELECT LAST_IDENTITY()
            """</span>)
            <span class="hljs-keyword">try</span>:
                result = conn.execute(sql).fetchall()
            <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> err:
                print(<span class="hljs-string">"An exception occurred:"</span>, err)
                <span class="hljs-keyword">return</span> <span class="hljs-string">''</span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">"rag_document"</span>+str(result[<span class="hljs-number">0</span>][<span class="hljs-number">0</span>])</code></pre>

 

管理ポータルで以下の SQL コマンドを入力し、ベクトルデータを取得します。

SELECT top 5id, embedding, document, metadata
FROM SQLUser.rag_document2

image

 

選択されたドキュメントの取り込みを使ってチャットする

チャットオプションの選択セクションから「Document」を選択して質問を入力します。アプリケーションはベクトルデータを読み取り、関連する回答を返します。
image
以下の Python コードは、選択されたドキュメントをべく鳥に保存します。

from langchain_iris import IRISVector
from langchain_openai import OpenAIEmbeddings,ChatOpenAI
from langchain.chains import ConversationChain
from langchain.chains.conversation.memory import ConversationSummaryMemory
from langchain.chat_models import ChatOpenAI

classRagOpr:defragSearch(self,prompt,id):#Concat document id with rag_doucment to get the collection name COLLECTION_NAME = "rag_document"+str(id) embeddings = OpenAIEmbeddings() #Get vector store reference db2 = IRISVector ( embedding_function=embeddings,
collection_name=COLLECTION_NAME, connection_string=self.CONNECTION_STRING, ) #Similarity search docs_with_score = db2.similarity_search_with_score(prompt) #Prepair the retrieved documents to pass to LLM relevant_docs = ["".join(str(doc.page_content)) + " "for doc, _ in docs_with_score] #init LLM llm = ChatOpenAI( temperature=0,
model_name="gpt-3.5-turbo" ) #manage and handle LangChain multi-turn conversations conversation_sum = ConversationChain( llm=llm, memory= ConversationSummaryMemory(llm=llm), verbose=False ) #Create prompt template = f""" Prompt: {prompt} Relevant Docuemnts: {relevant_docs} """#Return the answer resp = conversation_sum(template) return resp['response']

</code></pre>


詳細については、iris-RAG-Gen の Open Exchange アプリケーションページをご覧ください。

よろしくお願いします。

0
0 66
記事 Tomohiro Iwamoto · 11月 13, 2024 7m read

以前、Azure用にOAouth2クライアントをセットアップする記事を書いた時に思ったのですが、各IdPはサンプルコードとしてPythonコードや専用のモジュールを提供しているので、それがそのまま使用できれば効率が良いのにな、と思いました。

IRISが埋め込みPython機能としてWSGIをサポートしたことにより、これが簡単に実現しそうなので、その方法をご紹介したいと思います。

導入方法

今回は、IdPとしてOKTAを使用してAuthorization Codeフローを行います。

OKTAでの設定内容

参考までに、今回使用した環境を後半に記載しています。

アプリケーションの起動

コンテナ化してありますので、コンテナのビルド環境をお持ちの方は、下記を実行してください。

git clone https://github.com/IRISMeister/iris-okta-oidc-wsgi
cd iris-okta-oidc-wsgi

python/.env.templateをpython/.envという名前でコピーを作成して、OKTAで得られる設定値を指定してください。

AUTH0_CLIENT_ID="0oaxxxxxxx"  
AUTH0_CLIENT_SECRET="qUudxxxxxxxxxxx"
AUTH0_DOMAIN="dev-xxxxx.okta.com/oauth2/default"

AUTH0_CLIENT_ID,AUTH0_CLIENT_SECRETは後述の「アプリケーション追加」で使用する画面から取得できます。 AUTH0_DOMAINは、後述の「カスタムSCOPE追加」で使用する画面から取得できる発行者URIを設定します。

docker compose build
docker compose up -d

下記でIRISの管理ポータルにアクセスできます。

http://localhost:8882/csp/sys/%25CSP.Portal.Home.zen
ユーザ名:SuperUser, パスワード:SYS

WSGI環境での実行

まずは、純粋なWSGI環境での実行を行って、設定が正しくできているかを確認します。コードはこちらを使用しました。

元々はAuth0用ですが、ほぼそのままでOKTAでも使用できました

下記のコマンドでFlaskを起動します。

docker compose exec iris python3 /usr/irissys/mgr/python/run.py

ブラウザでメインページにアクセスしてください。

http://127.0.0.1:8889/ ではリダイレクトに失敗します。

「Login」をクリックするとOKTAのログイン画面が表示されます。OKTAサインアップ時に使用した多要素認証(スマホアプリ)を使用してログインしてください。

うまく動作した場合は、取得したトークンの情報等が表示されます。namespace: USERと表示されている通り、IRISへのアクセスも行っています。

Welcome Tomohiro Iwamoto!
Logout

{
    "access_token": "eyJraWQiOi.....",
    "expires_at": 1731482958,
    "expires_in": 3600,
    "id_token": "eyJraWQ......",
    "scope": "email user/*.* profile openid",
    "token_type": "Bearer",
    "userinfo": {
        "amr": [
            "mfa",
            "otp",
            "pwd"
        ],
        "at_hash": "3cRg3plSvDPqGUwEBzefoA",
        "aud": "xxxxx",
        "auth_time": 1731477799,
        "email": "iwamoto@intersystems.com",
        "exp": 1731482958,
        "iat": 1731479358,
        "idp": "xxxxxxxxxx",
        "iss": "https://dev-xxxxxx.okta.com/oauth2/default",
        "jti": "ID.Z0icZKkP61n3WDLgD08q3QxJ4Ags6_rwhrqFX3lAUjs",
        "name": "Tomohiro Iwamoto",
        "nonce": "DYrD0GKQPyXuT6Fni1So",
        "preferred_username": "iwamoto@intersystems.com",
        "sub": "xxxxxx",
        "ver": 1
    }
}
namespace: USER

「Logout」をクリックするとOKTAからのログアウトが実行され、最初のページにリダイレクトされます。

IRIS+WSGI環境での実行

ブラウザでIRIS+WSGI用のメインページにアクセスしてください。以降の操作は同じです。

同じFlask用のコードを使用していますので、全く同じ動作になります。

何が可能になったのか

これは「IRIS+WSGIで何が可能になるか?」という問いと同じですが、本トピックに限定すると、例えばbearer tokenであるアクセストークンを、cookie(flaskのsessionの仕組み)ではなく、IRISのDB上に保存する、他のCSP/RESTアプリケーションに渡す、という応用が考えられます。

元々、CSPやIRISのRESTで作成したアプリケーションがあって、そこにIdP発行のアクセストークンを渡したい、といった用法に向いているアプローチかと思います。

また、IRISでWSGIを実行することにより、gunicornのような運用レベルのWSGI用ウェブサーバを別途立てる必要がなくなります。

OKTAでの設定内容

今回使用した環境です。

こちらのトライアル環境を使用しました。若干画面が変わっていましたが、サインアップ手順はこちらを参考にしました。登録の際にはMFA(多要素認証)としてスマホが必要です。

ログインすると、次のようなメイン画面が表示されます。

以降、アプリケーション追加、カスタムSCOPE追加、認証ポリシー設定などを行っています。

アプリケーション追加

メニューのアプリケーション->アプリケーションで、flask-code-flowという名称でアプリケーションを追加します。

「一般」タブのクライアント資格情報のは下記のようになります。

一般設定は下記のようになります。

ログイン設定は下記のようになります。複数の登録がありますが、これは実行環境に合わせて各々オリジン(ブラウザで指定するURL)が異なるためです。

ログアウト設定は下記のようになります。複数の登録がある理由はログイン設定と同じです。

「サインオン」タブの設定は下記のようになります。サインオン方法としてOpenID Connectが指定されます。

「サインオン」タブの下のほうに「ユーザー認証」というセクションがありますので、そこのポリシーの詳細リンクを押して「認証ポリシー」画面に遷移します。

「認証ポリシー」では、ルールはそのままで、アプリケーションには追加したアプリケーションを「アプリを追加」で追加登録します。

「割り当て」タブのクライアント資格情報のは下記のようになります。

カスタムSCOPE追加

リソースサーバ用にカスタムのSCOPEを追加します。「セキュリティ」->「API」でdefaultを編集して、オーディエンスとして適当なURL(ここでは http://localhost/csp/healthshare/sc/fhir/r4 )を設定します。

defaultをクリックすると下記の画面に遷移します。

「スコープ」タブを選択し、「スコープを追加」を押してカスタムSCOPEを追加します。ここではuser/*.*というスコープを追加しました。

アクセスポリシー設定

アクセスポリシーを追加します。「セキュリティ」->「API」で認可サーバ:defaultを選択します。「アクセスポリシー」タブを選択し、default policyにアプリケーションを追加します。

「ルールを追加」を押して、新しいルールを追加します。以下のような設定にしました。

0
0 68
記事 Kawasaki Kazuhito · 10月 14, 2024 15m read

開発者の皆様はじめまして。 私からはIRISのソースコントロール機能を用いたソースの自動チェック機能のご紹介をしたいと思います。 チーム開発では、ソースの可読性や実装方法等がある程度統一されるようにコーディング規約を作成すると思います。 しかし、メンバーの入れ替わりでコーディング規約の説明をしていても徹底されないことが起こることも少なくありません。 なので、ソースコントロールを使用してコンパイル時に自動的にチェックするようにしました。 IRIS内で完結させるメリットとして、エラーチェックだけでなくチェック後にエラーがなければコンパイルまで自動で行えること、 %Dictionary.ClassDefinition(クラス定義)を使用できるので、チェッククラスを作成しやすいこと等があげられます。

目次

  1. ソースコントロールについて
  2. 今回用意したチェック用クラスの紹介
  3. ソースコントロールへの設定
  4. 実際の動作
  5. 感想

1.ソースコントロールについて まず、ソースコントロールについて簡単に記載します。 ソースコントロールとは、一般的にコードに対する変更を追跡し管理することを表します。 IRISのソースコントロール機能には様々なメソッドが用意されています。 今回はそれを使用することでソースの自動チェック機能を実現していきます。 参考リンク:InterSystems IRIS とソース・コントロール・システムの統合、 ソース・コントロール設定の構成

2.今回用意したチェック用クラスの紹介 今回作成したチェック用クラスには基底クラスとして、「%Studio.SourceControl.Base」と「%Studio.Extension.Base」(%Studio.SourceControl.Baseの基底クラス)を使用しています。 上記のクラスにはログイン時のイベントやロード前イベントなどが定義されており、今回は「OnBeforeCompile」(コンパイル前イベント)を使用しました。 image

では、実際のチェック用クラスの内容をご紹介します。 今回は以下をコーディング規則として実装をしています。 ①クラスの命名チェック - 「XXXX」始まりのクラス名であること ②インデントチェック - インデントは4の倍数の半角空白で埋めること ③変数の利用チェック - 定義した変数は利用すること ④引数の利用チェック - パラメータとして受け取った引数は利用すること

Class User.CompileChk Extends %Studio.SourceControl.Base
{

///  処理概要 :コンパイル前チェック
///  <br>IN :InternalName : コンパイル対象クラス
///  <br>OUT :%Status
///  <br>処理詳細:規約に則さない実装がされている場合、コンパイルエラーにする。
Method OnBeforeCompile(InternalName As %String, ByRef qstruct As %String) As %Status
{
    Set InternalName = $REPLACE(InternalName, ".CLS", "")
    Set clsDef = ##Class(%Dictionary.ClassDefinition).%OpenId(InternalName)

    Set SKIPuFLG = $$$NO
    Write !,"****************コンパイルチェック開始****************",!
    Write "TARGET : "_InternalName,!
    Set hasErr = 0
    
    #; クラス名チェック
    Set hasErr = hasErr + '##class(User.Chk.ClassNamingChecker).%New().IsCorrectDefine(clsDef)
    #; インデントチェック
    Set hasErr = hasErr + '##class(User.Chk.IndentChecker).%New().UseWrongIndent(clsDef)
    #; 変数の利用チェック
    Set hasErr = hasErr + '##class(User.Chk.UseValChecker).%New().UseVal(clsDef)
    #; 引数の利用チェック
    Set hasErr = hasErr + '##class(User.Chk.UseArgsChecker).%New().UseArgs(clsDef)
    

    Write "****************コンパイルチェック完了****************",!
    If (hasErr > 0) {
        Return $$$ERROR($$$GeneralError, "コンパイルエラーがあります。")
    } Else {
        Return $$$OK
    }
}

}

①クラスの命名チェッククラス - User.Chk.ClassNamingChecker Class User.Chk.ClassNamingChecker Extends %RegisteredObject {

///  処理概要 :クラス定義の命名規約違反チェック
///  <br>IN :clsDef : クラス定義
///  <br>OUT :%Boolean
///  <br>処理詳細:クラスの命名がコーディング規約に従っているかどうかをチェックする。
Method IsCorrectDefine(clsDef As %Dictionary.ClassDefinition) As %Boolean
{
    Write "*クラス名チェック",!

    Set ret = $$$YES
    If (clsDef '= "") {
        Set clsName = clsDef.Name
        Set ret = ..WriteStartWithStrErr(clsName)
    }

    Return ret
}

///  処理概要 :命名先頭不正のエラー表示
///  <br>IN :clsName クラス名/ keyword キーワード
///  <br>OUT :%Boolean
///  <br>処理詳細:クラス名の先頭がキーワードで開始していなければエラーを表示する。
Method WriteStartWithStrErr(clsName As %String) As %Boolean [ Private ]
{
    If ($FIND(clsName, ".XXXX") > 0) {
        #; OK
        Return $$$YES
    } Else {
        Write "E: クラス名はXXXXという単語で始まる必要があります。",!
        Return $$$NO
    }
}

}

クラス内で行っていること 引数として%Dictionary.ClassDefinition(クラス定義)を受け取り、クラス定義内のプロパティであるNameを使用することで クラス名を取得。取得したクラス名に対して、「.XXXX」を$FINDで検索することでクラスの先頭が「XXXX」であるかチェックを行います。

②インデントチェック - User.Chk.IndentChecker

Class User.Chk.IndentChecker Extends %RegisteredObject
{

///  処理概要 :インデント不正チェック
///  <br>IN :clsDef : クラス定義
///  <br>OUT :%Boolean
///  <br>処理詳細:インデントが4の倍数になっているかをチェックをする。
Method UseWrongIndent(clsDef As %Dictionary.ClassDefinition) As %Boolean
{
    Write "*インデント不正チェック",!
    Set isCorrect = $$$YES
    Set count = clsDef.Methods.Count()
    For i = 1: 1: count {
        Set cnt = 0
        Set method = clsDef.Methods.GetAt(i)
        Do method.Implementation.Rewind()

        While ('method.Implementation.AtEnd) {
            Set cnt = cnt + 1
            Set line = method.Implementation.ReadLine()
            If (line = "") {
                Continue
            }

            If ('$MATCH(line, "^( {4}){1,}[^ ].*")) {
                Set isCorrect = $$$NO
                Write "E: インデントが4の倍数になっていません。: "_method.Name_"+"_cnt_line,!
            }
        }
    }

    Return isCorrect
}

}

クラス内で行っていること 引数として%Dictionary.ClassDefinition(クラス定義)を受け取り、クラス定義内のプロパティであるMethods.Count()でメソッドの数を取得。 メソッドごとに1行ずつチェックを行います。チェックは正規表現を使用(^( {4}){1,}[^ ].*の部分)し、半角スペースが4の倍数になっているかチェックを行います。

③変数の利用チェック - User.Chk.UseValChecker

Class User.Chk.UseValChecker Extends %RegisteredObject
{

///  処理概要 :変数の利用チェック
///  <br>IN :clsDef : クラス定義
///  <br>OUT :%Boolean
///  <br>処理詳細:定義された変数が利用されているかをチェックをする。
Method UseVal(clsDef As %Dictionary.ClassDefinition) As %Boolean
{

    Set isCorrect = $$$YES
    Set count = clsDef.Methods.Count()
    For i = 1: 1: count {
        Set cnt = 0
        Set method = clsDef.Methods.GetAt(i)
        Do method.Implementation.Rewind()

        Set args = method.FormalSpec
        Set argList = {}
        For j = 1: 1: $LENGTH(args, ",") {
            Set arg = $REPLACE($REPLACE($REPLACE($PIECE($PIECE(args, ",", j), ":", 1), "&", ""), "*", ""), "...", "")
            If (arg = "") {
                Continue
            }
            Do argList.%Set(arg, "")
        }

        Set valList = {}
        While ('method.Implementation.AtEnd) {
            Set cnt = cnt + 1
            Set line = method.Implementation.ReadLine()
            If (line = "") {
                Continue
            }
            If (..HasComment(line)) {
                Continue
            }

            If ($FIND(line, "Set ") > 0) {
                Set valNm = $PIECE($REPLACE($PIECE($PIECE(line, "Set ", 2), "="), " ", ""), "(")
                #; オブジェクトへの参照は対象外。
                If (($FIND(valNm, ".") = 0) && ($FIND(valNm, "$") = 0)) {
                    #; 変数の定義があればObjectに登録。
                    If (('valList.%IsDefined(valNm)) && 'argList.%IsDefined(valNm)) {
                        Do valList.%Set(valNm, $$$NO)
                    }
                }
            }

            Set iter = valList.%GetIterator()
            While iter.%GetNext(.key, .value) {
                If ($FIND($REPLACE(line, "Set "_key_" ", ""), key) > 0) {
                    Do valList.%Set(key, $$$YES)
                }
            }
        }
        Set iter = valList.%GetIterator()
        While iter.%GetNext(.key, .value) {
            If ('value) {
                Write "E: "_method.Name_"() にて変数"_key_"は定義されていますが、利用されていない可能性があります。",!
                Set isCorrect = $$$NO
            }
        }
    }
    Return isCorrect
}

Method HasComment(line As %String) As %Boolean [ Private ]
{
    Return ($MATCH(line, "^( )*#;.*") > 0) || ($MATCH(line, "^( )*//.*") > 0)
}

}

クラス内で行っていること ②で行ったようにメソッド単位でチェックを行います。method.FormalSpec でメソッドの引数のリストを含む文字列を取得します。 上記で取得した文字列から、引数のみを抽出して引数リストを作ります。ここまで来たら、メソッドを1行ずつチェックしていきます。 HasCommentメソッドでコメントの場合は読み飛ばすようにしています。まず、「Set」の使用をチェックします。(If ($FIND(line, "Set ") > 0) {) 使用されている場合でもオブジェクトへの参照は対象外とするため、「.」や「$」が使用されている場合は読み飛ばします。(If (($FIND(valNm, ".") = 0) && ($FIND(valNm, "$") = 0)) {) 「.」や「$」が使用されていないかつ、引数のリストに存在しない場合は後のチェックのためにリストに追加します。(value側は$$$NOで登録しておきます) チェックリストを順番にリストのキー項目が定義箇所以外で使用されているかチェックしていきます。(L50~L54) 使用されている場合、該当キー項目のvalueを$$$YESに更新しておきます。 チェック処理としては上記で完了です。あとはvalueが$$$NOの項目を洗い出して、チェック完了となります。

④引数の利用チェック - User.Chk.UseArgsChecker

Class User.Chk.UseArgsChecker Extends %RegisteredObject
{

///  処理概要 :引数の利用チェック
///  <br>IN :clsDef : クラス定義
///  <br>OUT :%Boolean
///  <br>処理詳細:引数が利用されているかをチェックする。
Method UseArgs(clsDef As %Dictionary.ClassDefinition) As %Boolean
{
    Write "*引数の利用チェック",!
    Set ngKeyword = $LISTBUILD(")", """", "}")

    Set isCorrect = $$$YES
    Set count = clsDef.Methods.Count()
    For i = 1: 1: count {
        Set method = clsDef.Methods.GetAt(i)
        Set args = method.FormalSpec

        Do method.Implementation.Rewind()
        Set str = method.Implementation.Read()
        For j = 1: 1: $LENGTH(args, ",") {
            Set arg = $REPLACE($REPLACE($REPLACE($PIECE($PIECE(args, ",", j), ":", 1), "&", ""), "*", ""), "...", "")
            If (arg = "") {
                Continue
            }

            Set isIgnore = $$$NO
            Set ptr = 0
            While $LISTNEXT(ngKeyword, ptr, value) {
                #; NGリストの内容が含まれていると、切り出し対象外。
                If ($FIND(arg, value) > 0) {
                    Set isIgnore = $$$YES
                    Quit
                }
            }
            If (isIgnore) {
                Continue
            }

            If ($FIND(str, arg) = 0) {
                Write "E: 引数が利用されていません。: "_method.Name_"/"_arg,!
                Set isCorrect = $$$NO
            }
        }
    }

    Return isCorrect
}

}

クラス内で行っていること まず、コメントなどを引っ掛けないためにチェックしない文字を定義します。(L10) ③と同様にメソッド単位でチェックをするようにします。(L13~) メソッドの引数を取得します。(L16) メソッドの最初の行からチェックするようにポインタをストリームの先頭にしてメソッドの読込を行います。(L18、L19) 引数の数分チェックをまわしていきます。(L20) 引数の中にチェックしない文字が入っているかチェックします。(L28~L34) チェックしない文字が入っていない場合は、読み込んだメソッドの中で引数が使用されているかチェックを行います。(L39~L42)

3.ソースコントロールへの設定 管理ポータルにて、[システム管理]⇒[構成]⇒[追加の設定]⇒[ソースコントロール] を開くと ソースコントロールの設定画面となります。 imageimage

ソースコントロールクラス名の一覧には「%Studio.SourceControl.Base」クラスを継承したクラスが表示されます。 ソースコントロールを行いたいネームスペースを選択し、使用したいソースコントロールクラスを選択⇒保存します。 今回はUSERのネームスペースにチェック用クラス用意していますが、%SYSにソースコントロールクラスを作成することで全てのネームスペースに対して使用することができます。

4.実際の動作 今回テスト用に用意したクラスが以下のクラスです。

Class User.Test.NewClass1 Extends %RegisteredObject
{

///  処理概要 :テストメソッド
///  <br>IN :Str1 : テスト用文字列1
///  <br>IN :Str2 : テスト用文字列2
///  <br>OUT :%Boolean
Method TestMethod(Str1 As %String, Str2 As %String) As %Boolean
{
   Set test1 = Str1
    Set test2 = "TEST"
    
    Set ^TESTG(test1,"abc") = "hugehuge"
    
    Return $$$OK
}

}

クラス名が「XXXX」始まりでないこと。 - クラスの命名チェック インデントが4の倍数個の半角スペースになっていないこと。(L10) - インデントチェック 定義した変数test2が使用されていないこと。 - 変数の使用チェック TestMethodの引数として用意したStr2が使用されていないこと。 - 引数の使用チェック

実際にコンパイルした結果がこちらです。 image

すべてチェックに引っかかっており、コンパイルもされないようになっています。 では、チェッククラスに指摘された部分を修正したものをコンパイルしてみます。

Class User.Test.XXXXNewClass1 Extends %RegisteredObject
{

///  処理概要 :テストメソッド
///  <br>IN :Str1 : テスト用文字列1
///  <br>IN :Str2 : テスト用文字列2
///  <br>OUT :%Boolean
Method TestMethod(Str1 As %String, Str2 As %String) As %Boolean
{
    Set test1 = Str1
    Set test2 = "TEST"
    Set ^TESTG(test1,test2) = Str2
    Return $$$OK
}

}

image

見事にコンパイルが成功しました。

5.感想 今回参考例として4つのチェックを行いましたが、工夫や組み込み方次第では色々なチェックを組み込めると感じました。 Ex.) 変数がキャメルケースになっているか、利用してほしくないプロパティ等が使用されているか etc… また、他のライブラリでチェックツールは色々とあるかと思いますが、今回はIRISの中だけで完結させており、 チェックだけでなくエラーが出なかった時はコンパイルまで通るところがやはり良い部分に感じました。 今回使用したクラスはGithubにアップしておりますので、興味のある方はご確認いただければと思います。 追記)インデントチェッククラスをEmbedded Pythonで記載してみました。(Githubにもアップしております)

Class User.Chk.IndentCheckerP Extends %RegisteredObject
{

///  処理概要   :インデント不正チェック
///  <br>IN     :clsDef : クラス定義
///  <br>OUT    :%Boolean
///  <br>処理詳細:インデントが4の倍数になっているかをチェックをする。
ClassMethod UseWrongIndentP(clsDef As %Dictionary.ClassDefinition) As %Boolean [ Language = python ]
{
	import iris
	import re
	
	print("*インデント不正チェック\n")
	
	isCorrect = 1
	count = clsDef.Methods.Count()
	for i in range(count):
	    cnt = 0
	    method = clsDef.Methods.GetAt(i + 1)
	    while not method.Implementation.AtEnd:
	        cnt += 1
	        line = method.Implementation.ReadLine()
	        if line == '':
	            continue
	        if len(re.compile("^( {4}){1,}[^ ].*").findall(line)) == 0:
	            isCorrect = 0
	            print("E: インデントが4の倍数になっていません。: " + method.Name + str(cnt) + str(line) + "\n") 
	return isCorrect
}

}

以上になります。ご一読いただき、ありがとうございました。

0
0 165
記事 Akio Hashimoto · 10月 4, 2024 3m read

IRISはPythonの豊富なライブラリや既存のPythonプログラムをそのまま利用する事も、COS内でネイティブにコーディングする事も可能となりました。 しかし開発において、いくつかの問題点があります。

1. Pythonのバージョン

Pythonを使ったプロジェクトを構築していると、バージョンの問題にあたる時があります。 古いバージョンで開発していたところに、使いたいライブラリが対応していなかった等です。 しかし、IRISのEmbedded Pythonを利用する場合には、Pythonランタイムのバージョンに影響される為、プロジェクトで使用するバージョンは、プロジェクト単位はなく、IRISのバージョン単位で決まってしまいます。 また、現時点ではこのPythonランタイムをアップグレードする事はできません。

2. 外部Pythonファイルの利用

私は既に開発済みのPythonプログラムをそのまま活用したいと思い、外部Pythonファイルを読み込んで利用する方法を取りました。 IRISでは外部Pythonファイルを特定の場所に配置する事で、そのファイルをimportする事が出来ます。 デフォルトは{インストールパス}/lib/python です。 しかし、デフォルトではこの1ヶ所のディレクトリが対象となっていて、さらにサブフォルダは対象外となる為、プロジェクト単位などで管理する事も出来ません。

Python対象パスを指定

PythonPathの値を変更する事で、別のディレクトリを指定できます。 また、複数のディレクトリを指定する事も可能です。 Windowsであればカンマ区切り、linux等であればコロン区切りで複数指定が可能です。 但し、間にスペースを入れると認識されませんので注意して下さい。 また、複数ディレクトリに同じ名前のモジュールが存在すると、最初に読み込まれた物を利用するようです。(筆者実体験から) また、このPythonPathの値は、Pythonの対象ディレクトリを指定した場所だけに変更するのではなく、デフォルトのディレクトリに追加される事に注意して下さい。 PythonPathを書き換えてもデフォルトのディレクトリは読み込まれています。 もしデフォルトのディレクトリに同じ名前のPythonファイルがあると、そちらが先にimportされるようなので注意が必要です。

PythonPathの指定 image ここに記載されていないが、デフォルトのディレクトリも対象となている。

複数指定する場合は、スペースを入れない /opt/iris/python/common: /opt/iris/python/project --> コロンの後にスペースを入れると認識されない。

3. 修正が反映しない

IRISは外部Pythonファイルやライブラリを読み込むと、プロセスが閉じるまで再読み込みをしません。 ですので、外部Pythonファイルを修正しても即時反映しません。 この場合、プロセスを一度閉じて再実行する必要があります。 ターミナル実行時などであれば、対処法が思いつきますが、Webブラウザからの場合は、セッションが閉じられても再読み込みされません。 Webブラウザからのアクセスの場合は、ウェブゲートウェイを閉じる必要があります。それは結構な手間です。 そこで、即時反映に近い動きにする為には、importしたモジュールをリロードする方法があります。

importlib.reload({モジュール名})

これをCOSの中でモジュールをimportしている箇所に記載する事で、実行する毎にモジュールのリロードを行います。

import importlib
import module1
import module2

importlib.reload(module1)
importlib.reload(module2)

以上が、筆者が最近経験したEmbedded Pythonを利用する時に起こった問題点です。

0
0 416
記事 Saori Murata · 9月 30, 2024 19m read

開発者の皆さん、こんにちは! InterSystems IRIS(以下、IRIS)を使用したアプリケーション開発において、皆さんは環境設定をどうされていますか? 私は最近になって、「インストールマニフェスト」という機能があることを知りました。 これは、管理ポータルでポチポチしていた作業をコード化・自動化できる強力なツールです! 最初こそとっつきづらかったものの良いところがたくさんあるなと思ったので、簡単にではありますが皆さんにその良さと始め方をご紹介したいと思います。

なお、私が使用しているIRISバージョンは以下です。

2022.1

バージョンが異なる場合、違う書き方になっているもの等が存在する場合がありますので、 公式ドキュメント等を参照し適宜読み替えていただければと思います。

目次

  1. はじめに
  2. インストールマニフェストとは
  3. インストールマニフェストのメリット・デメリット
  4. インストールマニフェストの始め方と基本構造
    1. マニフェストの作成
    2. マニフェストの編集
    3. マニフェストの実行
  5. インストールマニフェストでできること
    1. 変数の設定
    2. ネームスペースとデータベースの設定
    3. データのインポートとマッピング
    4. セキュリティ設定
    5. InterOperability 機能の設定
  6. インストールマニフェスト以外で行う環境設定
    1. ObjectScriptによる実装
    2. Pythonによる実装
  7. まとめ

1. はじめに

IRISを使用するにあたって、管理ポータルでの環境設定は切っても切れない作業だと思います。 IRIS のインストールマニフェストを使用することで、ネームスペース、データベース、セキュリティ設定、InterOperabilityの有効化、 さらにはカスタムコードの実行まで、一連のプロセスを自動化することができます。

本記事では、実際のマニフェストファイルを例に挙げながら、IRIS の環境設定自動化の方法について解説します。

なお、本記事で解説しているサンプルコードの全文と、付随するフォルダ・ファイルについては、Githubにて公開しています。

2. インストールマニフェストとは

IRIS のインストールマニフェストとは、%Installerというツールのことを指します。 インストールマニフェスト定義を記述するだけで、 IRIS環境設定を変更するために必要なコードを自動生成することができます。 他人に共有する際は、定義したインストールマニフェストを配布するだけで済みます。

インストールマニフェストは XML 形式で記述されます。 主要な要素として <Manifest><Namespace><Configuration><Database> などがあります。

なお、公式ドキュメントは以下です。

インストール・マニフェストの作成および使用

3.インストールマニフェストのメリット・デメリット

私が使ってみて感じた、インストールマニフェストのメリット・デメリットについてお伝えします。 全ての場合でインストールマニフェストが優れているとは言えないと思いますので、下記を参考に使用したほうが良いかどうか判断してもらえたらと思います。

  • メリット

    • IRIS環境構築(管理ポータルで手動で実行していた作業)の自動化ができる
    • 環境構築内容をコードとして管理できる
    • 共通の環境を誰でも簡単に、素早く作れるようになる
    • 事前に内容を定義しておけるので、管理ポータルで操作してうっかり設定ミスや設定忘れをしてしまうといったことがなくなる
  • デメリット

    • 環境構築経験が豊富でない人にとっては、公式ドキュメントを読みながらでもコードが示す内容の把握が難解なところがある
    • マニフェストの記法を理解した人でないと、コードのメンテナンスを実施できない
    • 定義の作成、定義の配布、実行方法の共有など、事前準備に手間がかかるため、少人数の環境が対象だったり、設定項目が少なかったりすると手間のほうが大きくなる可能性がある

4. インストールマニフェストの始め方と基本構造

4-1. マニフェストの作成

インストールマニフェストは、簡単に作成することができます。 例えばあなたがスタジオを使っている場合、下記の手順でテンプレートを作成できます。

ファイル > 新規作成 > 一般 > %Installer マニフェスト > OK

こうして作成されるマニフェストは、以下のようになっています。

Include %occInclude  

/// %Installer Manifest MyApp.MyInstaller
Class MyApp.MyInstaller
{  
  
/// マニフェスト定義.  
XData MyManifest [ XMLNamespace = INSTALLER ]  
{  
<Manifest>
	<Namespace>
		<Configuration>
			<Database>
				<!-- Your Manifest code here -->
			</Database>
		</Configuration>
	</Namespace>
</Manifest>
}  
  
/// これは XGL により生成されたメソッド・ジェネレーターです。.  
ClassMethod setup(ByRef pVars, pLogLevel As %Integer = 3, pInstaller As %Installer.Installer, pLogger As %Installer.AbstractLogger) As %Status [ CodeMode = objectgenerator, Internal ]  
{  
    #; XGL ドキュメントでこのメソッドのコードを生成する.  
    Quit ##class(%Installer.Manifest).%Generate(%compiledclass, %code, "MyManifest")  
}  
  
}

4-2. マニフェストの編集

なにやら見覚えのないコードがたくさん生成されますが、安心してください。 そのうち、私たちが編集すればよいのは基本的に下記の部分のみです。

<Manifest>
	<Namespace>
		<Configuration>
			<Database>
				<!-- Your Manifest code here -->
			</Database>
		</Configuration>
	</Namespace>
</Manifest>

■マニフェストの基本構造

詳細については後ほど解説しますが、ここでは基本的な構造について紹介していきます。

  • <Manifest>すべてのタグのルートタグである必要がある。 他のすべてのタグを含む。
  • <Namespace>ネームスペースを定義する。 【親タグ:<Manifest>
  • <Configuration><Namespace>内で構成タグの親タグとして記述する必要がある。【親タグ:<Namespace>
  • <Database>データベースを定義する。 【親タグ:<Configuration>

ここで覚えるべきは、<Manifest>タグは唯一無二ですべてのルートタグとなること、 その中身となるそれぞれのタグにも親子関係があるためそれを守らなければならないこと、の2点です。

これら以外の記述は、インストールマニフェストの作成や実行に必要なコードです。 詳細の説明については、下記のページにあるので任せたいと思います。

%InstallerでInterSystems Cachéにアプリケーションをデプロイする

4-3. マニフェストの実行

このマニフェストを実行したいときは、ターミナルで以下のコマンドを実行してください。 ※マニフェストを実行する時は、%SYSネームスペースで実行することを推奨します。(それ以外のネームスペースでも動作はします)

USER>zn "%SYS"
%SYS>do ##class(MyApp.MyInstaller).setup()

そうすると、マニフェストが環境設定のためのコードを生成し、そのコードが実際にIRISの環境設定を変更していきます。

5. インストールマニフェストでできること

それではさっそく、インストールマニフェストで実際にできることについてみていきましょう。 私が触ったことのある機能を中心に紹介していますが、前述の公式ドキュメントにはそれ以外の設定もできるXMLタグが多数紹介されていますので、参照してみてください。

5-1. 変数の設定

マニフェストでは、変数を設定することができます。 変数設定ができるタグには2種類あります。

  • <Default>:変数値がまだ設定されていない場合のみ、変数値を設定します。(すでに値が設定されている場合、無効になる)【親タグ:<Manifest>
  • <Var>:マニフェストで使用できる変数を定義および設定します。【親タグ:<Manifest>
<Var Name="var1" Value="aaa" />
// 事前に変数 var1 が設定されているので、var1 は aaa のまま
<Default Name="var1" Value="bbb" />
// 事前に変数 var2 は設定されていないので、var2 は ccc になる
<Default Name="var2" Value="ccc" />

5-2. ネームスペースとデータベースの設定

マニフェストを使用して、ネームスペースとそれに関連するデータベースを簡単に作成できます。 関連するタグは3種類です。

  • <Namespace>:ネームスペースを定義する。【親タグ:<Manifest>
    • Name:ネームスペースの名前
    • Create:新しいネームスペースを作成するかどうか(yes/no/overwrite) ※デフォルト:yes
    • Code:コード用データベースの指定
    • Data:データ用データベースの指定
    • Ensemble:InterOperabilityを使用するネームスペースかどうかの指定
  • <Configuration><Namespace> 内で構成タグの親タグとして記述する必要がある。【親タグ:<Namespace>
  • <Database>:データベースを定義する。【親タグ:<Configuration>
    • Name:データベース名
    • Dir:データベース・ディレクトリ
    • Create:新しいデータベースを作成するかどうか
    • InitialSize:データベースの初期サイズ(MB)
    • Resource:データベースへのアクセスを制御するリソース
    • PublicPermissions:リソースに割り当てられる許可の値(新規作成リソースのみ適用)(R/W/RW)
<Default Name="Namespace" Value="TESTNMSP"/>  
<Default Name="DATADB" Value="${Namespace}-GBL"/>  
<Default Name="CODEDB" Value="${Namespace}-RTN"/>
<Default Name="SetupDir" Value="C:\work\git"/>

<!-- ネームスペース作成 -->  
<Namespace Name="${Namespace}" Code="${CODEDB}" Data="${DATADB}" Create="yes" Ensemble="0">  
	<!-- DB作成 -->  
	<Configuration>  
		<Database Name="${DATADB}" Dir="C:\IRISDB\${Namespace}\GBL" Create="yes" InitialSize="100" Resource="%DB_${DATADB}" PublicPermissions="R"/>  
		<Database Name="${CODEDB}" Dir="C:\IRISDB\${Namespace}\RTN" Create="yes" InitialSize="10" Resource="%DB_${CODEDB}" PublicPermissions="R"/>  
	</Configuration>  
</Namespace>

■結果(管理ポータル画面)

(ネームスペース)

(データベース)

5-3. データのインポートとマッピング

マニフェストを使用して、特定のネームスペースに既存のデータやコードをインポートすることもできます。 関連するタグは1種類です。(既出除く)

  • <Import>:ファイルをインポートする。(%SYSTEM.OBJ.ImportDir または %SYSTEM.OBJ.Load を使用する)【親タグ:<Namespace>
    • File:インポートするファイルまたはフォルダ
    • Flags:コンパイル・フラグ(参考:フラグおよび修飾子
    • Recurse:再帰的にインポートするかどうかを指定 ※デフォルト:0
    • IgnoreErrors:エラー時に続行するかどうかを指定 ※デフォルト:0
<Default Name="Namespace" Value="TESTNMSP"/>  
<Default Name="DATADB" Value="${Namespace}-GBL"/>  
<Default Name="CODEDB" Value="${Namespace}-RTN"/>
<Default Name="SetupDir" Value="C:\work\git"/>

<Namespace Name="${Namespace}" Code="${CODEDB}" Data="${DATADB}" Create="yes" Ensemble="0">  
	<!-- グローバル、クラス、ルーチンインポート -->  
	<Import File="${SetupDir}\Test1" Flags="ck" Recurse="1" IgnoreErrors="1"/>  
	<Import File="${SetupDir}\Test2" Flags="ck" Recurse="1" IgnoreErrors="1"/>  
</Namespace>

■結果(管理ポータル画面)

マニフェストを使用して、グローバルやクラスをマッピングすることもできます。 関連するタグは3種類です。(既出除く)

  • <GlobalMapping>:グローバルを現在のネームスペースにマッピングする。【親タグ:<Configuration>
    • Global:グローバル名
    • From:グローバルのソース・ネームスペース
  • <ClassMapping>:パッケージを現在のネームスペースにマッピングする。【親タグ:<Configuration>
    • Package:マップするパッケージ
    • From:マッピングに使用するソース・データベース
  • <RoutineMapping>:ルーチンを現在のネームスペースにマッピングする。【親タグ:<Configuration>
    • Name :ルーチン
    • Type :ルーチン・タイプ
    • From:ルーチンのソース・データベース
<Default Name="Namespace" Value="TESTNMSP"/>
<Default Name="Namespace2" Value="${Namespace}2"/>  
<Default Name="DATADB2" Value="${Namespace2}-GBL"/>  
<Default Name="CODEDB2" Value="${Namespace2}-RTN"/>

<Namespace Name="${Namespace2}" Code="${CODEDB2}" Data="${DATADB2}" Create="yes" Ensemble="0">  
	<Configuration>
		<!-- DB作成 -->  
		<Database Name="${DATADB2}" Dir="C:\IRISDB\${Namespace2}\GBL" Create="yes" InitialSize="100" Resource="%DB_${DATADB2}" PublicPermissions="R"/>  
		<Database Name="${CODEDB2}" Dir="C:\IRISDB\${Namespace2}\RTN" Create="yes" InitialSize="10" Resource="%DB_${CODEDB2}" PublicPermissions="R"/>  
		<!-- グローバル、クラス、ルーチンマッピング設定 -->  
		<GlobalMapping Global="test2" From="${DATADB}"/>  
		<ClassMapping Package="Test2" From="${CODEDB}"/>  
	</Configuration>  
</Namespace>

■結果(管理ポータル画面)

5-4. セキュリティ設定

マニフェストを使用して、ロールやユーザアカウントを複数作成することができます。 関連するタグは2種類です。

  • <Role>:ロールを定義する。【親タグ:<Manifest>
    • Name:ロール名
    • Description:ロールの説明(カンマ不可)
    • Resources:ロールで保持されているリソースと特権。
    • RolesGranted:付与されるロール
  • <User>:ユーザを定義する。【親タグ:<Manifest>
    • Username:ユーザ名
    • PasswordVar:ユーザ・パスワードが含まれる変数の名前(変数名を渡す必要があることに注意)
    • Fullname:ユーザのフルネーム
    • Roles:ユーザを割り当てるロールのリスト
    • Namespace:ユーザの実行開始ネームスペース
    • Enabled:ユーザが有効かどうか
    • Comment:オプションコメント
    • ChangePassword:次回ログイン時にユーザにパスワードの変更を要求するかどうか
    • ExpirationDate:それ以降のユーザ・ログインが無効になる日付
<Default Name="TestUserPw" Value="12345"/>

	<!-- ロール作成・変更 -->  
<Role  
	Name="TestOperator"  
	Description="テスト運用者ロール"  
	Resources="%DB_TESTNMSP-GBL:RW,%DB_TESTNMSP-RTN:RW"  
	RolesGranted="%Developer"/>  
<Role  
	Name="TestAdministrator"  
	Description="テスト管理者ロール"  
	RolesGranted="%All"/>  
 
<!-- ユーザ作成・変更 -->  
<User  
	Username="TestUser1"  
	PasswordVar="TestUserPw"  
	Fullname="テストユーザ1"  
	Roles="TestOperator"  
	Namespace="USER"  
	Enabled="1"  
	Comment="テストユーザ1"/>  
<User  
	Username="TestUser2"  
	PasswordVar="TestUserPw"  
	Fullname="テストユーザ2"  
	Roles="TestAdministrator"  
	Namespace="USER"  
	Enabled="1"  
	Comment="テストユーザ2"/> 

■結果(管理ポータル画面)

(ロール)

(ユーザ)

■クラスメソッドの実行(Invoke)

また、管理ポータルのセキュリティ設定をまるっと読み込むこともできます。 これにより、管理ポータルやターミナル等のログインの有効化や、詳細のセキュリティ設定までを一括で行うことができます。 ロールやユーザアカウントについても、この読み込みにより一括で作成することができます。 関連するタグは2種類です。(既出除く)

  • <Invoke>:クラス・メソッドを呼び出して、実行結果を変数の値として返す。【親タグ:<Namespace>
    • Class:クラス名
    • Method:メソッド名
    • CheckStatus:返されたステータスをチェックするかどうか
    • Return:結果の書き込み先の変数の名前
  • <Arg><Invoke> または <Error> から呼び出されるメソッドに引数を渡す。【親タグ:<Invoke>, <Error>
    • Value:引数の値
<Default Name="SetupDir" Value="C:\work\git"/>

<Namespace Name="%SYS" Create="overwrite">  
	<!-- セキュリティ設定のインポート -->  
	<Invoke Class="Security.System" Method="ImportAll" CheckStatus="false">  
		<Arg Value="${SetupDir}\SecurityExport.xml"/>  
	</Invoke>  
</Namespace>

これらのタグは、セキュリティ設定のインポート以外にも様々な用途に使用することができます。 例えば、タスクスケジュール全体のインポートをセキュリティと同じように行ったり、自作のクラスの処理を実行したり、既存のシステムクラスの処理を実行したりなどです。

※管理ポータルのログインの有効化設定については、2024.3以降のバージョンであればマニフェストのタグにて変更することができます。 (ターミナルのログイン有効化については、現在は<Invoke>を用いる方法か、後述の別途マニフェスト以外のコードを書く方法で実現するしかありません) 具体的には、下記のタグで実現できます。

  • <CSPApplication>:クラス内で定義されている 1 つ以上の Web アプリケーションを定義する。【親タグ:<Namespace>
    • AuthenticationMethods:有効な認証方法(例:32=password、64=unauthenticated)
    • CSPEnabled:CSP が有効かどうか ※デフォルト:1
    • DefaultTimeout:セッション・タイムアウト
    • Description:説明
    • Directory:CSP ファイルへのパス
    • Url:Webアプリケーションの名前

※セキュリティ設定のインポートの事前準備として、管理ポータルのセキュリティ設定(上述の「SecurityExport.xml」にあたるもの)をXMLとしてエクスポートしておく必要があります。 エクスポートは、次のように実施します。 ターミナルで^SECURITY ユーティリティを起動し、12) System parameter setup > 5) Export All Security settings を選択します。 そうすると、C:\InterSystems\[IRISインスタンス名]\mgr\SecurityExport.xml ができているはずです。 (IRISを入れている場所によっては出力場所は異なります)

★4.セキュリティ情報(ユーザ・サービスなど)のインポートInterSystems 製品の設定内容をインポート/エクスポートする方法 > セキュリティについて

%SYS>Do ^SECURITY
:
Option? 12       // System parameter setup
:
Option? 5        // Export All Security settings

Export ALL security records? Yes => Yes

Export to file name SecurityExport.xml =>
Parameters? "WNS" =>
Confirm export of selected security records to SecurityExport.xml? No => Yes

■結果(管理ポータル画面)

(デフォルトでは管理ポータルのログイン時にパスワード認証はないが、それをマニフェストで有効化した例)

5-5. InterOperability 機能の設定

InterOperability機能を使用する場合、専用のネームスペースを作成することができます。 また、プロダクションを自動的に設定することもできます。 関連するタグは2種類です。<Namespace>も改めて紹介します。

  • <Namespace>:ネームスペースを定義する。【親タグ:<Manifest>
    • Name:ネームスペースの名前
    • Create:新しいネームスペースを作成するかどうか(yes/no/overwrite) ※デフォルト:yes
    • Code:コード用データベースの指定
    • Data:データ用データベースの指定
    • Ensemble:InterOperabilityを使用するネームスペースかどうかの指定
  • <Production>:プロダクションを定義する。【親タグ:<Namespace>
    • Name:プロダクション名
    • AutoStart:プロダクションを自動的に起動するかどうか ※デフォルト:0
<Default Name="Namespace3" Value="ENSNMSP"/>
<Default Name="DATADB3" Value="${Namespace3}-GBL"/>  
<Default Name="CODEDB3" Value="${Namespace3}-RTN"/>  
<Default Name="SetupDir" Value="C:\work\git"/>

<Namespace  Name="${Namespace3}"  Code="${CODEDB3}"  Data="${DATADB3}"  Create="yes"  Ensemble="1">
	<!-- Database作成などの設定は省略 -->
	<!-- グローバル、クラス、ルーチンインポート -->  
	<Import File="C:\work\git\Test3" Flags="ck" Recurse="1" IgnoreErrors="1"/>
	<!-- プロダクションの作成 -->
	<Production  Name="Test3.job.Main"  AutoStart="1"  />
</Namespace>

■結果(管理ポータル画面)

6. インストールマニフェスト以外で行う環境設定

マニフェストを作成していると、管理ポータルでは設定できるけどマニフェストではどうやるのだろう?という設定に度々出会います。 公式ドキュメントを見るなどしてマニフェストでの実現方法がわかる場合もありますが、 中にはマニフェストでは実現方法がないもの、後続のバージョンでは修正されたが今使っているバージョンではバグが残っており実現できないもの、 等がある場合があります。

その場合、あきらめて管理ポータルで設定するというのも手ですが、 別途コード化してマニフェストの処理と一緒に実施してしまうのもありです。

実際に、いくつかの設定をマニフェスト以外のコードで実現し、それをマニフェストと一緒に実行するコードの簡単な例をご紹介します。

6-1. ObjectScriptによる実装

下記のコードでは、マニフェストの生成・実行処理であるsetupメソッド(前述のスタジオでの新規作成にて自動作成されるメソッド)を、 自作のsetupExecuteというメソッドでラップしています。 その上で、ロケールの変更や不要タスクの一時停止といった処理をコードで記述し、実行します。

/// <h2>マニフェスト生成・実行処理</h2>  
/// <p><pre>SAMPLES>  
/// Do ##class(MyApp.MyInstaller).setup()  
/// </p></pre>  
ClassMethod setup(ByRef pVars, pLogLevel As %Integer = 3, pInstaller As %Installer.Installer, pLogger As %Installer.AbstractLogger) As %Status [ CodeMode = objectgenerator, Internal ]  
{  
	#; XGL ドキュメントでこのメソッドのコードを生成する.  
	Quit ##class(%Installer.Manifest).%Generate(%compiledclass, %code, "MyManifest")  
}  
  
/// <h2>セットアップ開始処理</h2>  
/// <p>return %Status</p>  
/// <p><pre>SAMPLES>  
/// Do ##class(MyApp.MyInstaller).setupExecute()  
/// </p></pre>  
ClassMethod setupExecute(ByRef pVars) As %Status  
{  
	Set tSC = 0  
	New $NAMESPACE  
	Set $NAMESPACE = "%SYS"  
	#; すでにネームスペースがあるときはManifestの処理はスキップ  
	If ('##class(%SYS.Namespace).Exists("TESTNMSP")) {  
		Set tSC = ..setup(.pVars)  
	}  
	  
	#; 日本語ロケールへ変更  
	Do ##class(Config.NLS.Locales).Install("jpuw")  
	#; 不要なタスクを停止  
	Do ..SuspendTask()  
	  
	Return tSC  
}

/// <h2>タスク停止</h2>  
/// <p>不要なタスクスケジュールを停止する。</p>  
/// <p><pre>SAMPLES>  
/// Do ##class(MyApp.MyInstaller).SuspendTask()  
/// </p></pre>  
ClassMethod SuspendTask()  
{  
	Set targetId = -1  
	Set query = 0  
	Set query($INCREMENT(query)) = "SELECT %ID,Name FROM %SYS.Task"  
	Set tStatement = ##class(%SQL.Statement).%New()  
	Do tStatement.%Prepare(.query)  
	Set result = tStatement.%Execute()  
	While (result.%Next()) {  
		If (result.%GetData(2) = "機能トラッカー") {  
			Set targetId = result.%GetData(1)  
			Quit  
		}  
	}  
	If (targetId '= -1) {  
		#; 機能トラッカーを一時停止  
		Do ##class("%SYS.TaskSuper").Suspend(targetId)  
	}  
}

これを実行する時は、setupメソッドではなくsetupExecuteメソッドを、ターミナルから実行します。 %SYSネームスペースから実行してください。

USER>ZN "%SYS"
%SYS>Do ##class(MyApp.MyInstaller).setupExecute()

■結果(管理ポータル画面)

(機能トラッカーを一時停止した結果)

6-2. Pythonによる実装

本題からは逸れてしまいますが、「InterSystems Embedded Python」について私は今まで触ったことがなかったため、この機会にチャレンジしてみました。 簡単にですが、その結果と所感について述べたいと思います。

6-1の内容を、Embedded Pythonを用いて書き換えたコードは以下です。 ※setupメソッドについては「objectgeneratorメソッド」という特別なメソッドのため、ObjectScriptのみでしか動作しません。そのため、Pythonへの書き換えはできないようでした。

/// <h2>マニフェスト生成・実行処理</h2>
/// <p><pre>SAMPLES>
/// Do ##class(MyApp.MyInstaller).setup()
/// </p></pre>
ClassMethod setup(ByRef pVars, pLogLevel As %Integer = 3, pInstaller As %Installer.Installer, pLogger As %Installer.AbstractLogger) As %Status [ CodeMode = objectgenerator, Internal ]
{
     #; XGL ドキュメントでこのメソッドのコードを生成する.
     Quit ##class(%Installer.Manifest).%Generate(%compiledclass, %code, "MyManifest")
}

/// <h2>セットアップ開始処理</h2>
/// <p>return %Status</p>
/// <p><pre>SAMPLES>
/// Do ##class(MyApp.MyInstaller).setupExecute()
/// </p></pre>
ClassMethod setupExecute(pVars As %SYS.Python = "") As %Status [ Language = python ]
{
	import iris
	tSC = 0
	# すでにネームスペースがあるときはManifestの処理はスキップ
	if not iris.cls('%SYS.Namespace').Exists('TESTNMSP') == 1:
		tSC = iris.cls(__name__).setup(pVars)

	# 日本語ロケールへ変更
	iris.cls('Config.NLS.Locales').Install('jpuw')
	# 不要なタスクを停止
	iris.cls(__name__).SuspendTask()

	return tSC
}

/// <h2>タスク停止</h2>
/// <p>不要なタスクスケジュールを停止する。</p>
/// <p><pre>SAMPLES>
/// Do ##class(MyApp.MyInstaller).SuspendTask()
/// </p></pre>
ClassMethod SuspendTask() [ Language = python ]
{
	import iris
	
	targetId = -1
	df = iris.sql.exec('SELECT %ID,Name FROM %SYS.Task').dataframe()
	for i in range(len(df)):
		if df.iloc[i, 1] == '機能トラッカー':
			targetId = df.iloc[i, 0]
			break

	if not targetId == -1:
    	# 機能トラッカーを一時停止
		iris.cls('%SYS.TaskSuper').Suspend(int(targetId))
}

※SuspendTaskメソッドの実行に当たっては、事前にpandasライブラリのインストールが必要です。 未インストールの場合、下記のコマンドを実行してインストールを実行してください。 (IRISを入れている場所によっては対象のパスは異なります)

> cd C:\InterSystems\[IRISインスタンス名]\bin
> irispip install --target ..\mgr\python pandas

Embededd Pythonを少しだけ使用してみた所感としては、 純粋にPythonを書くところは知っている記法をそのまま書けるので苦なく書けましたが、 IRISとの連携部分(IRISのクラスを利用するなどIRIS独自の機能を利用したいとき)は都度書き方を調べる必要があったので少し大変でした。 一度書き方を覚えてしまえばより多くのコードを書く時でも問題なさそうですが、慣れが必要だと思います。 ObjectScriptの記法に慣れていると、ObjectScriptで当たり前にできることがPythonでは実装が難しい、直感的に書けない場合もあるようなので、注意が必要です。

しかし、それを踏まえてもコードをPythonで書けるのは良いと思いました! Pythonの記法のメリットとしてObjectScriptよりもシンプルに記載できる箇所も多いですし、 前述のPythonでは実装が難しい部分についても、バージョンがあがっていくことで機能は徐々に追加されていくと思いますので、これからに期待ができます。 なにより、ObjectScriptの経験がない人に対しても共通の言語を持てるというのはとても素晴らしいことだと思います。

これを実行する時は、ObjectScriptのときと同じで問題ありません。 %SYSネームスペースから実行してください。 ※そうでないと、「Config.NLS.Locales」の実行時にエラーとなります(%SYSにあるパッケージのため)

USER>ZN "%SYS"
%SYS>Do ##class(MyApp.MyInstaller).setupExecute()

7. まとめ

IRIS のインストールマニフェストを使用することで、環境設定の自動化が可能になり、共通の環境を素早く構築できます。 これにより、開発チームの生産性が向上し、人為的なミスも減らすことができそうです。

本記事で興味を持っていただいた方は、ぜひインストールマニフェストを使ってみてください! 以上、ここまでお読みいただきありがとうございました。

0
1 309
記事 Kosaku Ikeda · 9月 23, 2024 6m read

コミュニティの皆さんこんにちは。

突然ですが、皆さんはIRISの機能にある「ユニットテスト」は利用されているでしょうか。
筆者はまだ実装まで行えていませんが、各関数の品質保証を担保するため導入を検討している段階です。

現状、IRISのユニットテストには下記2点の対応すべき点があると考えています。

  1. テスト結果の可読性が低い(先日vscodeで拡張機能が出ていましたが、やはり見ずらいと感じました)
  2. ユニットテストを自動で実行する手段がない

特にテストが継続的に自動で実施されないと、ユニットテスト自体が次第に陳腐化し、実行されなくなり忘れ去られる恐れがあると考えます。
ただし、意味もなく定期的にテストを実行しても効果がありません。
そこで、Gitのpushのタイミングで行おうと考えました。

次にテスト環境です。
テスト環境の構築は、テスト自動化の観点からみるとCI/CDツール等を利用するのが一般的だと思います。
ただ今回は、テスト環境の構築を簡易にすませたいと考え、IRISの既存技術を組み合わせて構築しようと考えました。

そこで運用幅の広いInteroperabilityとユニットテストを組み合わせて、テストの自動化が可能か考察していきたいと思います。

【ユニットテスト全体概要】

【全体の流れ】

 ■ユーザの開発環境

  ①ユーザは改修したクラスをGitへpushする

 ■Git用のサーバ

0
0 210
記事 Hiroshi Sato · 9月 12, 2024 3m read

これは InterSystems FAQ サイトの記事です。
 

PythonからObjectScriptのルーチンを直接呼び出すことはできませんが、クラスメソッドを経由して間接的に呼び出すことができます。

しかし、Pythonの変数とObjectScriptのローカル変数は内部構造が異なるため、情報の交換には少し工夫が必要です。

簡単なサンプルでその方法について説明します。

まず、2つの変数を足し算する簡単なルーチン ^testを作ります。 

TEST    ;
    set sum = a + b

 

次にこの^testを呼び出すPythonのメソッドを含んだUser.testというクラスを作ります。

そしてpyという名前のPythonのメソッドを作成します。

先述の通りPythonからObjectScriptのルーチンを直接呼び出すことはできないので、ルーチンを間接的に呼び出すObjectScriptのメソッドを作成する必要があり、そのメソッドをPythonメソッドから呼び出すようにします。

渡したいデータが複数個ある場合、その数分引数を用意するのは面倒なため、Pythonの場合、情報をまとめて交換する際にdictionary(辞書)やlistという構造を使用することができます。

ここでは、dictionaryを使用する例を紹介します。

0
0 132
記事 Akio Hashimoto · 7月 5, 2024 2m read

IRISでPythonを扱う時に、既存の%DynamicObject型の値をそのまま利用したいと思うのですが、Embedded Pythonは自動で%DynamicObjectをdict型にはしてくれません。親和性はとてもあるのですが。。。

そこで、既存プログラムで生成した%DynamicObject型の値をPython側、特に外部のPythonファイル側でdict型を期待している関数に利用するにはどうすれば良いか。

少しスマートではありませんが、%DynamicObjectを一旦JSON文字列に置き換え、Embedded Python 内でJSON文字列からdict型に変換する方法しかないようです。
以下が、その手順です。

Set data = {}
Set data.name = "hanako"
Set data.age = 20

Do ..testPython(data)

ClassMethod testPython(arg As %DynamicObject) [ Language = python ]
{
    import json
    import pythonfile

    data = json.loads(arg._ToJSON())
    pythonfile.test(data)
}

pythonfile.py

def test(arg):
    name = arg.name
    age = arg.age

arg._ToJSON()で%DynamicObject型の値をJSON文字列に変換し、更に、json.loads()でJSON文字列をdict型に置き換えています。
"_ToJSON"は、%DynamicObject型の%ToJSONメソッドです。

Pythonファイルの関数からdict型で戻り値がある場合には、この逆を行えば%DynamicObjectで戻すことが出来ます。

Set data = {}
Set data.name = "hanako"
Set data.age = 20

Set sc =  ..testPython(data)
Write sc.status,!

ClassMethod testPython(arg As %DynamicObject) As %DynamicObject [ Language = python ]
{
    import json
    import pythonfile

    result = {}

    data = json.loads(arg._ToJSON())
    re = pythonfile.test(data)
    result.fromJSON(json.dumps(re))

    return result
}

pythonfile.py

def test(arg):
    result = {
        "age":arg.age,
        "status":"ok"
    }

    return result

これで、既存のCOSプログラムと、既存のPythonプログラムを更に有効活用出来るようになります。

5
0 252
記事 ima · 7月 20, 2024 24m read

埋め込みPythonは、同じプロセス空間で、IRIS言語とPython言語を組み合わせて使える面白い環境を提供しますが、組み合わせて使う場合、オブジェクトタイプとそのアクセス方法の違いをはっきり意識して使わないと混乱するように思います。その使い分けの勉強の為、両言語のオブジェクト参照から、その構造を解析ダンプするツールを作ってみました。とくに、実行中のPython情報が、ZWRITE Oref コマンドでの表示しかないようなので、有用かも知れません。ツールは、まだ、間違い、改良等があると思います(教えて下さい)が、ポストします。

ツール本体: Py.Dump.cls

1
0 299
記事 Toshihiko Minamoto · 7月 16, 2024 12m read

Pandas は単に人気のあるソフトウェアライブラリだけではありません。 これは、Python データ分析環境の基礎でもあります。 その単純さとパワーで知られており、データの準備と分析の複雑さをより扱いやすい形態に変換する上で不可欠な多様なデータ構造と関数が備わっています。 これは、主要なデータ管理および分析ソリューションである InterSystems IRIS プラットフォームのフレームワーク内で、主要評価指標(KPI)やレポート作成用の ObjectScript などの特殊な環境に特に関連しています。
 
データの処理と分析の分野において、Pandas はいくつかの理由により際立っています。 この記事では、それらの側面を詳細に探ります。
 

  • データ分析における Pandas の主なメリット:

ここでは、Pandas を使用する様々なメリットについて深く掘り下げます。 直感的な構文、大規模なデータセットの効率的な処理、および異なるデータ形式のシームレスな操作などが含まれます。 Pandas を既存のデータ分析ワークフローに統合する容易さも、生産性と効率を強化する大きな要因です。

  • Pandas による一般的なデータ分析タスクのソリューション:

Pandas には、単純なデータ集計から複雑な変換まで、日常的なデータ分析タスクを処理できる十分な汎用性が備わっています。 こういった一般的な課題の解決に Pandas をどのように使用できるについて、データクリーニング、変換、および探索的データ分析における機能を紹介しながら説明します。 このセクションでは、Pandas によってこれらのタスクがどれほど単純化されるかに関する実用的なデータを提供します。

  • IRIS の ObjectScript KPI で直接 Pandas を使用する:

IRIS プラットフォームで KPI の開発に Pandas と ObjectScript を統合すると、まさに変革がもたらされます。 ここでは、直接 ObjectScript 内で Pandas を使用して KPI 開発プロセスを強化する方法を説明します。 また、Pandas を使ってデータの分析と可視化を行い、それによってより堅牢でインサイトに満ちた KPI を得る実用的な例も探ります。

  •  IRIS 分析プロセスで Pandas を実装する際の推奨事項:

既存の分析プロセスに新しいツールを実装するのは困難な場合があります。 そのため、このセクションでは、Pandas をできる限り円滑に IRIS 分析エコシステムに統合するためにベストプラクティスと推奨事項を紹介します。 セットアップと構成から最適化とベストプラクティスまで、データ分析ワークフローへの Pandas の統合を成功させるための基本的なガイドラインを説明します。
 
Pandas は、Python プログラミング言語による強力なデータ分析ライブラリです。 Pandas でデータ分析を行うことには、以下のようないくつかのメリットがあります。
 

  1. 使いやすさ: Pandas にはデータ操作用の単純で直感的なインターフェースが備わっています。 NumPy ライブラリをベースに構築されており、DataFrames などの高レベルデータ構造を提供するため、表形式データを簡単に操作できます。  
  2. データ構造: Pandas の主なデータ構造は Series と DataFrame です。 Series はラベル付きの一次元配列であるのに対し、DataFrame は 1 セットの Series を表現する 2 次元のテーブルです。 これらのデータ構造を組み合わせることで、データを便利に格納し、操作することができます。  
  3. 欠損データの処理: Pandas には欠損データ(NaN または None)の検出と処理に使用する便利なメソッドが備わっています。 欠損データを削除、入力、または置換するメソッドがいくつか含まれるため、実際のデータの操作が単純化されます。  
  4. データのグループ化と集計: Pandas では、特徴毎のデータのグループ化と各データグループへの集計関数(合計、平均、中央など)の適応を簡単に行えます。  
  5. 強力なインデックス作成機能: Pandas には、データのインデックスを作成するための柔軟なツールが備わっています。 ラベル、数値インデックス、または複数のレベルのインデックス作成を使用できます。 データを効率的に絞り込み、選択、および操作することができます。  
  6. データの読み取りと書き込み: Pandas では、CSV、Excel、SQL、JSON、HTML など、複数のデータ形式がサポートされています。 様々なソースのデータの読み取りやソースへの書き込みのプロセスが容易です。  
  7. 広範な可視化機能: Pandas には、Matplotlib や Seaborn などの可視化ライブラリが統合されているため、特に Embedded Python 経由の統合を通じて DeepSeeWeb を使用することで、グラフの作成やデータの可視化を簡単に行えます。  
  8. 効率的な時間管理: Pandas には、タイムスタンプや期間を操作するための強力なツールなど、時系列の操作に使用できる機能が複数含まれています。  
  9. 広範なデータ操作機能: ライブラリには、データの絞り込み、ソート、および再構成や、テーブルの結合やマージを行うための様々な関数が備わっているため、強力なデータ操作ツールを得られます。  
  10. 優れたパフォーマンス: Pandas は、大量のデータを処理できるように意図的に最適化されています。 Cython と強化されたデータ構造を使用することで、高いパフォーマンスを提供しています。

 
ObjectScript 環境で Pandas の実装例を見てみましょう。 開発環境として VSCode を使用します。 この場合の IDE は、ObjectScript 用のデバッガーとエディターを提供する InterSystems ObjectScript Extension Pack の提供状況に基づいて選択されました。
まずは、KPI クラスを作成しましょう。

  Class BI.KPI.pandasKpi Extends%DeepSee.KPI
{
}


 
次に、KPI のタイプ、名前、および列とフィルターの数を定義する XML ドキュメントを作成する必要があります。
 

XData KPI [ XMLNamespace = "http://www.intersystems.com/deepsee/kpi" ]
{
<!-- 'manual' KPI タイプは、ユーザーが定義したクラスメソッドからデータが収集されることを DeepSee に示します-->
 
<kpiname="MembersPandasDemo"sourceType="manual">
 
 <!-- この KPI クエリに必要となる列は 1 つのみです -->
 
<propertycolumnNo="1"name="Members"displayName="Community Members"/>
 
<!-- 最後に、メンバーのフィルターを定義する必要があります -->
 
<filtername="InterSystemsMember"displayName="InterSystems member"sql="SELECT DISTINCT ISCMember from Community.Member"/>
 
 </kpi>
}


 
次のステップは、Python 関数の定義、インポートの記述、および必要な変数の作成です。
 

ClassMethod MembersDF(sqlstring) As%Library.DynamicArray [ Language = python ]
{
            # First of all, we import the most important library in our script: IRIS.
            # IRIS library provides syntax for calling ObjectScript classes.
            # It simplifies Python-ObjectScript integration.
            # With the help of the library we can call any class and class method, and
            # it returns whatever data type we like, and ObjectScript understands it.
            import iris
            # Then, of course, import the pandas itself.
            import pandas as pd
            # Create three empty arrays:
            Id_list = []
            time_list = []
            ics_member = []


 
次のステップ: データベースに対するクエリを定義します。
 

# Define SQL query for fetching data.
# The query can be as simple as possible.
# All the work will be done by pandas:
            query = """
            SELECT
            id as ID, CAST(TO_CHAR(Created, 'YYYYMM') as Int) as MonthYear, ISCMember as ISC
            FROM Community.Member
            order by Created DESC
            """


次に、生成されるデータを配列グループに保存する必要があります。

# Call the class specified for executing SQL statements.
# We use embedded Python library to call the class:
sql_class = iris.sql.prepare(query)
 
# We use it again to call dedicated class methods:
rs = sql_class.execute()
 
# Then we use pandas directly on the result set to make dataFrame:
data = rs.dataframe()


            
We also can pass an argument to filter our data frame.

# フィルターの例
# We take an argument sqlstring which, in this case, contains boolean data.
# With a handy function .loc filtering all the data
sqlstring が False でない場合:
        data = data.loc[data["ISC"] == int(sqlstring)]


 
次に、データをグループ化して、その x 軸を定義する必要があります。
 
 

# Group data by date displayed like MonthYear:
grouped_data = data.groupby(["MonthYear"]).count()

 

残念ながら、直接グループ化されたデータ DataFrame から日付列を取得することはできないため、
代わりに元の DataFrame から日付列を取得して処理します。


 

# Filter out duplicate dates and append them to a list.
# After grouping by MonthYear, pandas automatically filters off duplicate dates.
# We should do the same to match our arrays:
sorted_filtered_dates = [item for item in set(data["MonthYear"])]
# Reverse the dates from left to right:
date = sorted(sorted_filtered_dates, reverse=True)
# Convert dict to a list:
id = grouped_data["ID"].id.tolist()
# Reverse values according to the date array:
id.reverse()
 
# In order to return the appropriate object to ObjectScript so that it understands it,
# we call '%Library.DynamicArray' (it is the closest one to python and an easy-to-use type of array).
# Again, we use IRIS library inside python code:
OBJIDList = iris.cls('%Library.DynamicArray')._New()
OBJtimeList = iris.cls('%Library.DynamicArray')._New()
 
# Append all data to DynamicArray class methods Push()
for i in date:
        OBJtimeList._Push(i)
for i in ID:
        OBJIDList._Push(i)
return OBJIDList, OBJtimeList
}


 
次のステップでは、DeepSee がどのデータを取得するかを理解するように KPI 固有のメソッドを定義します。
 

// メソッドを定義します。 The method must always be %OnLoadKPI(). Otherwise, the system will not recognise it.
 
Method %OnLoadKPI() As%Status
{
 
 //Define string for the filter. Set the default to zero
 set sqlstring = 0
    //Call %filterValues method to fetch any filter data from the widget.
    if$IsObject(..%filterValues) {
        if (..%filterValues.InterSystemsMember'="")
        {
            set sqlstring=..%filterValues.%data("InterSystemsMember")
        }
    }
    //Call pandas function, pass filter value if any, and receive dynamic arrays with data.
    set sqlValue = ..MembersDF(sqlstring)
 
    //Assign each tuple to a variable.
    set idList = sqlValue.GetAt(1)
    set timeList = sqlValue.GetAt(2)
  
    //Calculate size of x-axis. It will be rows for our widget:
    set rowCount = timeList.%Size()
 
    //Since we need only one column, we assign variable to 1:
    set colCount = 1
    set ..%seriesCount=rowCount
  
    //Now, for each row, assign time value and ID value of our members:
    for rows = 1:1:..%seriesCount
    {
        set ..%seriesNames(rows)=timeList.%Get(rows-1)
       
        for col = 1:1:colCount
        {
            set ..%data(rows,"Members")=idList.%Get(rows-1)
        }
    }
    quit$$$OK


この時点で、KPI をコンパイルし、KPI データソースを使ってダッシュボードにウィジェットを作成します。

 

以上です! InterSystems IRIS の ObjectScript アプリケーションで Pandas の統合と使用のプロセスを確認できました。 このプロセスでは、データのフェッチと整形から絞り込みと表示までを 1 つの合理化された関数で行いました。 この実演では、データ分析における Pandas の効率性と力がハイライトされています。 では次に、IRIS 環境内で Pandas を実装する際の実用的な推奨事項を詳しく見て、変換による影響に関するインサイトで締めくくりましょう。
IRIS における実用的な Pandas アプリケーションの推奨事項

  • プロトタイピングから始める:

サンプルのデータセットとユーティリティを使用して Pandas での作業を始めましょう。 この方法によって、制御された使い慣れた環境で Pandas の基本と意味合いを理解できます。 プロトタイピングでは、ライブデータに関連するリスクを伴うことなく、様々な Pandas 関数とメソッドを実験できます。

  • 段階的な実装:

既存のデータプロセスに Pandas を徐々に導入しましょう。 全面的に変更するのではなく、Pandas によってデータ処理と分析が強化または単純化されるエリアを特定します。 データクリーニング集計などの単純なタスクであることも、Pandas の機能を完全に活用できるより複雑な分析であることもあります。

  •  Pandas の使用を最適化する:

大きなデータセットで作業する前に、Pandas コードを最適化しておくことが重要です。 コードの効率によって、処理時間やリソース消費量が大幅に削減されるため、大規模なデータ分析では特に重要です。 ベクトル化操作、適切なデータ型の使用、データ操作でのループの回避といった手法によって、パフォーマンスは大幅に強化されます。


まとめ

InterSystems IRIS プラットフォームの ObjectScript アプリケーションへの Pandas の統合は、データ分析の分野における著しい進歩です。 Pandas によってデータ処理、分析、および可視化に使用できる多数の強力なツールを使用できるようになり、IRIS ユーザーはこれを使用できるようになりました。 この統合は KPI の開発と分析を高速化して単純化するだけでなく、IRIS エコシステム内でのより洗練された高度なデータ分析機能への道も切り開いています。
 
Pandas を使用することで、アナリストと開発者は、その広範な機能を活用してデータからより深い洞察を得ることで、データ分析の新たな地平を探ることができます。 大規模なデータセットを効率的に処理して分析する能力に、魅力的な可視化の作成しやすさを合わせ、ユーザーはより多くの情報に基づいた意思決定を行い、これまで検出が困難であった傾向やパターンを明らかにできるようになります。
 
要約すると、InterSystems IRIS 環境への Pandas の統合は変革的なステップであり、プラットフォームの機能を強化し、増え続ける最新のデータ分析の課題と複雑さに取り組むための拡張されたツールキットをユーザーに提供することができます。

0
0 118
お知らせ Mihoko Iijima · 7月 8, 2024

開発者の皆さん、こんにちは!

次のInterSystems プログラミングコンテストの内容についてご案内します📣

🏆 InterSystems Python コンテスト 🏆

期間:2024年7月15日~2024年8月4日

賞金総額:$14,000


0
0 179
記事 Mihoko Iijima · 7月 4, 2024 9m read

これは InterSystems FAQ サイトの記事です。

PythonスクリプトファイルやPythonで記述されたIRIS内メソッドを呼び出す際、エラーが発生した場合の対応方法をご紹介します。

説明使用するコードや資料PDFは公開しています👉 test1.pyFS.Utilsクラスコードのコピー元ビデオで解説している資料PDF

Embedded Python 自習用ビデオをご用意しています(項目別にYouTubeプレイリストをご用意しています)。

各プレイリストについて詳しくはこちらをご参照ください👉【はじめてのInterSystems IRIS】Embedded Python セルフラーニングビデオシリーズ公開!

0
0 385
記事 Mihoko Iijima · 3月 11, 2024 2m read

これは InterSystems FAQ サイトの記事です。

現時点(2024年3月)では、コミュニティに掲載されているPythonライブラリ「iris-dollar-list」を利用することでIRISの$LIST()形式のデータをPythonのリストとして利用することができます。

※標準ツールではありませんがご利用いただけます。詳細はコミュニティの記事「もう1つの $ListBuild() の実装:Pythonライブラリ「iris-dollar-list」」をご参照ください。

WindowsにインストールしたIRISで使用する場合は、以下の方法で「iris-dollar-list」をインストールしてください。

※Windows以外にインストールしたIRISでは、pipコマンドを利用した通常の方法でインストールできます。

コマンドプロンプトを開き、以下実行します。(IRISをデフォルトインストールしたときのディレクトリで掲載しています)

>cd C:\InterSystems\IRIS\bin> irispip install --target C:\InterSystems\IRIS\mgr\python iris-dollar-list

実行例は以下の通りです。

1
1 180
記事 Toshihiko Minamoto · 6月 26, 2024 8m read

近頃、LLM や AI などに関する話題で非常ににぎわっています。 ベクトルデータベースもそれなりに関わっており、IRIS 以外では、世界中で多様なサポートがすでに実現されています。

なぜベクトルなのでしょうか?

  • 類似検索: ベクトルでは、データベース内で最も類似する項目やドキュメントを検索するなど、効率的な類似検索が可能です。 従来のリレーショナルデータベースは完全一致検索向けに設計されているため、画像やテキストの類似検索といったタスクには向いていません。
  • 柔軟性: ベクトル表現には汎用性があり、テキスト(Word2Vec、BERT などの埋め込み経由)や画像(ディープラーニングモデル経由)などの様々なデータタイプから導き出すことができます。
  • クロスモーダル検索: ベクトルでは、様々なデータモダリティでの検索が可能です。 たとえば、画像のベクトル表現を基に、マルチモーダルデータベースで類似する画像や関連するテキストを検索できます。

理由は他にも多数あります。

そこで、この Python コンテストでは、このサポートを実装してみることにしました。 残念ながら時間内に完成させることはできませんでしたが、その理由を以下で説明します。

完全にするには、やらなければならない主な項目がいくつかあります。

  • ベクトル化データを SQL で受け入れて格納する。以下は単純な例です(この例の「3」は次元数であり、フィールドごとに固定されており、そのフィールドのすべてのベクトルにこの正確な次元が必要です)。
    createtable items(embedding vector(3));
    insertinto items (embedding) values ('[1,2,3]');
    insertinto items (embedding) values ('[4,5,6]');
    
  • 類似関数。類似には様々なアルゴリズムがあり、インデックスを使用しない少量のデータでの単純な検索に適しています。
    -- ユークリッド距離select embedding, vector.l2_distance(embedding, '[9,8,7]') distance from items orderby distance;
    -- コサイン類似度select embedding, vector.cosine_distance(embedding, '[9,8,7]') distance from items orderby distance;
    -- 内積select embedding, -vector.inner_product(embedding, '[9,8,7]') distance from items orderby distance;
  • カスタムインデックス。大量のデータでのより高速な検索に役立ちます。インデックスには異なるアルゴリズム、上記の異なる距離関数、およびその他のオプションを使用できます。
    • HNSW
    • 転置ファイルインデックス
  • 検索は作成されたインデックスを使用し、そのアルゴリズムによってリクエストされた情報が検索されます。

ベクトルの挿入

ベクトルは、整数や浮動小数点数のほかに、符号付きや符号なしの数値の配列であることが期待されています。 IRIS では、それを単に $listbuild として格納できます。これには最適な表現があり、すでにサポートされているため、ODBC から論理への変換のみを実装する必要があります。

すると、ODBC/JDBC などの外部ドライバーを使って、または IRIS 内では ObjectScript を使って、プレーンテキストとして値を挿入できます。

  • プレーンな SQL を使用
    insertinto items (embedding) values ('[1,2,3]');
  • ObjectScript を使用
    set rs = ##class(%SQL.Statement).%ExecDirect(, "insert into test.items (embedding) values ('[1,2,3]')")
    

    set rs = ##class(%SQL.Statement).%ExecDirect(, "insert into test.items (embedding) values (?)", $listbuild(2,3,4))

  • または埋め込み SQL を使用
    &sql(insertinto test.items (embedding) values ('[1,2,3]'))
    

    set val = $listbuild(2,3,4) &sql(insertinto test.items (embedding) values (:val))

必ず $lb() として格納され、ODBC でテキスト形式で戻されます。

 
予期しない動作
DBeaver を使ってテストした際に、接続後の最初の行は正しく挿入されても、他の行は検証や変換が行われずにそのまま挿入されているのが判明しました。

その後、JDBC はデフォルトで高速挿入を使用しており、その場合、挿入されたデータを直接 globals に格納することがわかったため、その機能を手動でオフにする必要がありました。

DBeaver では、FeatureOption フィールドの optfastSelect を選択してください。

計算

ベクトルは主に、2 つのベクトルの間の距離の計算をサポートするために必要です。

コンテストにおいては、Embedded Python を使用する必要がありました。問題はここからです。Embedded Python で $lb をどのように操作するのか。%SYS.Class には ToList メソッドがありますが、Python パッケージは iris にビルトインされていないため、ObjectScript のやり方が必要となります。

ClassMethod l2DistancePy(v1 As dc.vector.type, v2 As dc.vector.type) As%Decimal(SCALE=10) [ Language = python, SqlName = l2_distance_py, SqlProc ]
{
    import iris 
    import math
vector_type = iris.cls('dc.vector.type')
v1 = iris.cls('<span class="hljs-built_in">%SYS.Python</span>').ToList(vector_type.Normalize(v1))
v2 = iris.cls('<span class="hljs-built_in">%SYS.Python</span>').ToList(vector_type.Normalize(v2))

<span class="hljs-keyword">return</span> math.sqrt(sum([(val1 - val2) ** <span class="hljs-number">2</span> <span class="hljs-keyword">for</span> val1, val2 in zip(v1, v2)]))

}

まったく正しいようには見えません。 $lb は Python でその場でリストとして、または少なくともビルトイン関数の o_list と from_list として解釈されるのが好ましいと思います。

もう 1 つの問題は、この関数を様々な方法でテストしようとしたときにありました。 Embedded Python で記述された SQL を 使用する SQL を Embedded Python から使用すると、クラッシュします。 そこで、ObjectScript の関数も追加する必要がありました。

ModuleNotFoundError: No module named 'dc'
SQL Function VECTOR.NORM_PY failed with error:  SQLCODE=-400,%msg=ERROR #5002: ObjectScript error: <OBJECT DISPATCH>%0AmBm3l0tudf^%sqlcq.USER.cls37.1 *python object not found

距離を計算するために現在実装されている関数(Python と ObjectScript の両方)

  • ユークリッド距離
    [SQL]_system@localhost:USER> select embedding, vector.l2_distance_py(embedding, '[9,8,7]') distance from items orderby distance;
    +-----------+----------------------+
    | embedding | distance             |
    +-----------+----------------------+
    | [4,5,6]   | 5.91607978309961613  |
    | [1,2,3]   | 10.77032961426900748 |
    +-----------+----------------------+
    2 rows in setTime: 0.011s
    [SQL]_system@localhost:USER> select embedding, vector.l2_distance(embedding, '[9,8,7]') distance from items orderby distance;
    +-----------+----------------------+
    | embedding | distance             |
    +-----------+----------------------+
    | [4,5,6]   | 5.916079783099616045 |
    | [1,2,3]   | 10.77032961426900807 |
    +-----------+----------------------+
    2 rows in setTime: 0.012s
  • コサイン類似度
    [SQL]_system@localhost:USER> select embedding, vector.cosine_distance(embedding, '[9,8,7]') distance from items orderby distance;
    +-----------+---------------------+
    | embedding | distance            |
    +-----------+---------------------+
    | [4,5,6]   | .034536677566264152 |
    | [1,2,3]   | .11734101007866331  |
    +-----------+---------------------+
    2 rows in setTime: 0.034s
    [SQL]_system@localhost:USER> select embedding, vector.cosine_distance_py(embedding, '[9,8,7]') distance from items orderby distance;
    +-----------+-----------------------+
    | embedding | distance              |
    +-----------+-----------------------+
    | [4,5,6]   | .03453667756626421781 |
    | [1,2,3]   | .1173410100786632659  |
    +-----------+-----------------------+
    2 rows in setTime: 0.025s
  • 内積
    [SQL]_system@localhost:USER> select embedding, vector.inner_product_py(embedding, '[9,8,7]') distance from items orderby distance;
    +-----------+----------+
    | embedding | distance |
    +-----------+----------+
    | [1,2,3]   | 46       |
    | [4,5,6]   | 118      |
    +-----------+----------+
    2 rows in setTime: 0.035s
    [SQL]_system@localhost:USER> select embedding, vector.inner_product(embedding, '[9,8,7]') distance from items orderby distance;
    +-----------+----------+
    | embedding | distance |
    +-----------+----------+
    | [1,2,3]   | 46       |
    | [4,5,6]   | 118      |
    +-----------+----------+
    2 rows in setTime: 0.032s

数学関数(加減乗除)を追加で実装しました。 InterSystems は独自の集計関数の作成をサポートしているため、 すべてのベクトルを合計したり平均を求めることが可能かもしれません。 しかし、残念ながら、InterSystems では同一の名前の使用をサポートしていないため、関数に独自の名前(およびスキーマ)を使用する必要があります。 ただし、集計関数では数値以外の結果はサポートされていません。

2 つのベクトルの合計を返す単純な vector_add 関数

集計として使用すると、0 を示し、期待されるベクトルも同様に表示されます。

インデックスの作成

残念ながら、実装中に直面した障害により、この部分は完成させられませんでした。

  • IRIS のベクトルが $lb に格納されている場合のビルトイン $lb から Python リストへの変換とその逆変換が不足しており、インデックス作成のすべてのロジックが Python で書かれていることが期待されるため、$lb からデータを取得して globals にも設定することが重要です。
  • globals のサポートの欠如
    • IRIS の $Order は方向をサポートしているため、逆方向でも使用可能ですが、Python Embedded での順序の実装にはこれが存在しないため、すべてのキーを読み取って順序を逆にするか、最後をどこかに格納する必要があります。
  • 上記の Python から呼び出される Python の SQL 関数がうまくいかないため、疑問を感じている
  • インデックス作成中、ベクトル間の距離がグラフに格納されることが期待されていたのに、global で浮動小数点数に関するバグが発生した

この作業中に見つかった Embedded Python 関連の課題を 11 件作成しました。ほとんどの時間は問題を解決するための回避策を見つけるのに費されました。 いくつかの問題は、@Guillaume Rongieriris-dollar-list というプロジェクトのお陰でなんとか解決できました。

インストール

いずれにせよ、これは引き続き提供中であり、IPM でインストールし、機能が制限されていても使用できます。

zpm "install vector"

または開発モードでは docker-compose を使用できます。

git clone https://github.com/caretdev/iris-vector.git
cd iris-vector
docker-compose up -d
0
0 173
お知らせ Mihoko Iijima · 3月 26, 2024

開発者の皆さん、こんにちは。

(2024/5/30:6月の日時、ウェビナー内容を更新しました)

InterSystems IRIS、InterSystems IRIS for Healthの新バージョン2024.1がリリースされました。

様々な機能の追加や実験的機能としての追加が行われましたが、その中から以下3種類の内容についてウェビナーを開催します!📣

✅4月23日(火)13時半~14時:IRIS 2024.1の管理用Webサーバ(PWS)廃止に備えて

YouTube公開しました👉https://youtu.be/bVwWZt1oNws?list=PLzSN_5VbNaxCeC_ibw2l-xneMCwCVf-Or

✅5月30日(木)13時半~14時:ベクトル検索機能のご紹介

YouTube公開しました👉https://youtu.be/v0G7K2et_Yk?list=PLzSN_5VbNaxB39_H2QMMEG_EsNEFc0ASz

✅6月25日(火)13時半~14時:FHIR新機能のご紹介~2024.1~

オンデマンド配信はこちら👉https://event.on24.com/wcc/r/4597704/ADA161B6446E6BA01623C875CF596FD0
(資料PDFもオンデマンド配信画面よりダウンロードいただけます)
 

2
0 208
記事 Toshihiko Minamoto · 4月 18, 2024 13m read

我々には、Redditユーザーが書いた、おいしいレシピデータセット がありますが, 情報のほとんどは投稿されたタイトルや説明といったフリーテキストです。埋め込みPythonLangchainフレームワークにあるOpenAIの大規模言語モデルの機能を使い、このデータセットを簡単にロードし、特徴を抽出、分析する方法を紹介しましょう。

データセットのロード

まず最初に、データセットをロードするかデータセットに接続する必要があります。

これを実現するにはさまざまな方法があります。たとえばCSVレコードマッパーを相互運用性プロダクションで使用したり csvgenのようなすばらしい OpenExchange アプリケーションを使用することもできます。

今回、外部テーブルを使用します。これは物理的に別の場所に保存されているデータをIRIS SQLで統合する非常に便利な機能です。

まずは外部サーバ(Foreign Server)を作成します。

CREATE FOREIGN SERVER dataset FOREIGN DATA WRAPPER CSV HOST '/app/data/'

その上でCSVファイルに接続する外部テーブルを作成します。

CREATE FOREIGN TABLE dataset.Recipes (
  CREATEDDATE DATE,
  NUMCOMMENTS INTEGER,
  TITLE VARCHAR,
  USERNAME VARCHAR,
  COMMENT VARCHAR,
  NUMCHAR INTEGER
) SERVER dataset FILE 'Recipes.csv' USING
{
  "from": {
    "file": {
       "skip": 1
    }
  }
}

以上です。すぐに「dataset.Recipes」にSQLクエリを実行できます。 image

## どんなデータが必要?

データセットは興味深く、直ぐに処理したいと思うのですが、調理のレシピを決めたいのであれば、分析に使える情報がもう少し必要です。 2つの永続化クラス(テーブル)を使用します。

  • yummy.data.Recipe抽出分析したいレシピのタイトルと説明、他のプロパティが入ったクラス (例: スコア、難易度、材料、調理タイプ、準備時間)
  • yummy.data.RecipeHistory レシピのログを取るためのシンプルなクラス

これで 「yummy.data*」 テーブルにデータセットの内容をロードすることができます。

do ##class(yummy.Utils).LoadDataset()

一見良さそうに見えますが、スコア、難易度、材料、準備時間、調理時間フィールドのデータをどのように生成するのかを見つける必要があります。

## レシピの分析 各レシピのタイトルと説明を処理します

  • 難易度, 材料, 調理タイプなどの抽出
  • 何を作りたいか決められるよう、基準に基づいて独自のスコアを構築

以下を使用します

  • より多くの分析を構築したい場合に再利用できる一般的な分析構造

LLM(大規模言語モデル)は自然言語を処理するための本当に素晴らしいツールです。

LangChainはPythonで動くようになっているので、Embedded Pythonを使ってInterSystems IRISで直接使うことができます。 LangChain is ready to work in Python, so we can use it directly in InterSystems IRIS using Embedded Python.

完全な SimpleOpenAI クラスは以下のようになります。

/// レシピ向けのシンプルな OpenAI 分析
Class yummy.analysis.SimpleOpenAI Extends Analysis
{

Property CuisineType As %String;

Property PreparationTime As %Integer;

Property Difficulty As %String;

Property Ingredients As %String;

/// 実行
/// ターミナルから実行できます。
/// set a = ##class(yummy.analysis.SimpleOpenAI).%New(##class(yummy.data.Recipe).%OpenId(8))
/// do a.Run()
/// zwrite a
Method Run()
{
    try {
        do ..RunPythonAnalysis()

        set reasons = ""

        // 好きな調理タイプ
        if "spanish,french,portuguese,italian,korean,japanese"[..CuisineType {
            set ..Score = ..Score + 2
            set reasons = reasons_$lb("It seems to be a "_..CuisineType_" recipe!")
        }

        // 丸一日調理に費やしたくない :)
        if (+..PreparationTime < 120) {
            set ..Score = ..Score + 1
            set reasons = reasons_$lb("You don't need too much time to prepare it") 
        }
        
        // 好きな材料ボーナス
        set favIngredients = $listbuild("kimchi", "truffle", "squid")
        for i=1:1:$listlength(favIngredients) {
            set favIngred = $listget(favIngredients, i)
            if ..Ingredients[favIngred {
                set ..Score = ..Score + 1
                set reasons = reasons_$lb("Favourite ingredient found: "_favIngred)
            }
        }

        set ..Reason = $listtostring(reasons, ". ")

    } catch ex {
        throw ex
    }
}

/// 分析結果でレシピを更新する
Method UpdateRecipe()
{
    try {
        // 親クラスの処理を先に呼び出す
        do ##super()

        // 個別のOpenAI 解析結果を追加
        set ..Recipe.Ingredients = ..Ingredients
        set ..Recipe.PreparationTime = ..PreparationTime
        set ..Recipe.Difficulty = ..Difficulty
        set ..Recipe.CuisineType = ..CuisineType

    } catch ex {
        throw ex
    }
}

/// 埋め込み Python + Langchain で分析を実行
/// do ##class(yummy.analysis.SimpleOpenAI).%New(##class(yummy.data.Recipe).%OpenId(8)).RunPythonAnalysis(1)
Method RunPythonAnalysis(debug As %Boolean = 0) [ Language = python ]
{
    # load OpenAI APIKEY from env
    import os
    from dotenv import load_dotenv, find_dotenv
    _ = load_dotenv('/app/.env')

    # account for deprecation of LLM model
    import datetime
    current_date = datetime.datetime.now().date()
    # date after which the model should be set to "gpt-3.5-turbo"
    target_date = datetime.date(2024, 6, 12)
    # set the model depending on the current date
    if current_date > target_date:
        llm_model = "gpt-3.5-turbo"
    else:
        llm_model = "gpt-3.5-turbo-0301"

    from langchain.chat_models import ChatOpenAI
    from langchain.prompts import ChatPromptTemplate
    from langchain.chains import LLMChain

    from langchain.output_parsers import ResponseSchema
    from langchain.output_parsers import StructuredOutputParser

    # init llm model
    llm = ChatOpenAI(temperature=0.0, model=llm_model)

    # prepare the responses we need
    cuisine_type_schema = ResponseSchema(
        name="cuisine_type",
        description="What is the cuisine type for the recipe? \
                     Answer in 1 word max in lowercase"
    )
    preparation_time_schema = ResponseSchema(
        name="preparation_time",
        description="How much time in minutes do I need to prepare the recipe?\
                     Anwer with an integer number, or null if unknown",
        type="integer",
    )
    difficulty_schema = ResponseSchema(
        name="difficulty",
        description="How difficult is this recipe?\
                     Answer with one of these values: easy, normal, hard, very-hard"
    )
    ingredients_schema = ResponseSchema(
        name="ingredients",
        description="Give me a comma separated list of ingredients in lowercase or empty if unknown"
    )
    response_schemas = [cuisine_type_schema, preparation_time_schema, difficulty_schema, ingredients_schema]

    # get format instructions from responses
    output_parser = StructuredOutputParser.from_response_schemas(response_schemas)
    format_instructions = output_parser.get_format_instructions()
    
    analysis_template = """\
    Interprete and evaluate a recipe which title is: {title}
    and the description is: {description}
    
    {format_instructions}
    """
    prompt = ChatPromptTemplate.from_template(template=analysis_template)

    messages = prompt.format_messages(title=self.Recipe.Title, description=self.Recipe.Description, format_instructions=format_instructions)
    response = llm(messages)

    if debug:
        print("======ACTUAL PROMPT")
        print(messages[0].content)
        print("======RESPONSE")
        print(response.content)

    # populate analysis with results
    output_dict = output_parser.parse(response.content)
    self.CuisineType = output_dict['cuisine_type']
    self.Difficulty = output_dict['difficulty']
    self.Ingredients = output_dict['ingredients']
    if type(output_dict['preparation_time']) == int:
        self.PreparationTime = output_dict['preparation_time']

    return 1
}

}

「RunPythonAnalysis」メソッドがOpenAIが詰め込むところです :)  ターミナルから直接実行してレシピを受け取れます。

do ##class(yummy.analysis.SimpleOpenAI).%New(##class(yummy.data.Recipe).%OpenId(12)).RunPythonAnalysis(1)

以下のような出力を受け取れます。

USER>do ##class(yummy.analysis.SimpleOpenAI).%New(##class(yummy.data.Recipe).%OpenId(12)).RunPythonAnalysis(1)
======実際の課題
                    レシピタイトルを解釈、評価: 巻きずし - アラスカロール
                    説明: 寿司がたべたいのに巻きすがない? 代わりに簡単なバージョンを試してみてください。超簡単なのに、同じようにおいしい!
[Video Recipe](https://www.youtube.com/watch?v=1LJPS1lOHSM)
# 材料
提供量:  \~サンドイッチ5枚分
* 米1カップ
* 水 3/4 カップ + 大さじ 2 1/2
* 昆布 小口切り 1枚
* 米酢 大さじ2
* さとう 大さじ1
* 塩 小さじ1
* アボカド 2個
* カニカマ 6個
* 和風マヨ 大さじ2
* サーモン 1/2 ポンド  
# レシピ     
* 酢飯1合をボウルに入れ、2回以上、または水が透明になるまで米を洗う。炊飯器に米を移し、昆布の小口切り1枚と水3/4カップ+大さじ2と1/2杯を加える。炊飯器の指示に従って炊く。
* 米酢大さじ2、砂糖大さじ1、塩小さじ1を中くらいのボウルに入れる。全体がよく混ざるまで混ぜる。
* 炊き上がったら昆布を取り除き、すぐに酢を入れた中ボウルに米をすべてすくい入れ、飯ベラを使ってよく混ぜる。米をつぶさないように、切るように混ぜること。炊きあがったら、キッチンタオルをかけて室温まで冷ます。
* アボカド1個の上部を切り、アボカドの中央に切り込みを入れ、ナイフに沿って回転させる。次にアボカドを半分ずつ取り、ひねる。その後、ピットのある側を取り、慎重にピットに切り込みを入れ、ひねって取り除く。その後、手で皮をむく。この手順をもう片方のアボカドでも繰り返す。作業スペースを確保するため、作業台を片付けるのを忘れずに。次に、アボカドを下向きに置き、薄くスライスする。スライスしたら、ゆっくりと広げていく。それが終わったら、脇に置いておく。
* カニカマから包みをはずす。カニカマを縦にむいていく。すべてのカニカマを剥いたら、横に回転させながら細かく刻み、和風マヨ(大さじ2)とともにボウルに入れ、全体がよく混ざるまで混ぜる。
* 鋭利なナイフを斜めに入れ、木目に逆らって薄くスライスする。切り口の厚さは好みによる。ただ、すべてのピースが同じような厚さになるようにする。
* 海苔巻きラップを手に取る。キッチンバサミを使い、海苔巻きラップの半分の位置から切り始め、ラップの中心を少し過ぎるまで切る。ラップを垂直に回転させ、作り始める。すし飯を握るために、手に水をつけておく。酢飯を手に取り、海苔巻きの左上の四辺に広げる。次に、右上にサーモンを2切れ並べる。右下にアボカドを2切れのせる。最後に左下にカニサラダを小さじ2杯ほどのせる。次に、右上の四つ角を右下の四つ角に折り込み、さらに左下の四つ角に折り込む。最後に、左上の四つ角をサンドイッチの残りの部分に折り込む。その後、ラップを上に置き、半分に切って、生姜とわさびを2、3枚添えれば出来上がり。
                    
                    出力は、先頭と末尾の"``json "と"``"を含む、以下のスキーマでフォーマットされたマークダウンのコードスニペットでなければなりません:
json
{
        "cuisine_type": string  // レシピの調理タイプは?                                  小文字の1単語で回答
        "preparation_time": integer  // レシピの準備に必要な時間(分)は? 整数で回答(不明な場合はnull)
        "difficulty": string  // レシピの難易度は?                               「容易」「標準」「難しい」「とても難しい」のうちから1つを回答
        "ingredients": string  // 小文字のカンマ区切りの材料リスト、不明な場合は空
}

                    
======応答
json
{
        "cuisine_type": "japanese",
        "preparation_time": 30,
        "difficulty": "easy",
        "ingredients": "sushi rice, water, konbu, rice vinegar, sugar, salt, avocado, imitation crab sticks, japanese mayo, salmon"
}

良さそうです。OpenAIのプロンプトは有用な情報を返してくれるようです。ターミナルから分析クラス全体を実行してみましょう:

set a = ##class(yummy.analysis.SimpleOpenAI).%New(##class(yummy.data.Recipe).%OpenId(12))
do a.Run()
zwrite a
USER>zwrite a
a=37@yummy.analysis.SimpleOpenAI  ; <OREF>
+----------------- general information ---------------
|      oref value: 37
|      class name: yummy.analysis.SimpleOpenAI
| reference count: 2
+----------------- attribute values ------------------
|        CuisineType = "japanese"
|         Difficulty = "easy"
|        Ingredients = "sushi rice, water, konbu, rice vinegar, sugar, salt, avocado, imitation crab sticks, japanese mayo, salmon"
|    PreparationTime = 30
|             Reason = "It seems to be a japanese recipe!. You don't need too much time to prepare it"
|              Score = 3
+----------------- swizzled references ---------------
|           i%Recipe = ""
|           r%Recipe = "30@yummy.data.Recipe"
+-----------------------------------------------------

## 全レシピを解析する!

当然、読み込んだすべてのレシピで分析を実行したいでしょう。 この方法でレシピ ID の範囲を分析することができます

USER>do ##class(yummy.Utils).AnalyzeRange(1,10)
> Recipe 1 (1.755185s)
> Recipe 2 (2.559526s)
> Recipe 3 (1.556895s)
> Recipe 4 (1.720246s)
> Recipe 5 (1.689123s)
> Recipe 6 (2.404745s)
> Recipe 7 (1.538208s)
> Recipe 8 (1.33001s)
> Recipe 9 (1.49972s)
> Recipe 10 (1.425612s)

その後、レシピテーブルを再度表示させ、結果をチェックします。

select * from yummy_data.Recipe

image

どんぐりカボチャのピザか、豚肉入り韓国風豆腐キムチを試してみます:) いずれにせよ、家で再確認する必要がありますね :)

最後に

サンプルソースは全て https://github.com/isc-afuentes/recipe-inspector にあります。

この簡単な例で、InterSystems IRIS で LLM テクニックを使用して機能を追加したり、データの一部を分析する方法を学びました。

これを起点に以下のことが考えられます

  • InterSystems BIを使い、キューブやダッシュボードでデータの検索やナビゲートをおこなう。
  • Webアプリを作成し、UIを提供する(例:Angular)RESTForms2のようなパッケージを活用することで、永続クラスへのREST APIを自動的に生成することができます。 *レシピが好きか嫌いかを保存し、新しいレシピが好きかどうかを判断するのはいかがでしょうか。IntegratedMLアプローチ、あるいはLLMアプローチでいくつかの例データを提供し、RAG(Retrieval Augmented Generation)ユースケースを構築してみるのも良いでしょう。

他にどんなことが試せそうでしょうか?ご意見をお聞かせください!

0
0 458