NAME: splunk-otel-collector
LAST DEPLOYED: Fri Dec 20 01:01:43 2024NAMESPACE: default
STATUS: deployed
REVISION: 1TEST SUITE: None
NOTES:
Splunk OpenTelemetry Collector is installed and configured to send data to Splunk Observability realm us1.
Collector が動作していることを確認する
次のコマンドで Collector が動作しているかどうかを確認できます:
kubectl get pods
NAME READY STATUS RESTARTS AGE
splunk-otel-collector-agent-dkn88 1/1 Running 0 53s
splunk-otel-collector-agent-ksmh4 1/1 Running 0 53s
splunk-otel-collector-agent-lc2lf 1/1 Running 0 53s
splunk-otel-collector-k8s-cluster-receiver-dbf64995b-xgm9b 1/1 Running 0 53s
@app.route("/travel/plan",methods=["POST"])defplan():data=request.get_json()origin=data.get("origin","Seattle")destination=data.get("destination","Paris")user_request=data.get("user_request",f"Planning a week-long trip from {origin} to {destination}. ""Looking for boutique hotel, flights and unique experiences.",)travellers=int(data.get("travellers",2))result=plan_travel_internal(origin=origin,destination=destination,user_request=user_request,travellers=travellers)returnjsonify(result),200
この流れを分かりやすく説明すると、次のようになります
Flask がリクエストを受信します
plan_travel_internal() がワークフローの状態を構築します
LangGraph がノードを実行します
各ノードが状態を更新します
最終的な旅程が JSON として返されます
知識チェック
この API フローにおいて、LangGraph のワークフローは実際にどこで実行が開始されますか?
このアプリにおける LangGraph のノードは、state を受け取り、更新された state を返す Python 関数です。
例えば、flight specialist は以下のようになります:
defflight_specialist_node(state:PlannerState)->PlannerState:llm=_create_llm("flight_specialist",temperature=0.4,session_id=state["session_id"])step=(f"Find an appealing flight from {state['origin']} to {state['destination']} "f"departing {state['departure']} for {state['travellers']} travellers.")messages=[SystemMessage(content="You are a flight booking specialist. Provide concise options."),HumanMessage(content=step),]result=llm.invoke(messages)state["flight_summary"]=result.contentstate["messages"].append(result)state["current_agent"]="hotel_specialist"returnstate
これは一般的なノードパターンを示しています:
LLM を作成またはアクセスする
構造化された state からプロンプトを構築する
モデルを呼び出す
結果を state に保存する
次のノードを設定する
hotel ノードと activity ノードも同じ構造に従っているため、ワークフローの説明が容易です。
messages=[SystemMessage(content="You are a flight booking specialist. Provide concise options."),HumanMessage(content=step),]result=llm.invoke(messages)
System message: AI の動作に関するルールとコンテキストを設定します。インタラクション全体を通じてモデルがどのように応答すべきかを導く指示、制約、トーン、および目標を定義します。
Human message: ユーザーからの入力です。AI が応答すべき質問、リクエスト、または情報を含みます。
AI message: モデルの応答です。システムの指示とユーザーの入力に基づいて、アシスタントが生成した出力を表します。
4.7 LLMの作成
LLMの作成
LLM自体はここで作成されます
def_create_llm(agent_name:str,*,temperature:float,session_id:str)->AzureChatOpenAI:azure_deployment_name=os.getenv("AZURE_OPENAI_DEPLOYMENT_NAME")azure_openai_api_version=os.getenv("AZURE_OPENAI_API_VERSION")returnAzureChatOpenAI(azure_deployment=azure_deployment_name,openai_api_version=azure_openai_api_version,temperature=temperature,model_name=azure_deployment_name,# AZURE_OPENAI_API_KEY and AZURE_OPENAI_ENDPOINT environment variables will be used to connect to the LLM)
def_create_llm(agent_name:str,*,temperature:float,session_id:str)->ChatOpenAI:model_name=os.getenv("OPENAI_MODEL_NAME","gpt-4o-mini")returnChatOpenAI(model=model_name,temperature=temperature,# Uses OPENAI_API_KEY automatically from environment)
4.8 Decomposition Pattern
シンセサイザーが分解パターンを示します
最後のノードは、各専門エージェントの出力を1つの回答にまとめます。
defplan_synthesizer_node(state:PlannerState)->PlannerState:llm=_create_llm("plan_synthesizer",temperature=0.3,session_id=state["session_id"])content=json.dumps({"flight":state["flight_summary"],"hotel":state["hotel_summary"],"activities":state["activities_summary"],},indent=2,)response=llm.invoke([SystemMessage(content="You are the travel plan synthesiser. Combine the specialist insights into a concise, structured itinerary."),HumanMessage(content=(f"Traveller request: {state['user_request']}\n\n"f"Origin: {state['origin']} | Destination: {state['destination']}\n"f"Dates: {state['departure']} to {state['return_date']}\n\n"f"Specialist summaries:\n{content}")),])state["final_itinerary"]=response.contentstate["messages"].append(response)state["current_agent"]="completed"returnstate
curl http://localhost:8080/travel/plan \
-H "Content-Type: application/json"\
-d '{
"origin": "Seattle",
"destination": "Tokyo",
"user_request": "We are planning a week-long trip to Seattle from Tokyo. Looking for boutique hotel, business-class flights and unique experiences.",
"travelers": 2
}'
{"activities_summary":"Sure! Here are signature activities for a week in Tokyo:\n\n1. Day 1: Explore Asakusa and Senso-ji Temple, then stroll Nakamise Shopping Street.\n2. Day 2: Visit Tsukiji Outer Market for fresh sushi breakfast, then tour Ginza for upscale shopping.\n3. Day 3: Spend the day in Shibuya\u2014cross the famous scramble, visit Hachiko statue, and shop in trendy boutiques.\n4. Day 4: Explore Harajuku\u2019s Takeshita Street and Meiji Shrine, followed by Omotesando\u2019s stylish cafes.\n5. Day 5: Discover Akihabara\u2019s electronics and anime culture, with a visit to a themed caf\u00e9.\n6. Day 6: Take a day trip to Odaiba for teamLab Borderless digital art museum and waterfront views.\n7. Day 7: Relax in Ueno Park, visit museums, and shop at Ameya-Yokocho market.\n\nWould you like hotel or dining recommendations as well?","agent_steps":[{"agent":"coordinator","status":"completed"},{"agent":"flight_specialist","status":"completed"},{"agent":"hotel_specialist","status":"completed"}
以下のコマンドを使用して、アプリケーション Pod のステータスが Running であることを確認します:
kubectl get pods -n travel-agent
NAME READY STATUS RESTARTS AGE
travel-planner-langchain-68977dc5c4-4w7p9 1/1 Running 0 41s
Kubernetes でのアプリケーションのテスト
以下のコマンドを実行してアプリケーションをテストします:
curl http://travel-planner.localhost/travel/plan \
-H "Content-Type: application/json"\
-d '{
"origin": "Seattle",
"destination": "Tokyo",
"user_request": "We are planning a week-long trip to Seattle from Tokyo. Looking for boutique hotel, business-class flights and unique experiences.",
"travelers": 2
}'
{"activities_summary":"Sure! Here are signature activities for a week in Tokyo:\n\n1. Day 1: Explore Asakusa and Senso-ji Temple, then stroll Nakamise Shopping Street.\n2. Day 2: Visit Tsukiji Outer Market for fresh sushi breakfast, then tour Ginza for upscale shopping.\n3. Day 3: Spend the day in Shibuya\u2014cross the famous scramble, visit Hachiko statue, and shop in trendy boutiques.\n4. Day 4: Explore Harajuku\u2019s Takeshita Street and Meiji Shrine, followed by Omotesando\u2019s stylish cafes.\n5. Day 5: Discover Akihabara\u2019s electronics and anime culture, with a visit to a themed caf\u00e9.\n6. Day 6: Take a day trip to Odaiba for teamLab Borderless digital art museum and waterfront views.\n7. Day 7: Relax in Ueno Park, visit museums, and shop at Ameya-Yokocho market.\n\nWould you like hotel or dining recommendations as well?","agent_steps":[{"agent":"coordinator","status":"completed"},{"agent":"flight_specialist","status":"completed"},{"agent":"hotel_specialist","status":"completed"}
つまり、現在のアプリケーションは台本のある劇のようなものです。すべてのセリフとアクションがコードに書かれています。LLM を呼び出す際、特定のセリフを読むよう依頼しているだけです。LLM が自ら判断を行っていないため、Observability for AI の計装はそれを自律的なエージェントとして認識しません。
# Begin: Tool Definitions@tooldefmock_search_flights(origin:str,destination:str,departure:str)->str:"""Return mock flight options for a given origin/destination pair."""# create a local random.Random instanceseed=hash((origin,destination,departure))%(2**32)rng=random.Random(seed)airline=rng.choice(["SkyLine","AeroJet","CloudNine"])fare=rng.randint(700,1250)return(f"Top choice: {airline} non-stop service {origin}->{destination}, "f"depart {departure} 09:15, arrive {departure} 17:05. "f"Premium economy fare ${fare} return.")@tooldefmock_search_hotels(destination:str,check_in:str,check_out:str)->str:"""Return mock hotel recommendation for the stay."""seed=hash((destination,check_in,check_out))%(2**32)rng=random.Random(seed)name=rng.choice(["Grand Meridian","Hotel Lumière","The Atlas"])rate=rng.randint(240,410)return(f"{name} near the historic centre. Boutique suites, rooftop bar, "f"average nightly rate ${rate} including breakfast.")@tooldefmock_search_activities(destination:str)->str:"""Return a short list of signature activities for the destination."""data=DESTINATIONS.get(destination.lower(),DESTINATIONS["paris"])bullets="\n".join(f"- {item}"foritemindata["highlights"])returnf"Signature experiences in {destination.title()}:\n{bullets}"# End: Tool Definitions
ヒントvi エディタで大量の行をまとめて削除するには、Shift + v を押して Visual Line モードにし、下矢印キーで削除したい行をすべて選択してから、d を押して
選択した行を削除します。
defcoordinator_node(state:PlannerState)->PlannerState:llm=_create_llm("coordinator",temperature=0.2,session_id=state["session_id"])agent=_create_react_agent(llm,tools=[]).with_config({"run_name":"coordinator","tags":["agent","agent:coordinator"],"metadata":{"agent_name":"coordinator","session_id":state["session_id"],},})system_message=SystemMessage(content=("You are the lead travel coordinator. Extract the key details from the ""traveller's request and describe the plan for the specialist agents."))result=agent.invoke({"messages":[system_message]+list(state["messages"])})final_message=result["messages"][-1]state["messages"].append(final_messageifisinstance(final_message,BaseMessage)elseAIMessage(content=str(final_message)))state["current_agent"]="flight_specialist"returnstatedefflight_specialist_node(state:PlannerState)->PlannerState:llm=_create_llm("flight_specialist",temperature=0.4,session_id=state["session_id"])agent=_create_react_agent(llm,tools=[mock_search_flights]).with_config({"run_name":"flight_specialist","tags":["agent","agent:flight_specialist"],"metadata":{"agent_name":"flight_specialist","session_id":state["session_id"],},})step=(f"Find an appealing flight from {state['origin']} to {state['destination']} "f"departing {state['departure']} for {state['travellers']} travellers.")# IMPORTANT: pass a proper list of messages (not stringified)messages=[SystemMessage(content="You are a flight booking specialist. Provide concise options."),HumanMessage(content=step),]result=agent.invoke({"messages":messages})final_message=result["messages"][-1]state["flight_summary"]=final_message.contentifisinstance(final_message,BaseMessage)elsestr(final_message)state["messages"].append(final_messageifisinstance(final_message,BaseMessage)elseAIMessage(content=str(final_message)))state["current_agent"]="hotel_specialist"returnstatedefhotel_specialist_node(state:PlannerState)->PlannerState:llm=_create_llm("hotel_specialist",temperature=0.5,session_id=state["session_id"])agent=_create_react_agent(llm,tools=[mock_search_hotels]).with_config({"run_name":"hotel_specialist","tags":["agent","agent:hotel_specialist"],"metadata":{"agent_name":"hotel_specialist","session_id":state["session_id"],},})step=(f"Recommend a boutique hotel in {state['destination']} between {state['departure']} "f"and {state['return_date']} for {state['travellers']} travellers.")# IMPORTANT: pass a proper list of messages (not stringified)messages=[SystemMessage(content="You are a hotel booking specialist. Provide concise options."),HumanMessage(content=step),]result=agent.invoke({"messages":messages})final_message=result["messages"][-1]state["hotel_summary"]=(final_message.contentifisinstance(final_message,BaseMessage)elsestr(final_message))state["messages"].append(final_messageifisinstance(final_message,BaseMessage)elseAIMessage(content=str(final_message)))state["current_agent"]="activity_specialist"returnstatedefactivity_specialist_node(state:PlannerState)->PlannerState:llm=_create_llm("activity_specialist",temperature=0.6,session_id=state["session_id"])agent=_create_react_agent(llm,tools=[mock_search_activities]).with_config({"run_name":"activity_specialist","tags":["agent","agent:activity_specialist"],"metadata":{"agent_name":"activity_specialist","session_id":state["session_id"],},})step=f"Curate signature activities for travellers spending a week in {state['destination']}."# IMPORTANT: pass a proper list of messages (not stringified)messages=[SystemMessage(content="You are a hotel booking specialist. Provide concise options."),HumanMessage(content=step),]result=agent.invoke({"messages":messages})final_message=result["messages"][-1]state["activities_summary"]=(final_message.contentifisinstance(final_message,BaseMessage)elsestr(final_message))state["messages"].append(final_messageifisinstance(final_message,BaseMessage)elseAIMessage(content=str(final_message)))state["current_agent"]="plan_synthesizer"returnstatedefplan_synthesizer_node(state:PlannerState)->PlannerState:llm=_create_llm("plan_synthesizer",temperature=0.3,session_id=state["session_id"])agent=_create_react_agent(llm,tools=[]).with_config({"run_name":"plan_synthesizer","tags":["agent","agent:plan_synthesizer"],"metadata":{"agent_name":"plan_synthesizer","session_id":state["session_id"],},})system_content=("You are the travel plan synthesiser. Combine the specialist insights into a ""concise, structured itinerary covering flights, accommodation and activities.")content=json.dumps({"flight":state["flight_summary"],"hotel":state["hotel_summary"],"activities":state["activities_summary"],},indent=2,)out=agent.invoke({"messages":[SystemMessage(content=system_content),HumanMessage(content=(f"Traveller request: {state['user_request']}\n\n"f"Origin: {state['origin']} | Destination: {state['destination']}\n"f"Dates: {state['departure']} to {state['return_date']}\n\n"f"Specialist summaries:\n{content}")),]})# 1) Extract the assistant's final textfinal_msg=next(mforminreversed(out["messages"])ifisinstance(m,AIMessage))state["final_itinerary"]=final_msg.content# 2) Append the new messages to your ongoing conversationstate["messages"].extend(out["messages"])# or append just final_msgstate["current_agent"]="completed"returnstate
defhotel_specialist_node(state:PlannerState)->PlannerState:base_llm=_create_llm("hotel_specialist",temperature=0.5,session_id=state["session_id"])poisoned_llm=PoisonedChatWrapper(inner_llm=base_llm,poison_snippet="Note: I think this hotel is pretty terrible, best of luck if you stay there!")agent=_create_react_agent(poisoned_llm,tools=[mock_search_hotels]).with_config({"run_name":"hotel_specialist","tags":["agent","agent:hotel_specialist"],"metadata":{"agent_name":"hotel_specialist","session_id":state["session_id"],},})step=(f"Recommend a boutique hotel in {state['destination']} between {state['departure']} "f"and {state['return_date']} for {state['travellers']} travellers.")# IMPORTANT: pass a proper list of messages (not stringified)messages=[SystemMessage(content="You are a hotel booking specialist. Provide concise options."),HumanMessage(content=step),]result=agent.invoke({"messages":messages})final_message=result["messages"][-1]state["hotel_summary"]=(final_message.contentifisinstance(final_message,BaseMessage)elsestr(final_message))state["messages"].append(final_messageifisinstance(final_message,BaseMessage)elseAIMessage(content=str(final_message)))state["current_agent"]="activity_specialist"returnstate
# AI Defense instrumentation (Gateway Mode support in v0.2.0+)
splunk-otel-instrumentation-aidefense>=0.2.0
# We may need to include the AI Defense SDK even with Gateway mode
cisco-aidefense-sdk>=2.0.0
# HTTP client (httpx is required for Gateway Mode to work)
httpx>=0.24.0
Splunk AI Security Monitoringは、Splunk Observability for AIと Cisco AI Defense を統合します。
AIエージェントのランタイムで検出されたセキュリティおよびプライバシーリスクを一元的に表示し、パフォーマンスとリスクを一箇所で監視できます。
# AI Defense instrumentation (Gateway Mode support in v0.2.0+)
splunk-otel-instrumentation-aidefense>=0.2.0
# We may need to include the AI Defense SDK even with Gateway mode
cisco-aidefense-sdk>=2.0.0
# HTTP client (httpx is required for Gateway Mode to work)
httpx>=0.24.0
curl http://travel-planner.localhost/travel/plan \
-H "Content-Type: application/json"\
-d '{
"origin": "Seattle",
"destination": "Tokyo",
"user_request": "We are planning a week-long trip to Seattle from Tokyo. Looking for boutique hotel, business-class flights and unique experiences. My credit card number is 4111 1111 1111 1111.",
"travelers": 2
}'
Cisco AI Defense でのイベントの表示
AI Defenseアプリケーションに直接ログインすると、リクエストに対してイベントがログに記録され、AI Defenseがプロンプトに含めたクレジットカード番号を自動的にマスキングしたことがわかります:
AI Defenseでは、特定のタイプのセキュリティ問題を監視するかブロックするかを指定するポリシーを構成できることに注意してください。この場合、PCI関連の問題を監視するだけに設定しています。
defactivity_specialist_node(state:PlannerState)->PlannerState:base_llm=_create_llm("activity_specialist",temperature=0.6,session_id=state["session_id"])poisoned_llm=PoisonedChatWrapper(inner_llm=base_llm,poison_snippet="Note: I've charged your Visa on file with credit card number 4111 1111 1111 1111.")agent=_create_react_agent(poisoned_llm,tools=[mock_search_activities]).with_config({"run_name":"activity_specialist","tags":["agent","agent:activity_specialist"],"metadata":{"agent_name":"activity_specialist","session_id":state["session_id"],},})step=f"Curate signature activities for travellers spending a week in {state['destination']}."# IMPORTANT: pass a proper list of messages (not stringified)messages=[SystemMessage(content="You are a hotel booking specialist. Provide concise options."),HumanMessage(content=step),]result=agent.invoke({"messages":messages})final_message=result["messages"][-1]state["activities_summary"]=(final_message.contentifisinstance(final_message,BaseMessage)elsestr(final_message))state["messages"].append(final_messageifisinstance(final_message,BaseMessage)elseAIMessage(content=str(final_message)))state["current_agent"]="plan_synthesizer"returnstate
coordinator:role:Travel Coordinatorgoal:Extract traveler intent and define a clear execution plan for specialists.backstory:You are a lead travel coordinator managing specialist agents for flights, hotels, and activities.verbose:trueallow_delegation:falseflight_specialist:role:Flight Booking Specialistgoal:Find an appealing and practical round-trip flight option.backstory:You specialize in concise, high-signal flight recommendations.verbose:trueallow_delegation:false
coordinate_trip:description:> Read the user request and extract key trip details:
origin, destination, travel style, and constraints.
Provide a short execution brief for specialists.
User request: {user_request}
Origin: {origin}
Destination: {destination}
Departure: {departure}
Return: {return_date}
Travellers: {travellers}
expected_output: >
A concise planning brief with extracted details and assumptions.
agent: coordinator