22# SPDX-License-Identifier: Apache-2.0
33import logging
44import os
5+ from typing import Annotated
56import uuid
67from collections import defaultdict
78from textwrap import dedent
89
910from a2a .types import (
11+ AgentCapabilities ,
1012 AgentSkill ,
1113 Artifact ,
1214 FilePart ,
2123from beeai_framework .agents .experimental .events import (
2224 RequirementAgentSuccessEvent ,
2325)
26+ from beeai_framework .agents .experimental .utils ._tool import FinalAnswerTool
2427from beeai_framework .backend .types import ChatModelParameters
2528from beeai_framework .memory import UnconstrainedMemory
2629from beeai_framework .middleware .trajectory import GlobalTrajectoryMiddleware
2932from beeai_framework .tools .search .wikipedia import WikipediaTool
3033from beeai_framework .tools .weather .openmeteo import OpenMeteoTool
3134
32- from beeai_sdk .a2a .extensions import AgentDetail , AgentDetailTool
35+ from beeai_sdk .a2a .extensions import (
36+ AgentDetail ,
37+ AgentDetailTool ,
38+ CitationExtensionServer ,
39+ CitationExtensionSpec ,
40+ TrajectoryExtensionServer ,
41+ TrajectoryExtensionSpec ,
42+ )
3343from beeai_sdk .a2a .types import AgentMessage
3444from beeai_sdk .server import Server
3545from beeai_sdk .server .context import Context
46+ from chat .helpers .citations import extract_citations
47+ from chat .helpers .trajectory import TrajectoryContent
3648from openinference .instrumentation .beeai import BeeAIInstrumentor
3749
3850from chat .tools .files .file_creator import FileCreatorTool , FileCreatorToolOutput
5163
5264BeeAIInstrumentor ().instrument ()
5365## TODO: https://github.com/phoenixframework/phoenix/issues/6224
54- logging .getLogger ("opentelemetry.exporter.otlp.proto.http._log_exporter" ).setLevel (logging .CRITICAL )
55- logging .getLogger ("opentelemetry.exporter.otlp.proto.http.metric_exporter" ).setLevel (logging .CRITICAL )
56-
66+ logging .getLogger ("opentelemetry.exporter.otlp.proto.http._log_exporter" ).setLevel (
67+ logging .CRITICAL
68+ )
69+ logging .getLogger ("opentelemetry.exporter.otlp.proto.http.metric_exporter" ).setLevel (
70+ logging .CRITICAL
71+ )
5772
5873logger = logging .getLogger (__name__ )
5974
7691 ui_type = "chat" ,
7792 user_greeting = "How can I help you?" ,
7893 tools = [
79- AgentDetailTool (name = "Web Search (DuckDuckGo)" , description = "Retrieves real-time search results." ),
80- AgentDetailTool (name = "Wikipedia Search" , description = "Fetches summaries from Wikipedia." ),
81- AgentDetailTool (name = "Weather Information (OpenMeteo)" , description = "Provides real-time weather updates." ),
94+ AgentDetailTool (
95+ name = "Web Search (DuckDuckGo)" ,
96+ description = "Retrieves real-time search results." ,
97+ ),
98+ AgentDetailTool (
99+ name = "Wikipedia Search" , description = "Fetches summaries from Wikipedia."
100+ ),
101+ AgentDetailTool (
102+ name = "Weather Information (OpenMeteo)" ,
103+ description = "Provides real-time weather updates." ,
104+ ),
82105 ],
83106 framework = "BeeAI" ,
84107 ),
114137 """
115138 ),
116139 tags = ["chat" ],
117- examples = ["Please find a room in LA, CA, April 15, 2025, checkout date is april 18, 2 adults" ],
140+ examples = [
141+ "Please find a room in LA, CA, April 15, 2025, checkout date is april 18, 2 adults"
142+ ],
118143 )
119144 ],
120145)
121- async def chat (message : Message , context : Context ):
146+ async def chat (
147+ message : Message ,
148+ context : Context ,
149+ trajectory : Annotated [TrajectoryExtensionServer , TrajectoryExtensionSpec ()],
150+ citation : Annotated [CitationExtensionServer , CitationExtensionSpec ()],
151+ ):
122152 """
123153 The agent is an AI-powered conversational system with memory, supporting real-time search, Wikipedia lookups,
124154 and weather updates through integrated tools.
125155 """
126- extracted_files = await extract_files (history = messages [context .context_id ], incoming_message = message )
156+ extracted_files = await extract_files (
157+ history = messages [context .context_id ], incoming_message = message
158+ )
127159 input = to_framework_message (message )
128160
129161 # Configure tools
130162 file_reader_tool_class = create_file_reader_tool_class (
131163 extracted_files
132164 ) # Dynamically created tool input schema based on real provided files ensures that small LLMs can't hallucinate the input
133165
166+ FinalAnswerTool .description = """Assemble and send the final answer to the user. When using information gathered from other tools that provided URL addresses, you MUST properly cite them using markdown citation format: [description](URL).
167+
168+ Citation Requirements:
169+ - Use descriptive text that summarizes the source content
170+ - Include the exact URL provided by the tool
171+ - Place citations inline where the information is referenced
172+
173+ Examples:
174+ - According to [OpenAI's latest announcement](https://example.com/gpt5), GPT-5 will be released next year.
175+ - Recent studies show [AI adoption has increased by 67%](https://example.com/ai-study) in enterprise environments.
176+ - Weather data indicates [temperatures will reach 25°C tomorrow](https://weather.example.com/forecast).""" # type: ignore
177+
134178 tools = [
135179 # Auxiliary tools
136180 ActTool (), # Enforces correct thinking sequence by requiring tool selection before execution
@@ -180,6 +224,16 @@ async def chat(message: Message, context: Context):
180224
181225 last_step = event .state .steps [- 1 ] if event .state .steps else None
182226 if last_step and last_step .tool is not None :
227+ trajectory_content = TrajectoryContent (
228+ input = last_step .input ,
229+ output = last_step .output ,
230+ error = last_step .error ,
231+ )
232+ yield trajectory .trajectory_metadata (
233+ title = last_step .tool .name ,
234+ content = trajectory_content .model_dump_json (),
235+ )
236+
183237 if isinstance (last_step .output , FileCreatorToolOutput ):
184238 result = last_step .output .result
185239 for file_info in result .files :
@@ -205,7 +259,15 @@ async def chat(message: Message, context: Context):
205259
206260 if final_answer :
207261 framework_messages [context .context_id ].append (final_answer )
208- message = AgentMessage (text = final_answer .text )
262+
263+ citations , clean_text = extract_citations (final_answer .text )
264+
265+ message = AgentMessage (
266+ text = clean_text ,
267+ metadata = (
268+ citation .citation_metadata (citations = citations ) if citations else None
269+ ),
270+ )
209271 messages [context .context_id ].append (message )
210272 yield message
211273
0 commit comments