5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Serenaやってみた!StreamlitとStrands Agentsを組み合わせてオレオレコーディングエージェントを作る

Last updated at Posted at 2025-08-04

追記:末尾に少しブラッシュアップしたStreamlitアプリのコードを置いてます

Serenaってのが流行ってるみたいです。

StreamliとStrands Agentsを組み合わせて、遊んでみました


コーディングエージェントを作成する

ディレクトリーを作成します。

mkdir serena-app
cd serena-app

uvでプロジェクトを初期化します。

uv init --name serena-app --python 3.12

ライブラリーを追加します。

uv add strands-agents streamlit

main.pyを実装してきます。

まずは枠を作ります。

main.py
import asyncio

import streamlit as st
from mcp import StdioServerParameters, stdio_client
from strands.agent import Agent
from strands.models import BedrockModel
from strands.tools.mcp import MCPClient


async def main():
    st.title("Strands Agents with Serena")

asyncio.run(main=main())

main()関数の中を埋めていきます。

サイドバーに設定を用意します。

main.py
async def main():
    st.title("Strands Agents with Serena")
    
    with st.sidebar:
        st.subheader("設定")
        work_dir = st.text_input("作業ディレクトリ", value="./workspace")
        model_id = st.text_input("モデルID", value="apac.amazon.nova-pro-v1:0")
        region_name = st.text_input("リージョン", value="ap-northeast-1")

    if not work_dir:
        return

メッセージを表示するところを追加します。

Tool Useがあったりするので、textがあるものだけを出力するように工夫してます。

    if "messages" not in st.session_state:
        st.session_state["messages"] = []

    for message in st.session_state["messages"]:
        role = message["role"]

        text_content = [
            content["text"] for content in message["content"] if "text" in content
        ]
        if len(text_content) > 0:
            with st.chat_message(role):
                for text in text_content:
                    st.write(text)

いよいよここから、プロンプト入力後の処理です。
まずはプロンプトをそのまま出力します。

main.py
    if prompt := st.chat_input():
        with st.chat_message("user"):
            st.write(prompt)

ここでSerenaの登場です。SerenaはMCPサーバーとして呼び出せますのでMCPクライアントを作成します。

パラメーターが色々ありますが、公式ドキュメントを参照ください🙇

main.py
        mcp_client = MCPClient(
            lambda: stdio_client(
                StdioServerParameters(
                    command="uvx",
                    args=[
                        "--from",
                        "git+https://github.com/oraios/serena",
                        "serena",
                        "start-mcp-server",
                        "--project",
                        work_dir,
                        "--context",
                        "ide-assistant",
                    ],
                )
            )
        )

このMCPサーバーのツールをセットしたAgentを生成します。

messages=st.session_state["messages"]とすると、エージェント呼び出し時のユーザーメッセージから最後のアシスタントメッセージまでが、st.session_state["messages"]にいい感じにセットされることを発見しました!

main.py
        with mcp_client:
            tools = mcp_client.list_tools_sync()
            model = BedrockModel(
                model_id=model_id,
                region_name=region_name,
            )
            agent = Agent(
                model=model,
                system_prompt="あなたは優秀なエージェントです。ユーザーとの会話は日本語で行います。",
                messages=st.session_state[
                    "messages"
                ],  # agent呼び出し後、自動でユーザーとアシスタントのメッセージがappendされる
                tools=tools,
                callback_handler=None,
            )

ストリームで呼び出して、レスポンスからツール呼び出しとアシスタントメッセージを出力します。

main.py
            agent_stream = agent.stream_async(prompt)

            async for event in agent_stream:
                match event:
                    case {"current_tool_use": current_tool_use}:
                        with st.expander("ツール呼び出し", expanded=False):
                            st.write(current_tool_use)
                    case {"message": message}:
                        if "role" in message and message["role"] == "assistant":
                            with st.chat_message("assistant"):
                                for content in message["content"]:
                                    if "text" in content:
                                        st.write(content["text"])

Claude Sonnet 4を使った場合に、current_tool_useブロックがストリームで返ってくるので見た目は変な感じになります。。

できました!

main.py全文(折りたたまれてます)
main.py
import asyncio

import streamlit as st
from mcp import StdioServerParameters, stdio_client
from strands.agent import Agent
from strands.models import BedrockModel
from strands.tools.mcp import MCPClient


async def main():
    st.title("Strands Agents with Serena")

    with st.sidebar:
        st.subheader("設定")
        work_dir = st.text_input("作業ディレクトリ")
        model_id = st.text_input("モデルID", value="apac.anthropic.claude-3-7-sonnet-20250219-v1:0")
        region_name = st.text_input("リージョン", value="ap-northeast-1")

    if not work_dir:
        return

    if "messages" not in st.session_state:
        st.session_state["messages"] = []

    for message in st.session_state["messages"]:
        role = message["role"]

        text_content = [
            content["text"] for content in message["content"] if "text" in content
        ]
        if len(text_content) > 0:
            with st.chat_message(role):
                for text in text_content:
                    st.write(text)

    if prompt := st.chat_input():
        with st.chat_message("user"):
            st.write(prompt)

        mcp_client = MCPClient(
            lambda: stdio_client(
                StdioServerParameters(
                    command="uvx",
                    args=[
                        "--from",
                        "git+https://github.com/oraios/serena",
                        "serena",
                        "start-mcp-server",
                        "--project",
                        work_dir,
                        "--context",
                        "ide-assistant",
                    ],
                )
            )
        )

        with mcp_client:
            tools = mcp_client.list_tools_sync()
            model = BedrockModel(
                model_id=model_id,
                region_name=region_name,
            )
            agent = Agent(
                model=model,
                system_prompt="あなたは優秀なエージェントです。ユーザーとの会話は日本語で行います。",
                messages=st.session_state[
                    "messages"
                ],  # agent呼び出し後、自動でユーザーとアシスタントのメッセージがappendされる
                tools=tools,
                callback_handler=None,
            )

            agent_stream = agent.stream_async(prompt)

            async for event in agent_stream:
                match event:
                    case {"current_tool_use": current_tool_use}:
                        with st.expander("ツール呼び出し", expanded=False):
                            st.write(current_tool_use)
                    case {"message": message}:
                        if "role" in message and message["role"] == "assistant":
                            with st.chat_message("assistant"):
                                for content in message["content"]:
                                    if "text" in content:
                                        st.write(content["text"])


