beamai_agent_callbacks_live_test.erl
1 %%%------------------------------------------------------------------- 2 %%% @doc beamai_agent Callback 系统实时测试 3 %%% 4 %%% 使用真实的 LLM API 测试所有 Callback 功能。 5 %%% 需要环境变量 ZHIPU_API_KEY。 6 %%% 7 %%% 配置:GLM-4.7 + Anthropic Provider 8 %%% @end 9 %%%------------------------------------------------------------------- 10 -module(beamai_agent_callbacks_live_test). 11 12 -include_lib("eunit/include/eunit.hrl"). 13 14 %%==================================================================== 15 %% 配置 16 %%==================================================================== 17 18 %% GLM-4.7 配置 19 glm_config() -> 20 ApiKey = list_to_binary(os:getenv("ZHIPU_API_KEY", "")), 21 case ApiKey of 22 <<>> -> 23 io:format("警告: 未设置 ZHIPU_API_KEY 环境变量~n"), 24 error(missing_api_key); 25 _ -> 26 #{ 27 provider => anthropic, 28 model => <<"glm-4.7">>, 29 api_key => ApiKey, 30 base_url => <<"https://open.bigmodel.cn/api/anthropic">>, 31 timeout => 60000, 32 max_tokens => 2048 33 } 34 end. 35 36 %%==================================================================== 37 %% Test fixtures 38 %%==================================================================== 39 40 setup() -> 41 application:ensure_all_started(beamai_runtime), 42 ok. 43 44 cleanup(_) -> 45 ok. 46 47 %%==================================================================== 48 %% 测试:基础 Callback 触发 49 %%==================================================================== 50 51 basic_callbacks_test_() -> 52 {setup, 53 fun setup/0, 54 fun cleanup/1, 55 [ 56 {"on_llm_start 和 on_llm_end 触发", {timeout, 120, fun test_llm_callbacks/0}}, 57 {"on_chain_start 和 on_chain_end 触发", {timeout, 120, fun test_chain_callbacks/0}}, 58 {"on_agent_finish 触发", {timeout, 120, fun test_agent_finish/0}} 59 ]}. 60 61 %%==================================================================== 62 %% 测试:LLM 回调 63 %%==================================================================== 64 65 test_llm_callbacks() -> 66 Self = self(), 67 Events = ets:new(events, [public, ordered_set]), 68 69 %% 创建 Agent,设置回调 70 {ok, Agent} = beamai_agent:start_link(<<"test_llm_cb">>, #{ 71 system_prompt => <<"You are a helpful assistant. Keep responses short.">>, 72 llm => glm_config(), 73 callbacks => #{ 74 on_llm_start => fun(Prompts, Meta) -> 75 io:format("✓ on_llm_start 触发~n"), 76 io:format(" Prompts: ~p~n", [Prompts]), 77 io:format(" Meta: ~p~n", [Meta]), 78 ets:insert(Events, {erlang:monotonic_time(), llm_start, Prompts}), 79 Self ! {callback, llm_start} 80 end, 81 on_llm_end => fun(Response, Meta) -> 82 io:format("✓ on_llm_end 触发~n"), 83 io:format(" Response: ~p~n", [Response]), 84 io:format(" Meta: ~p~n", [Meta]), 85 ets:insert(Events, {erlang:monotonic_time(), llm_end, Response}), 86 Self ! {callback, llm_end} 87 end 88 } 89 }), 90 91 %% 运行简单对话 92 io:format("~n=== 开始测试 LLM Callbacks ===~n"), 93 {ok, Result} = beamai_agent:run(Agent, <<"Say hello in one sentence.">>), 94 io:format("~nAgent 响应: ~p~n", [Result]), 95 96 %% 等待回调触发 97 receive {callback, llm_start} -> ok after 5000 -> ?assert(false) end, 98 receive {callback, llm_end} -> ok after 5000 -> ?assert(false) end, 99 100 %% 验证事件顺序 101 AllEvents = ets:tab2list(Events), 102 io:format("~n记录的事件: ~p~n", [AllEvents]), 103 ?assert(length(AllEvents) >= 2), 104 105 %% 清理 106 beamai_agent:stop(Agent), 107 ets:delete(Events), 108 io:format("~n=== LLM Callbacks 测试完成 ===~n~n"), 109 ok. 110 111 %%==================================================================== 112 %% 测试:Chain 回调 113 %%==================================================================== 114 115 test_chain_callbacks() -> 116 Self = self(), 117 118 {ok, Agent} = beamai_agent:start_link(<<"test_chain_cb">>, #{ 119 system_prompt => <<"You are a helpful assistant. Keep responses short.">>, 120 llm => glm_config(), 121 callbacks => #{ 122 on_chain_start => fun(Input, Meta) -> 123 io:format("✓ on_chain_start 触发~n"), 124 io:format(" Input: ~p~n", [Input]), 125 io:format(" Meta: ~p~n", [Meta]), 126 Self ! {callback, chain_start, Input} 127 end, 128 on_chain_end => fun(Output, Meta) -> 129 io:format("✓ on_chain_end 触发~n"), 130 io:format(" Output: ~p~n", [Output]), 131 io:format(" Meta: ~p~n", [Meta]), 132 Self ! {callback, chain_end, Output} 133 end 134 } 135 }), 136 137 io:format("~n=== 开始测试 Chain Callbacks ===~n"), 138 TestInput = <<"What is 2+2? Answer with just the number.">>, 139 {ok, _Result} = beamai_agent:run(Agent, TestInput), 140 141 %% 验证 chain_start 收到正确输入 142 receive 143 {callback, chain_start, Input} -> 144 ?assertEqual(TestInput, Input) 145 after 5000 -> 146 ?assert(false) 147 end, 148 149 %% 验证 chain_end 收到输出 150 receive 151 {callback, chain_end, Output} -> 152 ?assert(is_map(Output)), 153 io:format("~nChain 输出: ~p~n", [Output]) 154 after 5000 -> 155 ?assert(false) 156 end, 157 158 beamai_agent:stop(Agent), 159 io:format("~n=== Chain Callbacks 测试完成 ===~n~n"), 160 ok. 161 162 %%==================================================================== 163 %% 测试:Agent Finish 回调 164 %%==================================================================== 165 166 test_agent_finish() -> 167 Self = self(), 168 169 {ok, Agent} = beamai_agent:start_link(<<"test_finish_cb">>, #{ 170 system_prompt => <<"You are a helpful assistant. Keep responses short.">>, 171 llm => glm_config(), 172 callbacks => #{ 173 on_agent_finish => fun(Result, Meta) -> 174 io:format("✓ on_agent_finish 触发~n"), 175 io:format(" Result: ~p~n", [Result]), 176 io:format(" Meta: ~p~n", [Meta]), 177 Self ! {callback, agent_finish, Result} 178 end 179 } 180 }), 181 182 io:format("~n=== 开始测试 Agent Finish Callback ===~n"), 183 {ok, Result} = beamai_agent:run(Agent, <<"Tell me a fun fact in one sentence.">>), 184 185 %% 验证 agent_finish 被调用 186 receive 187 {callback, agent_finish, FinishResult} -> 188 ?assert(is_map(FinishResult)), 189 io:format("~nAgent Finish 结果: ~p~n", [FinishResult]) 190 after 5000 -> 191 ?assert(false) 192 end, 193 194 beamai_agent:stop(Agent), 195 io:format("~n=== Agent Finish Callback 测试完成 ===~n~n"), 196 ok. 197 198 %%==================================================================== 199 %% 测试:多个 Callback 同时工作 200 %%==================================================================== 201 202 multiple_callbacks_test_() -> 203 {setup, 204 fun setup/0, 205 fun cleanup/1, 206 [ 207 {"所有 Callback 类型同时触发", {timeout, 120, fun test_all_callbacks/0}} 208 ]}. 209 210 test_all_callbacks() -> 211 Self = self(), 212 CallbackLog = ets:new(callback_log, [public, duplicate_bag]), 213 214 {ok, Agent} = beamai_agent:start_link(<<"test_all_cb">>, #{ 215 system_prompt => <<"You are a helpful assistant. Keep responses very short.">>, 216 llm => glm_config(), 217 callbacks => #{ 218 on_llm_start => fun(_Prompts, _Meta) -> 219 log_callback(CallbackLog, on_llm_start), 220 Self ! {cb, on_llm_start} 221 end, 222 on_llm_end => fun(_Response, _Meta) -> 223 log_callback(CallbackLog, on_llm_end), 224 Self ! {cb, on_llm_end} 225 end, 226 on_chain_start => fun(_Input, _Meta) -> 227 log_callback(CallbackLog, on_chain_start), 228 Self ! {cb, on_chain_start} 229 end, 230 on_chain_end => fun(_Output, _Meta) -> 231 log_callback(CallbackLog, on_chain_end), 232 Self ! {cb, on_chain_end} 233 end, 234 on_agent_finish => fun(_Result, _Meta) -> 235 log_callback(CallbackLog, on_agent_finish), 236 Self ! {cb, on_agent_finish} 237 end 238 } 239 }), 240 241 io:format("~n=== 开始测试所有 Callbacks ===~n"), 242 {ok, _Result} = beamai_agent:run(Agent, <<"What is 1+1? Just say the number.">>), 243 244 %% 收集所有回调 245 ExpectedCallbacks = [on_chain_start, on_llm_start, on_llm_end, on_agent_finish, on_chain_end], 246 ReceivedCallbacks = collect_callbacks(ExpectedCallbacks, []), 247 248 io:format("~n预期的 Callbacks: ~p~n", [ExpectedCallbacks]), 249 io:format("收到的 Callbacks: ~p~n", [ReceivedCallbacks]), 250 251 %% 验证所有回调都被触发 252 lists:foreach(fun(Expected) -> 253 ?assert(lists:member(Expected, ReceivedCallbacks)), 254 io:format("✓ ~p 已触发~n", [Expected]) 255 end, ExpectedCallbacks), 256 257 %% 显示日志统计 258 AllLogs = ets:tab2list(CallbackLog), 259 io:format("~n总共触发的 Callback 事件数: ~p~n", [length(AllLogs)]), 260 261 beamai_agent:stop(Agent), 262 ets:delete(CallbackLog), 263 io:format("~n=== 所有 Callbacks 测试完成 ===~n~n"), 264 ok. 265 266 %%==================================================================== 267 %% 测试:自定义事件 268 %%==================================================================== 269 270 custom_event_test_() -> 271 {setup, 272 fun setup/0, 273 fun cleanup/1, 274 [ 275 {"触发自定义事件", {timeout, 120, fun test_custom_event/0}} 276 ]}. 277 278 test_custom_event() -> 279 Self = self(), 280 281 {ok, Agent} = beamai_agent:start_link(<<"test_custom">>, #{ 282 system_prompt => <<"You are a helpful assistant.">>, 283 llm => glm_config(), 284 callbacks => #{ 285 on_custom_event => fun(EventName, Data, Meta) -> 286 io:format("✓ on_custom_event 触发~n"), 287 io:format(" Event: ~p~n", [EventName]), 288 io:format(" Data: ~p~n", [Data]), 289 io:format(" Meta: ~p~n", [Meta]), 290 Self ! {custom_event, EventName, Data} 291 end 292 } 293 }), 294 295 io:format("~n=== 开始测试自定义事件 ===~n"), 296 297 %% 触发自定义事件 298 TestData = #{message => <<"Hello">>, value => 42}, 299 beamai_agent:emit_custom_event(Agent, my_event, TestData), 300 301 %% 验证事件被接收 302 receive 303 {custom_event, my_event, Data} -> 304 ?assertEqual(TestData, Data), 305 io:format("~n自定义事件数据验证通过~n") 306 after 2000 -> 307 ?assert(false) 308 end, 309 310 beamai_agent:stop(Agent), 311 io:format("~n=== 自定义事件测试完成 ===~n~n"), 312 ok. 313 314 %%==================================================================== 315 %% 辅助函数 316 %%==================================================================== 317 318 log_callback(Ets, CallbackName) -> 319 Timestamp = erlang:monotonic_time(), 320 ets:insert(Ets, {Timestamp, CallbackName}), 321 io:format(" [~p] ~p~n", [Timestamp, CallbackName]). 322 323 collect_callbacks([], Acc) -> 324 lists:reverse(Acc); 325 collect_callbacks([Expected | Rest], Acc) -> 326 receive 327 {cb, Expected} -> 328 collect_callbacks(Rest, [Expected | Acc]) 329 after 10000 -> 330 io:format("超时等待 callback: ~p~n", [Expected]), 331 collect_callbacks(Rest, Acc) 332 end.