aip-agents-binary 0.5.20__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aip_agents/__init__.py +65 -0
- aip_agents/a2a/__init__.py +19 -0
- aip_agents/a2a/server/__init__.py +10 -0
- aip_agents/a2a/server/base_executor.py +1086 -0
- aip_agents/a2a/server/google_adk_executor.py +198 -0
- aip_agents/a2a/server/langflow_executor.py +180 -0
- aip_agents/a2a/server/langgraph_executor.py +270 -0
- aip_agents/a2a/types.py +232 -0
- aip_agents/agent/__init__.py +27 -0
- aip_agents/agent/base_agent.py +970 -0
- aip_agents/agent/base_langgraph_agent.py +2942 -0
- aip_agents/agent/google_adk_agent.py +926 -0
- aip_agents/agent/google_adk_constants.py +6 -0
- aip_agents/agent/hitl/__init__.py +24 -0
- aip_agents/agent/hitl/config.py +28 -0
- aip_agents/agent/hitl/langgraph_hitl_mixin.py +515 -0
- aip_agents/agent/hitl/manager.py +532 -0
- aip_agents/agent/hitl/models.py +18 -0
- aip_agents/agent/hitl/prompt/__init__.py +9 -0
- aip_agents/agent/hitl/prompt/base.py +42 -0
- aip_agents/agent/hitl/prompt/deferred.py +73 -0
- aip_agents/agent/hitl/registry.py +149 -0
- aip_agents/agent/interface.py +138 -0
- aip_agents/agent/interfaces.py +65 -0
- aip_agents/agent/langflow_agent.py +464 -0
- aip_agents/agent/langgraph_memory_enhancer_agent.py +433 -0
- aip_agents/agent/langgraph_react_agent.py +2514 -0
- aip_agents/agent/system_instruction_context.py +34 -0
- aip_agents/clients/__init__.py +10 -0
- aip_agents/clients/langflow/__init__.py +10 -0
- aip_agents/clients/langflow/client.py +477 -0
- aip_agents/clients/langflow/types.py +18 -0
- aip_agents/constants.py +23 -0
- aip_agents/credentials/manager.py +132 -0
- aip_agents/examples/__init__.py +5 -0
- aip_agents/examples/compare_streaming_client.py +783 -0
- aip_agents/examples/compare_streaming_server.py +142 -0
- aip_agents/examples/demo_memory_recall.py +401 -0
- aip_agents/examples/hello_world_a2a_google_adk_client.py +49 -0
- aip_agents/examples/hello_world_a2a_google_adk_client_agent.py +48 -0
- aip_agents/examples/hello_world_a2a_google_adk_client_streaming.py +60 -0
- aip_agents/examples/hello_world_a2a_google_adk_server.py +79 -0
- aip_agents/examples/hello_world_a2a_langchain_client.py +39 -0
- aip_agents/examples/hello_world_a2a_langchain_client_agent.py +39 -0
- aip_agents/examples/hello_world_a2a_langchain_client_lm_invoker.py +37 -0
- aip_agents/examples/hello_world_a2a_langchain_client_streaming.py +41 -0
- aip_agents/examples/hello_world_a2a_langchain_reference_client_streaming.py +60 -0
- aip_agents/examples/hello_world_a2a_langchain_reference_server.py +105 -0
- aip_agents/examples/hello_world_a2a_langchain_server.py +79 -0
- aip_agents/examples/hello_world_a2a_langchain_server_lm_invoker.py +78 -0
- aip_agents/examples/hello_world_a2a_langflow_client.py +83 -0
- aip_agents/examples/hello_world_a2a_langflow_server.py +82 -0
- aip_agents/examples/hello_world_a2a_langgraph_artifact_client.py +73 -0
- aip_agents/examples/hello_world_a2a_langgraph_artifact_client_streaming.py +76 -0
- aip_agents/examples/hello_world_a2a_langgraph_artifact_server.py +92 -0
- aip_agents/examples/hello_world_a2a_langgraph_client.py +54 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_agent.py +54 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_agent_lm_invoker.py +32 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_streaming.py +50 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_streaming_lm_invoker.py +44 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_streaming_tool_streaming.py +92 -0
- aip_agents/examples/hello_world_a2a_langgraph_server.py +84 -0
- aip_agents/examples/hello_world_a2a_langgraph_server_lm_invoker.py +79 -0
- aip_agents/examples/hello_world_a2a_langgraph_server_tool_streaming.py +132 -0
- aip_agents/examples/hello_world_a2a_mcp_langgraph.py +196 -0
- aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_client.py +244 -0
- aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_server.py +251 -0
- aip_agents/examples/hello_world_a2a_with_metadata_langchain_client.py +57 -0
- aip_agents/examples/hello_world_a2a_with_metadata_langchain_server_lm_invoker.py +80 -0
- aip_agents/examples/hello_world_google_adk.py +41 -0
- aip_agents/examples/hello_world_google_adk_mcp_http.py +34 -0
- aip_agents/examples/hello_world_google_adk_mcp_http_stream.py +40 -0
- aip_agents/examples/hello_world_google_adk_mcp_sse.py +44 -0
- aip_agents/examples/hello_world_google_adk_mcp_sse_stream.py +48 -0
- aip_agents/examples/hello_world_google_adk_mcp_stdio.py +44 -0
- aip_agents/examples/hello_world_google_adk_mcp_stdio_stream.py +48 -0
- aip_agents/examples/hello_world_google_adk_stream.py +44 -0
- aip_agents/examples/hello_world_langchain.py +28 -0
- aip_agents/examples/hello_world_langchain_lm_invoker.py +15 -0
- aip_agents/examples/hello_world_langchain_mcp_http.py +34 -0
- aip_agents/examples/hello_world_langchain_mcp_http_interactive.py +130 -0
- aip_agents/examples/hello_world_langchain_mcp_http_stream.py +42 -0
- aip_agents/examples/hello_world_langchain_mcp_multi_server.py +155 -0
- aip_agents/examples/hello_world_langchain_mcp_sse.py +34 -0
- aip_agents/examples/hello_world_langchain_mcp_sse_stream.py +40 -0
- aip_agents/examples/hello_world_langchain_mcp_stdio.py +30 -0
- aip_agents/examples/hello_world_langchain_mcp_stdio_stream.py +41 -0
- aip_agents/examples/hello_world_langchain_stream.py +36 -0
- aip_agents/examples/hello_world_langchain_stream_lm_invoker.py +39 -0
- aip_agents/examples/hello_world_langflow_agent.py +163 -0
- aip_agents/examples/hello_world_langgraph.py +39 -0
- aip_agents/examples/hello_world_langgraph_bosa_twitter.py +41 -0
- aip_agents/examples/hello_world_langgraph_mcp_http.py +31 -0
- aip_agents/examples/hello_world_langgraph_mcp_http_stream.py +34 -0
- aip_agents/examples/hello_world_langgraph_mcp_sse.py +35 -0
- aip_agents/examples/hello_world_langgraph_mcp_sse_stream.py +50 -0
- aip_agents/examples/hello_world_langgraph_mcp_stdio.py +35 -0
- aip_agents/examples/hello_world_langgraph_mcp_stdio_stream.py +50 -0
- aip_agents/examples/hello_world_langgraph_stream.py +43 -0
- aip_agents/examples/hello_world_langgraph_stream_lm_invoker.py +37 -0
- aip_agents/examples/hello_world_model_switch_cli.py +210 -0
- aip_agents/examples/hello_world_multi_agent_adk.py +75 -0
- aip_agents/examples/hello_world_multi_agent_langchain.py +54 -0
- aip_agents/examples/hello_world_multi_agent_langgraph.py +66 -0
- aip_agents/examples/hello_world_multi_agent_langgraph_lm_invoker.py +69 -0
- aip_agents/examples/hello_world_pii_logger.py +21 -0
- aip_agents/examples/hello_world_sentry.py +133 -0
- aip_agents/examples/hello_world_step_limits.py +273 -0
- aip_agents/examples/hello_world_stock_a2a_server.py +103 -0
- aip_agents/examples/hello_world_tool_output_client.py +46 -0
- aip_agents/examples/hello_world_tool_output_server.py +114 -0
- aip_agents/examples/hitl_demo.py +724 -0
- aip_agents/examples/mcp_configs/configs.py +63 -0
- aip_agents/examples/mcp_servers/common.py +76 -0
- aip_agents/examples/mcp_servers/mcp_name.py +29 -0
- aip_agents/examples/mcp_servers/mcp_server_http.py +19 -0
- aip_agents/examples/mcp_servers/mcp_server_sse.py +19 -0
- aip_agents/examples/mcp_servers/mcp_server_stdio.py +19 -0
- aip_agents/examples/mcp_servers/mcp_time.py +10 -0
- aip_agents/examples/pii_demo_langgraph_client.py +69 -0
- aip_agents/examples/pii_demo_langgraph_server.py +126 -0
- aip_agents/examples/pii_demo_multi_agent_client.py +80 -0
- aip_agents/examples/pii_demo_multi_agent_server.py +247 -0
- aip_agents/examples/todolist_planning_a2a_langchain_client.py +70 -0
- aip_agents/examples/todolist_planning_a2a_langgraph_server.py +88 -0
- aip_agents/examples/tools/__init__.py +27 -0
- aip_agents/examples/tools/adk_arithmetic_tools.py +36 -0
- aip_agents/examples/tools/adk_weather_tool.py +60 -0
- aip_agents/examples/tools/data_generator_tool.py +103 -0
- aip_agents/examples/tools/data_visualization_tool.py +312 -0
- aip_agents/examples/tools/image_artifact_tool.py +136 -0
- aip_agents/examples/tools/langchain_arithmetic_tools.py +26 -0
- aip_agents/examples/tools/langchain_currency_exchange_tool.py +88 -0
- aip_agents/examples/tools/langchain_graph_artifact_tool.py +172 -0
- aip_agents/examples/tools/langchain_weather_tool.py +48 -0
- aip_agents/examples/tools/langgraph_streaming_tool.py +130 -0
- aip_agents/examples/tools/mock_retrieval_tool.py +56 -0
- aip_agents/examples/tools/pii_demo_tools.py +189 -0
- aip_agents/examples/tools/random_chart_tool.py +142 -0
- aip_agents/examples/tools/serper_tool.py +202 -0
- aip_agents/examples/tools/stock_tools.py +82 -0
- aip_agents/examples/tools/table_generator_tool.py +167 -0
- aip_agents/examples/tools/time_tool.py +82 -0
- aip_agents/examples/tools/weather_forecast_tool.py +38 -0
- aip_agents/executor/agent_executor.py +473 -0
- aip_agents/executor/base.py +48 -0
- aip_agents/mcp/__init__.py +1 -0
- aip_agents/mcp/client/__init__.py +14 -0
- aip_agents/mcp/client/base_mcp_client.py +369 -0
- aip_agents/mcp/client/connection_manager.py +193 -0
- aip_agents/mcp/client/google_adk/__init__.py +11 -0
- aip_agents/mcp/client/google_adk/client.py +381 -0
- aip_agents/mcp/client/langchain/__init__.py +11 -0
- aip_agents/mcp/client/langchain/client.py +265 -0
- aip_agents/mcp/client/persistent_session.py +359 -0
- aip_agents/mcp/client/session_pool.py +351 -0
- aip_agents/mcp/client/transports.py +215 -0
- aip_agents/mcp/utils/__init__.py +7 -0
- aip_agents/mcp/utils/config_validator.py +139 -0
- aip_agents/memory/__init__.py +14 -0
- aip_agents/memory/adapters/__init__.py +10 -0
- aip_agents/memory/adapters/base_adapter.py +717 -0
- aip_agents/memory/adapters/mem0.py +84 -0
- aip_agents/memory/base.py +84 -0
- aip_agents/memory/constants.py +49 -0
- aip_agents/memory/factory.py +86 -0
- aip_agents/memory/guidance.py +20 -0
- aip_agents/memory/simple_memory.py +47 -0
- aip_agents/middleware/__init__.py +17 -0
- aip_agents/middleware/base.py +88 -0
- aip_agents/middleware/manager.py +128 -0
- aip_agents/middleware/todolist.py +274 -0
- aip_agents/schema/__init__.py +69 -0
- aip_agents/schema/a2a.py +56 -0
- aip_agents/schema/agent.py +111 -0
- aip_agents/schema/hitl.py +157 -0
- aip_agents/schema/langgraph.py +37 -0
- aip_agents/schema/model_id.py +97 -0
- aip_agents/schema/step_limit.py +108 -0
- aip_agents/schema/storage.py +40 -0
- aip_agents/sentry/__init__.py +11 -0
- aip_agents/sentry/sentry.py +151 -0
- aip_agents/storage/__init__.py +41 -0
- aip_agents/storage/base.py +85 -0
- aip_agents/storage/clients/__init__.py +12 -0
- aip_agents/storage/clients/minio_client.py +318 -0
- aip_agents/storage/config.py +62 -0
- aip_agents/storage/providers/__init__.py +15 -0
- aip_agents/storage/providers/base.py +106 -0
- aip_agents/storage/providers/memory.py +114 -0
- aip_agents/storage/providers/object_storage.py +214 -0
- aip_agents/tools/__init__.py +33 -0
- aip_agents/tools/bosa_tools.py +105 -0
- aip_agents/tools/browser_use/__init__.py +82 -0
- aip_agents/tools/browser_use/action_parser.py +103 -0
- aip_agents/tools/browser_use/browser_use_tool.py +1112 -0
- aip_agents/tools/browser_use/llm_config.py +120 -0
- aip_agents/tools/browser_use/minio_storage.py +198 -0
- aip_agents/tools/browser_use/schemas.py +119 -0
- aip_agents/tools/browser_use/session.py +76 -0
- aip_agents/tools/browser_use/session_errors.py +132 -0
- aip_agents/tools/browser_use/steel_session_recording.py +317 -0
- aip_agents/tools/browser_use/streaming.py +813 -0
- aip_agents/tools/browser_use/structured_data_parser.py +257 -0
- aip_agents/tools/browser_use/structured_data_recovery.py +204 -0
- aip_agents/tools/browser_use/types.py +78 -0
- aip_agents/tools/code_sandbox/__init__.py +26 -0
- aip_agents/tools/code_sandbox/constant.py +13 -0
- aip_agents/tools/code_sandbox/e2b_cloud_sandbox_extended.py +257 -0
- aip_agents/tools/code_sandbox/e2b_sandbox_tool.py +411 -0
- aip_agents/tools/constants.py +165 -0
- aip_agents/tools/document_loader/__init__.py +44 -0
- aip_agents/tools/document_loader/base_reader.py +302 -0
- aip_agents/tools/document_loader/docx_reader_tool.py +68 -0
- aip_agents/tools/document_loader/excel_reader_tool.py +171 -0
- aip_agents/tools/document_loader/pdf_reader_tool.py +79 -0
- aip_agents/tools/document_loader/pdf_splitter.py +169 -0
- aip_agents/tools/gl_connector/__init__.py +5 -0
- aip_agents/tools/gl_connector/tool.py +351 -0
- aip_agents/tools/memory_search/__init__.py +22 -0
- aip_agents/tools/memory_search/base.py +200 -0
- aip_agents/tools/memory_search/mem0.py +258 -0
- aip_agents/tools/memory_search/schema.py +48 -0
- aip_agents/tools/memory_search_tool.py +26 -0
- aip_agents/tools/time_tool.py +117 -0
- aip_agents/tools/tool_config_injector.py +300 -0
- aip_agents/tools/web_search/__init__.py +15 -0
- aip_agents/tools/web_search/serper_tool.py +187 -0
- aip_agents/types/__init__.py +70 -0
- aip_agents/types/a2a_events.py +13 -0
- aip_agents/utils/__init__.py +79 -0
- aip_agents/utils/a2a_connector.py +1757 -0
- aip_agents/utils/artifact_helpers.py +502 -0
- aip_agents/utils/constants.py +22 -0
- aip_agents/utils/datetime/__init__.py +34 -0
- aip_agents/utils/datetime/normalization.py +231 -0
- aip_agents/utils/datetime/timezone.py +206 -0
- aip_agents/utils/env_loader.py +27 -0
- aip_agents/utils/event_handler_registry.py +58 -0
- aip_agents/utils/file_prompt_utils.py +176 -0
- aip_agents/utils/final_response_builder.py +211 -0
- aip_agents/utils/formatter_llm_client.py +231 -0
- aip_agents/utils/langgraph/__init__.py +19 -0
- aip_agents/utils/langgraph/converter.py +128 -0
- aip_agents/utils/langgraph/tool_managers/__init__.py +15 -0
- aip_agents/utils/langgraph/tool_managers/a2a_tool_manager.py +99 -0
- aip_agents/utils/langgraph/tool_managers/base_tool_manager.py +66 -0
- aip_agents/utils/langgraph/tool_managers/delegation_tool_manager.py +1071 -0
- aip_agents/utils/langgraph/tool_output_management.py +967 -0
- aip_agents/utils/logger.py +195 -0
- aip_agents/utils/metadata/__init__.py +27 -0
- aip_agents/utils/metadata/activity_metadata_helper.py +407 -0
- aip_agents/utils/metadata/activity_narrative/__init__.py +35 -0
- aip_agents/utils/metadata/activity_narrative/builder.py +817 -0
- aip_agents/utils/metadata/activity_narrative/constants.py +51 -0
- aip_agents/utils/metadata/activity_narrative/context.py +49 -0
- aip_agents/utils/metadata/activity_narrative/formatters.py +230 -0
- aip_agents/utils/metadata/activity_narrative/utils.py +35 -0
- aip_agents/utils/metadata/schemas/__init__.py +16 -0
- aip_agents/utils/metadata/schemas/activity_schema.py +29 -0
- aip_agents/utils/metadata/schemas/thinking_schema.py +31 -0
- aip_agents/utils/metadata/thinking_metadata_helper.py +38 -0
- aip_agents/utils/metadata_helper.py +358 -0
- aip_agents/utils/name_preprocessor/__init__.py +17 -0
- aip_agents/utils/name_preprocessor/base_name_preprocessor.py +73 -0
- aip_agents/utils/name_preprocessor/google_name_preprocessor.py +100 -0
- aip_agents/utils/name_preprocessor/name_preprocessor.py +87 -0
- aip_agents/utils/name_preprocessor/openai_name_preprocessor.py +48 -0
- aip_agents/utils/pii/__init__.py +25 -0
- aip_agents/utils/pii/pii_handler.py +397 -0
- aip_agents/utils/pii/pii_helper.py +207 -0
- aip_agents/utils/pii/uuid_deanonymizer_mapping.py +195 -0
- aip_agents/utils/reference_helper.py +273 -0
- aip_agents/utils/sse_chunk_transformer.py +831 -0
- aip_agents/utils/step_limit_manager.py +265 -0
- aip_agents/utils/token_usage_helper.py +156 -0
- aip_agents_binary-0.5.20.dist-info/METADATA +681 -0
- aip_agents_binary-0.5.20.dist-info/RECORD +280 -0
- aip_agents_binary-0.5.20.dist-info/WHEEL +5 -0
- aip_agents_binary-0.5.20.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""This is a tool that generates a tiny bar/line chart and returns it as a Command artifact.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# Optional imports for chart rendering
|
|
8
|
+
import io
|
|
9
|
+
|
|
10
|
+
from langchain_core.tools import BaseTool
|
|
11
|
+
from langgraph.types import Command
|
|
12
|
+
from pydantic import BaseModel, Field
|
|
13
|
+
|
|
14
|
+
from aip_agents.a2a.types import MimeType
|
|
15
|
+
from aip_agents.utils.artifact_helpers import create_artifact_command
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
from PIL import Image, ImageDraw # type: ignore
|
|
19
|
+
|
|
20
|
+
PIL_AVAILABLE = True
|
|
21
|
+
except ImportError:
|
|
22
|
+
PIL_AVAILABLE = False
|
|
23
|
+
Image = None
|
|
24
|
+
ImageDraw = None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class GraphCommandInput(BaseModel):
|
|
28
|
+
"""Input schema for a tiny bar/line chart artifact tool.
|
|
29
|
+
|
|
30
|
+
Note: width/height are clamped to a small size to keep artifacts lightweight.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
chart_type: str = Field(
|
|
34
|
+
default="bar",
|
|
35
|
+
description="Type of chart to generate (bar or line).",
|
|
36
|
+
pattern="^(bar|line)$",
|
|
37
|
+
)
|
|
38
|
+
labels: list[str] = Field(
|
|
39
|
+
default_factory=lambda: ["A", "B", "C"],
|
|
40
|
+
description="Labels for categories",
|
|
41
|
+
)
|
|
42
|
+
values: list[float] = Field(
|
|
43
|
+
default_factory=lambda: [10.0, 20.0, 15.0],
|
|
44
|
+
description="Values corresponding to labels",
|
|
45
|
+
)
|
|
46
|
+
title: str = Field(default="Chart", description="Chart title (metadata only)")
|
|
47
|
+
width: int = Field(default=64, ge=16, le=128, description="Desired image width in px (will be clamped)")
|
|
48
|
+
height: int = Field(default=64, ge=16, le=128, description="Desired image height in px (will be clamped)")
|
|
49
|
+
image_name: str = Field(default="chart", description="Base filename (without extension)")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class GraphArtifactCommandTool(BaseTool):
|
|
53
|
+
"""Generate a very small PNG chart and return it as a Command artifact."""
|
|
54
|
+
|
|
55
|
+
name: str = "graph_generator_command"
|
|
56
|
+
description: str = "Generate a tiny bar/line chart and return a lightweight PNG artifact using Command."
|
|
57
|
+
args_schema: type[BaseModel] = GraphCommandInput
|
|
58
|
+
|
|
59
|
+
def _run( # noqa: PLR0913
|
|
60
|
+
self,
|
|
61
|
+
chart_type: str = "bar",
|
|
62
|
+
labels: list[str] | None = None,
|
|
63
|
+
values: list[float] | None = None,
|
|
64
|
+
title: str = "Chart",
|
|
65
|
+
width: int = 64,
|
|
66
|
+
height: int = 64,
|
|
67
|
+
image_name: str = "chart",
|
|
68
|
+
) -> Command:
|
|
69
|
+
labels = labels or ["A", "B", "C"]
|
|
70
|
+
values = values or [10.0, 20.0, 15.0]
|
|
71
|
+
|
|
72
|
+
if not labels or len(labels) != len(values):
|
|
73
|
+
return Command(update={"result": "Invalid input: labels and values must be non-empty and equal length."})
|
|
74
|
+
|
|
75
|
+
# Clamp to very small size to keep base64 short
|
|
76
|
+
img_w = max(16, min(int(width), 64))
|
|
77
|
+
img_h = max(16, min(int(height), 64))
|
|
78
|
+
|
|
79
|
+
png_bytes = self._render_tiny_chart(chart_type, labels, values, img_w, img_h)
|
|
80
|
+
|
|
81
|
+
result_text = f"Generated tiny {chart_type} chart '{title}' with {len(labels)} points as {image_name}.png"
|
|
82
|
+
metadata_delta = {
|
|
83
|
+
"last_chart": {
|
|
84
|
+
"name": f"{image_name}.png",
|
|
85
|
+
"chart_type": chart_type,
|
|
86
|
+
"title": title,
|
|
87
|
+
"num_points": len(labels),
|
|
88
|
+
"size": f"{img_w}x{img_h}",
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return create_artifact_command(
|
|
93
|
+
result=result_text,
|
|
94
|
+
artifact_data=png_bytes,
|
|
95
|
+
artifact_name=f"{image_name}.png",
|
|
96
|
+
artifact_description=f"Tiny {chart_type} chart: {title}",
|
|
97
|
+
mime_type=MimeType.IMAGE_PNG,
|
|
98
|
+
metadata_update=metadata_delta,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
@staticmethod
|
|
102
|
+
def _render_tiny_chart(
|
|
103
|
+
chart_type: str,
|
|
104
|
+
labels: list[str],
|
|
105
|
+
values: list[float],
|
|
106
|
+
width: int,
|
|
107
|
+
height: int,
|
|
108
|
+
) -> bytes:
|
|
109
|
+
"""Render a tiny chart using only PIL to keep artifact size minimal.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
chart_type (str): Type of chart to render ("bar" or "line").
|
|
113
|
+
labels (list[str]): Labels for the data points.
|
|
114
|
+
values (list[float]): Values to plot.
|
|
115
|
+
width (int): Width of the chart image in pixels.
|
|
116
|
+
height (int): Height of the chart image in pixels.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
bytes: PNG image data as bytes.
|
|
120
|
+
"""
|
|
121
|
+
if not PIL_AVAILABLE:
|
|
122
|
+
raise ImportError("PIL is not available for chart rendering")
|
|
123
|
+
|
|
124
|
+
# Canvas and basic layout
|
|
125
|
+
image = Image.new("RGB", (width, height), color="white")
|
|
126
|
+
draw = ImageDraw.Draw(image)
|
|
127
|
+
|
|
128
|
+
# Small margins for axes
|
|
129
|
+
margin_left = max(3, width // 12)
|
|
130
|
+
margin_right = max(2, width // 20)
|
|
131
|
+
margin_top = max(3, height // 12)
|
|
132
|
+
margin_bottom = max(6, height // 8)
|
|
133
|
+
|
|
134
|
+
plot_w = max(1, width - margin_left - margin_right)
|
|
135
|
+
plot_h = max(1, height - margin_top - margin_bottom)
|
|
136
|
+
|
|
137
|
+
# Axes
|
|
138
|
+
x0, y0 = margin_left, height - margin_bottom
|
|
139
|
+
x1, y1 = width - margin_right, margin_top
|
|
140
|
+
draw.line((x0, y0, x1, y0), fill="black")
|
|
141
|
+
draw.line((x0, y0, x0, y1), fill="black")
|
|
142
|
+
|
|
143
|
+
# Normalize values to plot height
|
|
144
|
+
max_val = max(values + [1.0])
|
|
145
|
+
n = len(values)
|
|
146
|
+
if chart_type == "bar":
|
|
147
|
+
# Thin bars to keep readable at tiny sizes
|
|
148
|
+
bar_spacing = plot_w / max(n, 1)
|
|
149
|
+
bar_w = max(1, int(bar_spacing * 0.6))
|
|
150
|
+
for i, val in enumerate(values):
|
|
151
|
+
x_center = x0 + int((i + 0.5) * bar_spacing)
|
|
152
|
+
bar_h = int((val / max_val) * plot_h)
|
|
153
|
+
x_left = x_center - bar_w // 2
|
|
154
|
+
x_right = x_center + bar_w // 2
|
|
155
|
+
y_top = y0 - bar_h
|
|
156
|
+
draw.rectangle((x_left, y_top, x_right, y0), fill="#4C78A8")
|
|
157
|
+
else:
|
|
158
|
+
# Polyline with small points
|
|
159
|
+
points = []
|
|
160
|
+
step = plot_w / max(n, 1)
|
|
161
|
+
for i, val in enumerate(values):
|
|
162
|
+
x = x0 + int((i + 0.5) * step)
|
|
163
|
+
y = y0 - int((val / max_val) * plot_h)
|
|
164
|
+
points.append((x, y))
|
|
165
|
+
if len(points) >= 2: # noqa: PLR2004
|
|
166
|
+
draw.line(points, fill="#F58518")
|
|
167
|
+
for px, py in points:
|
|
168
|
+
draw.ellipse((px - 1, py - 1, px + 1, py + 1), fill="#F58518")
|
|
169
|
+
|
|
170
|
+
buf = io.BytesIO()
|
|
171
|
+
image.save(buf, format="PNG", optimize=True)
|
|
172
|
+
return buf.getvalue()
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Defines a weather tool that can be used to get the weather for a specified city.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from langchain_core.tools import tool
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
from aip_agents.utils.logger import get_logger
|
|
11
|
+
|
|
12
|
+
logger = get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class WeatherToolInputSchema(BaseModel):
|
|
16
|
+
"""Schema for weather tool input."""
|
|
17
|
+
|
|
18
|
+
city: str
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@tool(args_schema=WeatherToolInputSchema)
|
|
22
|
+
def weather_tool(city: str) -> str:
|
|
23
|
+
"""Gets the weather for a specified city.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
city: The name of the city to get weather for.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
A string describing the weather conditions.
|
|
30
|
+
"""
|
|
31
|
+
weather_data = {
|
|
32
|
+
"Jakarta": "32°C, Partly cloudy with high humidity",
|
|
33
|
+
"Singapore": "30°C, Scattered thunderstorms",
|
|
34
|
+
"Tokyo": "25°C, Clear skies",
|
|
35
|
+
"London": "18°C, Light rain",
|
|
36
|
+
"New York": "22°C, Sunny",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
city_name = city.strip().title()
|
|
40
|
+
|
|
41
|
+
weather = weather_data.get(city_name)
|
|
42
|
+
if weather:
|
|
43
|
+
logger.info(f"Found weather for {city_name}: {weather}")
|
|
44
|
+
return weather
|
|
45
|
+
else:
|
|
46
|
+
message = f"Weather data not available for {city}"
|
|
47
|
+
logger.warning(message)
|
|
48
|
+
return message
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""Tool that wraps a LangGraph agent with time and weather tools.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Fachriza Adhiatma (fachriza.d.adhiatma@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from langchain_core.runnables import RunnableConfig
|
|
11
|
+
from langchain_core.tools import BaseTool
|
|
12
|
+
from pydantic import BaseModel, Field, PrivateAttr
|
|
13
|
+
|
|
14
|
+
from aip_agents.agent.langgraph_react_agent import LangGraphReactAgent
|
|
15
|
+
from aip_agents.examples.tools.langchain_weather_tool import weather_tool
|
|
16
|
+
from aip_agents.examples.tools.time_tool import TimeTool
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class StreamingToolConfig(BaseModel):
|
|
20
|
+
"""Tool configuration schema for the LangGraph streaming tool."""
|
|
21
|
+
|
|
22
|
+
time_format: str = Field(
|
|
23
|
+
default="%m/%d/%y %H:%M:%S",
|
|
24
|
+
description="DateTime format for time-related queries (e.g., '%Y-%m-%d %H:%M:%S', '%A, %B %d, %Y')",
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class LangGraphStreamingToolInput(BaseModel):
|
|
29
|
+
"""Input schema for the LangGraphStreamingTool."""
|
|
30
|
+
|
|
31
|
+
query: str = Field(..., description="Query prompt for the LangGraph agent to execute")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class LangGraphStreamingTool(BaseTool):
|
|
35
|
+
"""Tool that wraps a LangGraph agent with time and weather forecast capabilities."""
|
|
36
|
+
|
|
37
|
+
name: str = "langgraph_streaming_tool"
|
|
38
|
+
description: str = (
|
|
39
|
+
"Execute tasks using a LangGraph agent with time and weather capabilities. "
|
|
40
|
+
"Can get current time in various formats and weather information for specific cities. "
|
|
41
|
+
"Supports configuration for time format settings."
|
|
42
|
+
)
|
|
43
|
+
args_schema: type[BaseModel] = LangGraphStreamingToolInput
|
|
44
|
+
tool_config_schema: type[BaseModel] = StreamingToolConfig
|
|
45
|
+
_agent: Any = PrivateAttr()
|
|
46
|
+
|
|
47
|
+
def __init__(self, model: Any = None, **kwargs):
|
|
48
|
+
"""Initialize the LangGraphStreamingTool.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
model: The model to use for the agent.
|
|
52
|
+
**kwargs: Additional keyword arguments.
|
|
53
|
+
"""
|
|
54
|
+
super().__init__(**kwargs)
|
|
55
|
+
|
|
56
|
+
if model is None:
|
|
57
|
+
model = os.getenv("DEFAULT_MODEL", "openai/gpt-4o")
|
|
58
|
+
|
|
59
|
+
self._agent = LangGraphReactAgent(
|
|
60
|
+
name="internal_time_weather_agent",
|
|
61
|
+
instruction="You are a helpful assistant with access to time and weather tools. "
|
|
62
|
+
"When users ask for both time and weather information, use BOTH tools: "
|
|
63
|
+
"1. Use the time_tool to get current time information "
|
|
64
|
+
"2. Use the weather_tool to get weather for specific cities "
|
|
65
|
+
"Always provide complete answers using all relevant tools.",
|
|
66
|
+
model=model,
|
|
67
|
+
tools=[TimeTool(), weather_tool],
|
|
68
|
+
description="Internal agent for time and weather tasks",
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def agent(self):
|
|
73
|
+
"""Access the internal agent."""
|
|
74
|
+
return self._agent
|
|
75
|
+
|
|
76
|
+
def _run(self, query: str) -> str:
|
|
77
|
+
"""Run the tool synchronously.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
query: The query to execute.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
The final output from the tool execution.
|
|
84
|
+
"""
|
|
85
|
+
result = self.agent.run(query)
|
|
86
|
+
if isinstance(result, dict) and "output" in result:
|
|
87
|
+
return str(result["output"])
|
|
88
|
+
return str(result)
|
|
89
|
+
|
|
90
|
+
async def _arun(self, query: str) -> str:
|
|
91
|
+
"""Run the tool asynchronously.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
query: The query to execute.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
The final output from the tool execution.
|
|
98
|
+
"""
|
|
99
|
+
result = await self.agent.arun(query)
|
|
100
|
+
if isinstance(result, dict) and "output" in result:
|
|
101
|
+
return str(result["output"])
|
|
102
|
+
return str(result)
|
|
103
|
+
|
|
104
|
+
async def arun_streaming(self, query: str = None, config: RunnableConfig = None, **kwargs):
|
|
105
|
+
"""Execute the LangGraph agent asynchronously with A2A streaming output.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
query: The query to execute.
|
|
109
|
+
config: Tool configuration containing user context and preferences.
|
|
110
|
+
**kwargs: Additional keyword arguments.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
An async generator that yields streaming events.
|
|
114
|
+
"""
|
|
115
|
+
if query is None:
|
|
116
|
+
query = kwargs.get("query") or kwargs.get("task")
|
|
117
|
+
|
|
118
|
+
effective_config = self.get_tool_config(config)
|
|
119
|
+
|
|
120
|
+
if not effective_config:
|
|
121
|
+
effective_config = StreamingToolConfig(time_format="%m/%d/%y %H:%M:%S")
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
enhanced_query = f"[Time format: {effective_config.time_format}] {query}"
|
|
125
|
+
|
|
126
|
+
async for event in self.agent.arun_a2a_stream(enhanced_query, **kwargs):
|
|
127
|
+
if event is not None and isinstance(event, dict):
|
|
128
|
+
yield event
|
|
129
|
+
except Exception as e:
|
|
130
|
+
yield {"event_type": "error", "content": f"Error in agent wrapper streaming: {str(e)}", "is_final": True}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Simple mock tool that returns references for testing reference propagation.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from gllm_core.schema import Chunk
|
|
8
|
+
from langchain_core.tools import BaseTool
|
|
9
|
+
from pydantic import BaseModel, Field
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MockRetrievalInput(BaseModel):
|
|
13
|
+
"""Input schema for the MockRetrievalTool."""
|
|
14
|
+
|
|
15
|
+
query: str = Field(..., description="Search query")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MockRetrievalTool(BaseTool):
|
|
19
|
+
"""Mock tool that returns hardcoded references for testing."""
|
|
20
|
+
|
|
21
|
+
name: str = "mock_retrieval"
|
|
22
|
+
description: str = "Mock retrieval tool that returns sample references for testing reference propagation."
|
|
23
|
+
args_schema: type[BaseModel] = MockRetrievalInput
|
|
24
|
+
save_output_history: bool = True
|
|
25
|
+
|
|
26
|
+
def _run(self, query: str) -> str:
|
|
27
|
+
"""Return a simple mock response.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
query (str): The search query to process.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
str: Mock retrieval result for the query.
|
|
34
|
+
"""
|
|
35
|
+
return f"Mock retrieval result for: {query}"
|
|
36
|
+
|
|
37
|
+
def _format_agent_reference(self, tool_output: str) -> list[Chunk]:
|
|
38
|
+
"""Return hardcoded references for testing.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
tool_output (str): The tool output to format as references.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
list[Chunk]: List of formatted chunks for the query.
|
|
45
|
+
"""
|
|
46
|
+
query = tool_output.split(":")[-1].strip()
|
|
47
|
+
return [
|
|
48
|
+
Chunk(
|
|
49
|
+
content=f"Mock reference 1 for query: {query}",
|
|
50
|
+
metadata={"tool_name": "mock_retrieval", "source": "Mock Database", "title": "Mock Reference 1"},
|
|
51
|
+
),
|
|
52
|
+
Chunk(
|
|
53
|
+
content=f"Mock reference 2 for query: {query}",
|
|
54
|
+
metadata={"tool_name": "mock_retrieval", "source": "Mock Database", "title": "Mock Reference 2"},
|
|
55
|
+
),
|
|
56
|
+
]
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""Mock tools for demonstrating PII handling in parallel tool execution.
|
|
2
|
+
|
|
3
|
+
This module provides tools that return personally identifiable information (PII)
|
|
4
|
+
to demonstrate how the PII handler automatically anonymizes and deanonymizes data
|
|
5
|
+
during tool execution.
|
|
6
|
+
|
|
7
|
+
Tools included:
|
|
8
|
+
- get_customer_info: Returns customer information including email and phone
|
|
9
|
+
- get_employee_data: Returns employee data including name and salary
|
|
10
|
+
- get_user_profile: Returns user profile with personal details
|
|
11
|
+
|
|
12
|
+
Authors:
|
|
13
|
+
Cascade AI Assistant
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from langchain_core.tools import tool
|
|
17
|
+
from pydantic import BaseModel
|
|
18
|
+
|
|
19
|
+
from aip_agents.utils.logger import LoggerManager
|
|
20
|
+
|
|
21
|
+
logger = LoggerManager().get_logger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CustomerIdSchema(BaseModel):
|
|
25
|
+
"""Schema for customer ID input."""
|
|
26
|
+
|
|
27
|
+
customer_id: str
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class EmployeeIdSchema(BaseModel):
|
|
31
|
+
"""Schema for employee ID input."""
|
|
32
|
+
|
|
33
|
+
employee_id: str
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class UserIdSchema(BaseModel):
|
|
37
|
+
"""Schema for user ID input."""
|
|
38
|
+
|
|
39
|
+
user_id: str
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@tool(args_schema=CustomerIdSchema)
|
|
43
|
+
def get_customer_info(customer_id: str) -> str:
|
|
44
|
+
"""Gets customer information including email and phone number.
|
|
45
|
+
|
|
46
|
+
This tool demonstrates PII handling by returning sensitive customer data
|
|
47
|
+
that will be automatically anonymized by the PII handler.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
customer_id: The ID of the customer to retrieve information for.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
A string containing customer information with PII.
|
|
54
|
+
"""
|
|
55
|
+
customer_data = {
|
|
56
|
+
"C001": {
|
|
57
|
+
"name": "John Smith",
|
|
58
|
+
"email": "john.smith@example.com",
|
|
59
|
+
"phone": "+1-555-0101",
|
|
60
|
+
"address": "123 Main St, New York, NY 10001",
|
|
61
|
+
},
|
|
62
|
+
"C002": {
|
|
63
|
+
"name": "Alice Johnson",
|
|
64
|
+
"email": "alice.johnson@example.com",
|
|
65
|
+
"phone": "+1-555-0102",
|
|
66
|
+
"address": "456 Oak Ave, Los Angeles, CA 90001",
|
|
67
|
+
},
|
|
68
|
+
"C003": {
|
|
69
|
+
"name": "Bob Williams",
|
|
70
|
+
"email": "bob.williams@example.com",
|
|
71
|
+
"phone": "+1-555-0103",
|
|
72
|
+
"address": "789 Pine Rd, Chicago, IL 60601",
|
|
73
|
+
},
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
customer = customer_data.get(customer_id.upper())
|
|
77
|
+
if customer:
|
|
78
|
+
logger.info(f"Retrieved customer info for {customer_id}")
|
|
79
|
+
return (
|
|
80
|
+
f"Customer {customer_id}: Name: {customer['name']}, "
|
|
81
|
+
f"Email: {customer['email']}, Phone: {customer['phone']}, "
|
|
82
|
+
f"Address: {customer['address']}"
|
|
83
|
+
)
|
|
84
|
+
else:
|
|
85
|
+
message = f"Customer {customer_id} not found"
|
|
86
|
+
logger.warning(message)
|
|
87
|
+
return message
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@tool(args_schema=EmployeeIdSchema)
|
|
91
|
+
def get_employee_data(employee_id: str) -> str:
|
|
92
|
+
"""Gets employee data including name, email, and salary information.
|
|
93
|
+
|
|
94
|
+
This tool demonstrates PII handling with employee-specific data that includes
|
|
95
|
+
sensitive information like salary and personal email addresses.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
employee_id: The ID of the employee to retrieve data for.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
A string containing employee information with PII.
|
|
102
|
+
"""
|
|
103
|
+
employee_data = {
|
|
104
|
+
"E001": {
|
|
105
|
+
"name": "Carol Davis",
|
|
106
|
+
"email": "carol.davis@company.com",
|
|
107
|
+
"phone": "+1-555-0201",
|
|
108
|
+
"salary": "$85,000",
|
|
109
|
+
"department": "Engineering",
|
|
110
|
+
},
|
|
111
|
+
"E002": {
|
|
112
|
+
"name": "David Miller",
|
|
113
|
+
"email": "david.miller@company.com",
|
|
114
|
+
"phone": "+1-555-0202",
|
|
115
|
+
"salary": "$92,000",
|
|
116
|
+
"department": "Product",
|
|
117
|
+
},
|
|
118
|
+
"E003": {
|
|
119
|
+
"name": "Emma Wilson",
|
|
120
|
+
"email": "emma.wilson@company.com",
|
|
121
|
+
"phone": "+1-555-0203",
|
|
122
|
+
"salary": "$78,000",
|
|
123
|
+
"department": "Marketing",
|
|
124
|
+
},
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
employee = employee_data.get(employee_id.upper())
|
|
128
|
+
if employee:
|
|
129
|
+
logger.info(f"Retrieved employee data for {employee_id}")
|
|
130
|
+
return (
|
|
131
|
+
f"Employee {employee_id}: Name: {employee['name']}, "
|
|
132
|
+
f"Email: {employee['email']}, Phone: {employee['phone']}, "
|
|
133
|
+
f"Salary: {employee['salary']}, Department: {employee['department']}"
|
|
134
|
+
)
|
|
135
|
+
else:
|
|
136
|
+
message = f"Employee {employee_id} not found"
|
|
137
|
+
logger.warning(message)
|
|
138
|
+
return message
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@tool(args_schema=UserIdSchema)
|
|
142
|
+
def get_user_profile(user_id: str) -> str:
|
|
143
|
+
"""Gets user profile information with personal details.
|
|
144
|
+
|
|
145
|
+
This tool demonstrates PII handling with user profile data that includes
|
|
146
|
+
personal information like email, phone, and date of birth.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
user_id: The ID of the user to retrieve profile for.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
A string containing user profile information with PII.
|
|
153
|
+
"""
|
|
154
|
+
user_data = {
|
|
155
|
+
"U001": {
|
|
156
|
+
"username": "john_doe",
|
|
157
|
+
"email": "john.doe@personal.com",
|
|
158
|
+
"phone": "+1-555-0301",
|
|
159
|
+
"dob": "1990-05-15",
|
|
160
|
+
"city": "Seattle, WA",
|
|
161
|
+
},
|
|
162
|
+
"U002": {
|
|
163
|
+
"username": "jane_smith",
|
|
164
|
+
"email": "jane.smith@personal.com",
|
|
165
|
+
"phone": "+1-555-0302",
|
|
166
|
+
"dob": "1992-08-22",
|
|
167
|
+
"city": "Portland, OR",
|
|
168
|
+
},
|
|
169
|
+
"U003": {
|
|
170
|
+
"username": "bob_jones",
|
|
171
|
+
"email": "bob.jones@personal.com",
|
|
172
|
+
"phone": "+1-555-0303",
|
|
173
|
+
"dob": "1988-03-10",
|
|
174
|
+
"city": "San Francisco, CA",
|
|
175
|
+
},
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
user = user_data.get(user_id.upper())
|
|
179
|
+
if user:
|
|
180
|
+
logger.info(f"Retrieved user profile for {user_id}")
|
|
181
|
+
return (
|
|
182
|
+
f"User {user_id}: Username: {user['username']}, "
|
|
183
|
+
f"Email: {user['email']}, Phone: {user['phone']}, "
|
|
184
|
+
f"DOB: {user['dob']}, City: {user['city']}"
|
|
185
|
+
)
|
|
186
|
+
else:
|
|
187
|
+
message = f"User {user_id} not found"
|
|
188
|
+
logger.warning(message)
|
|
189
|
+
return message
|