asyncio.run(main=main())

Serenaで扱うプロジェクトのセットアップ

自分なりの解釈でやってみたのですが、合ってるかわかりません。。(なんか間違ってる気がする。。)

ディレクトリーを作成します。先ほどのserena-appのサブディレクトリとして作成します。

mkdir workspace
cd workspace

対象のソースとして、「Generative AI Use Cases (GenU)」のソースを取得します。

git clone https://github.com/aws-samples/generative-ai-use-cases.git

Serenaのプロジェクトとして初期化します。

workspace
uvx --from git+https://github.com/oraios/serena serena project generate-yml

実行すると、.serena/project.ymlが生成されます。

コメントを除くとこんな内容です。languageは配下のソースから認識されます。

.serena/project.yml
language: typescript
ignore_all_files_in_gitignore: true
ignored_paths: []
read_only: false
excluded_tools: []
initial_prompt: ""
project_name: "workspace"

インデックスを作成します。

uvx --from git+https://github.com/oraios/serena serena project index

cacheが作成されます。

.serena/
├── cache
│   └── typescript
│       └── document_symbols_cache_v23-06-25.pkl
└── project.yml

3 directories, 2 files

オレオレコーディングエージェントを起動する

serena-appディレクトリに移動し、Streamlitを起動します。

cd ..
uv run streamlit run main.py

ブラウザでhttp://localhost:8501/にアクセスします。

どんなツールが使えるか聞いてみました。すごくたくさんのツールがあるようです。

image.png

コーディングっぽいことを聞いてみましょう。

Bedrockの呼び出しをしているソースの場所を特定して

search_for_patternツールを使用して、関連するソースを取得しました。

image.png

Bedrockを呼び出しているコードを特定してください。

(使い方が悪いのか、Nova Proでは無限ループっぽい動きをしたのでClaude Sonnet 4で実施しました)

image.png

対応している生成AIモデルのモデルIDをすべて教えて

image.png

新しく「us.anthropic.claude-opus-5-20261231-v1:0」が追加されたので、「us.anthropic.claude-opus-4-20250514-v1:0」が使えるところに追加してほしいです。

合ってるかわからないですが、修正はガッツリ行われました!

image.png

イマイチSerenaの凄さがわからないままですが、なんかできましたw


追記

こっちのほうがスマートな気がします。

import asyncio

import streamlit as st
from mcp import StdioServerParameters, stdio_client
from strands.agent import Agent
from strands.models import BedrockModel
from strands.tools.mcp import MCPClient


def write_message(message):
    with st.chat_message(message["role"]):
        for content in message["content"]:
            match content:
                case {"text": text}:
                    st.write(text)
                    pass
                case {"toolUse": toolUse}:
                    with st.expander(
                        f"toolUse: {toolUse['name']}",
                        expanded=False,
                    ):
                        st.write(toolUse)

                case {"toolResult": toolResult}:
                    with st.expander("toolResult", expanded=False):
                        st.write(toolResult)


async def main():
    st.title("Strands Agents with Serena")

    with st.sidebar:
        st.subheader("設定")
        work_dir = st.text_input("作業ディレクトリ", value="./workspace")
        model_id = st.text_input(
            "モデルID",
            value="apac.amazon.nova-pro-v1:0",
            # "モデルID", value="apac.anthropic.claude-sonnet-4-20250514-v1:0"
        )
        region_name = st.text_input("リージョン", value="ap-northeast-1")

    if not work_dir:
        return

    if "messages" not in st.session_state:
        st.session_state["messages"] = []

    for message in st.session_state["messages"]:
        write_message(message)

    if prompt := st.chat_input():
        with st.chat_message("user"):
            st.write(prompt)

        mcp_client = MCPClient(
            lambda: stdio_client(
                StdioServerParameters(
                    command="uvx",
                    args=[
                        "--from",
                        "git+https://github.com/oraios/serena",
                        "serena",
                        "start-mcp-server",
                        "--project",
                        work_dir,
                        "--context",
                        "ide-assistant",
                    ],
                )
            )
        )

        with mcp_client:
            tools = mcp_client.list_tools_sync()
            model = BedrockModel(
                model_id=model_id,
                region_name=region_name,
            )
            agent = Agent(
                model=model,
                system_prompt="あなたは優秀なエージェントです。ユーザーとの会話は日本語で行います。",
                messages=st.session_state[
                    "messages"
                ],  # agent呼び出し後、自動でユーザーとアシスタントのメッセージがappendされる
                tools=tools,
                callback_handler=None,
            )

            agent_stream = agent.stream_async(prompt)

            async for event in agent_stream:
                match event:
                    case {"message": message}:
                        write_message(message)


asyncio.run(main=main())
5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?