/ test / beamai_agent_callbacks_live_test.erl
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.