raif 1.2.1 → 1.3.0

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.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +29 -935
  3. data/app/assets/builds/raif_admin.css +5 -1
  4. data/app/assets/images/raif-logo-white.svg +8 -0
  5. data/app/assets/stylesheets/raif_admin.scss +4 -0
  6. data/app/jobs/raif/conversation_entry_job.rb +1 -1
  7. data/app/models/raif/agents/re_act_step.rb +1 -2
  8. data/app/models/raif/concerns/has_llm.rb +1 -1
  9. data/app/models/raif/concerns/task_run_args.rb +62 -0
  10. data/app/models/raif/conversation.rb +8 -0
  11. data/app/models/raif/conversation_entry.rb +6 -9
  12. data/app/models/raif/llm.rb +1 -1
  13. data/app/models/raif/llms/open_router.rb +47 -4
  14. data/app/models/raif/task.rb +22 -9
  15. data/app/views/layouts/raif/admin.html.erb +3 -1
  16. data/app/views/raif/conversation_entries/_form.html.erb +1 -1
  17. data/app/views/raif/conversations/_full_conversation.html.erb +3 -6
  18. data/app/views/raif/conversations/_initial_chat_message.html.erb +5 -0
  19. data/config/locales/en.yml +8 -0
  20. data/db/migrate/20250804013843_add_task_run_args_to_raif_tasks.rb +13 -0
  21. data/db/migrate/20250811171150_make_raif_task_creator_optional.rb +8 -0
  22. data/exe/raif +7 -0
  23. data/lib/generators/raif/agent/agent_generator.rb +22 -7
  24. data/lib/generators/raif/agent/templates/agent.rb.tt +20 -24
  25. data/lib/generators/raif/agent/templates/agent_eval_set.rb.tt +48 -0
  26. data/lib/generators/raif/agent/templates/application_agent.rb.tt +0 -2
  27. data/lib/generators/raif/base_generator.rb +19 -0
  28. data/lib/generators/raif/conversation/conversation_generator.rb +21 -2
  29. data/lib/generators/raif/conversation/templates/application_conversation.rb.tt +0 -2
  30. data/lib/generators/raif/conversation/templates/conversation.rb.tt +29 -33
  31. data/lib/generators/raif/conversation/templates/conversation_eval_set.rb.tt +70 -0
  32. data/lib/generators/raif/eval_set/eval_set_generator.rb +28 -0
  33. data/lib/generators/raif/eval_set/templates/eval_set.rb.tt +21 -0
  34. data/lib/generators/raif/evals/setup/setup_generator.rb +47 -0
  35. data/lib/generators/raif/install/install_generator.rb +15 -0
  36. data/lib/generators/raif/install/templates/initializer.rb +14 -3
  37. data/lib/generators/raif/model_tool/model_tool_generator.rb +5 -2
  38. data/lib/generators/raif/model_tool/templates/model_tool.rb.tt +78 -76
  39. data/lib/generators/raif/model_tool/templates/model_tool_invocation_partial.html.erb.tt +10 -0
  40. data/lib/generators/raif/task/task_generator.rb +22 -3
  41. data/lib/generators/raif/task/templates/application_task.rb.tt +0 -2
  42. data/lib/generators/raif/task/templates/task.rb.tt +55 -59
  43. data/lib/generators/raif/task/templates/task_eval_set.rb.tt +54 -0
  44. data/lib/raif/cli/base.rb +39 -0
  45. data/lib/raif/cli/evals.rb +47 -0
  46. data/lib/raif/cli/evals_setup.rb +27 -0
  47. data/lib/raif/cli.rb +67 -0
  48. data/lib/raif/configuration.rb +23 -9
  49. data/lib/raif/engine.rb +2 -1
  50. data/lib/raif/evals/eval.rb +30 -0
  51. data/lib/raif/evals/eval_set.rb +111 -0
  52. data/lib/raif/evals/eval_sets/expectations.rb +53 -0
  53. data/lib/raif/evals/eval_sets/llm_judge_expectations.rb +255 -0
  54. data/lib/raif/evals/expectation_result.rb +39 -0
  55. data/lib/raif/evals/llm_judge.rb +32 -0
  56. data/lib/raif/evals/llm_judges/binary.rb +94 -0
  57. data/lib/raif/evals/llm_judges/comparative.rb +89 -0
  58. data/lib/raif/evals/llm_judges/scored.rb +63 -0
  59. data/lib/raif/evals/llm_judges/summarization.rb +166 -0
  60. data/lib/raif/evals/run.rb +201 -0
  61. data/lib/raif/evals/scoring_rubric.rb +174 -0
  62. data/lib/raif/evals.rb +26 -0
  63. data/lib/raif/llm_registry.rb +33 -0
  64. data/lib/raif/migration_checker.rb +3 -3
  65. data/lib/raif/utils/colors.rb +23 -0
  66. data/lib/raif/utils.rb +1 -0
  67. data/lib/raif/version.rb +1 -1
  68. data/lib/raif.rb +4 -0
  69. data/spec/support/current_temperature_test_tool.rb +34 -0
  70. data/spec/support/test_conversation.rb +1 -1
  71. metadata +37 -3
@@ -57,6 +57,9 @@ body.raif-admin {
57
57
  font-size: 0.9375rem;
58
58
  }
59
59
 
60
+ .raif-admin .navbar-brand-logo {
61
+ width: 100px;
62
+ }
60
63
  .raif-admin h1,
61
64
  .raif-admin h2,
62
65
  .raif-admin h3,
@@ -89,6 +92,7 @@ body.raif-admin {
89
92
  box-shadow: inset -1px 0 0 #e2e8f0;
90
93
  background-color: white !important;
91
94
  padding-top: 1rem;
95
+ height: 100vh;
92
96
  }
93
97
  .raif-admin .sidebar .nav-link {
94
98
  color: #525f7f;
@@ -263,4 +267,4 @@ body.raif-admin {
263
267
  }
264
268
  }
265
269
 
266
- /*# sourceMappingURL=data:application/json;base64,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ZCxxQkFBcUI7QUFDdkI7QUFDQTtFQUNFLHlCQUF5QjtFQUN6QixrQkFBa0I7RUFDbEIsYUFBYTtFQUNiLGdCQUFnQjtFQUNoQixtQkFBbUI7RUFDbkIsY0FBYztFQUNkLHlCQUF5QjtBQUMzQjtBQUNBO0VBQ0UscUJBQXFCO0FBQ3ZCO0FBQ0E7RUFDRSxxQkFBcUI7RUFDckIsYUFBYTtBQUNmO0FBQ0E7RUFDRSwyQkFBMkI7RUFDM0IsNEJBQTRCO0FBQzlCO0FBQ0E7RUFDRSw4QkFBOEI7RUFDOUIsK0JBQStCO0FBQ2pDO0FBQ0E7RUFDRSxjQUFjO0VBQ2QscUJBQXFCO0VBQ3JCLHVCQUF1QjtBQUN6QjtBQUNBO0VBQ0UseUJBQXlCO0VBQ3pCLHFCQUFxQjtFQUNyQixjQUFjO0FBQ2hCO0FBQ0E7RUFDRSx5QkFBeUI7RUFDekIscUJBQXFCO0FBQ3ZCO0FBQ0E7RUFDRSxjQUFjO0FBQ2hCO0FBQ0E7RUFDRSxZQUFZO0VBQ1osa0JBQWtCO0VBQ2xCLDJFQUEyRTtBQUM3RTtBQUNBO0VBQ0UseUNBQXlDO0VBQ3pDLGNBQWM7QUFDaEI7QUFDQTtFQUNFLHlCQUF5QjtBQUMzQjtBQUNBO0VBQ0U7SUFDRSxnQkFBZ0I7SUFDaEIsWUFBWTtJQUNaLGNBQWM7RUFDaEI7RUFDQTtJQUNFLFNBQVM7SUFDVCxnQkFBZ0I7RUFDbEI7RUFDQTtJQUNFLGlCQUFpQjtFQUNuQjtBQUNGIiwiZmlsZSI6InJhaWZfYWRtaW4uY3NzIiwic291cmNlc0NvbnRlbnQiOlsiLmNvbnZlcnNhdGlvbi1jYXJkIC5jYXJkLWJvZHkge1xuICBiYWNrZ3JvdW5kLWNvbG9yOiAjZjhmOWZhO1xufVxuXG4uY29udmVyc2F0aW9uLWhpc3RvcnkgLm1lc3NhZ2Uge1xuICBwb3NpdGlvbjogcmVsYXRpdmU7XG59XG4uY29udmVyc2F0aW9uLWhpc3RvcnkgLm1lc3NhZ2U6bGFzdC1jaGlsZCB7XG4gIG1hcmdpbi1ib3R0b206IDA7XG59XG4uY29udmVyc2F0aW9uLWhpc3RvcnkgLm1lc3NhZ2UudXNlciB7XG4gIGJvcmRlci1sZWZ0OiAzcHggc29saWQgIzNiODJmNjtcbn1cbi5jb252ZXJzYXRpb24taGlzdG9yeSAubWVzc2FnZS51c2VyIC5tZXNzYWdlLWNvbnRlbnQge1xuICBiYWNrZ3JvdW5kLWNvbG9yOiAjZjBmOWZmO1xufVxuLmNvbnZlcnNhdGlvbi1oaXN0b3J5IC5tZXNzYWdlLmFzc2lzdGFudCB7XG4gIGJvcmRlci1sZWZ0OiAzcHggc29saWQgcmdiKDI1LCAxMzUsIDg0KTtcbn1cbi5jb252ZXJzYXRpb24taGlzdG9yeSAubWVzc2FnZS5hc3Npc3RhbnQgLm1lc3NhZ2UtY29udGVudCB7XG4gIGJhY2tncm91bmQtY29sb3I6IHJnYigyMzIsIDI1NSwgMjQ0KTtcbn1cbi5jb252ZXJzYXRpb24taGlzdG9yeSAubWVzc2FnZSAubWVzc2FnZS1jb250ZW50IHtcbiAgYm9yZGVyLXJhZGl1czogNHB4O1xuICBvdmVyZmxvdzogaGlkZGVuO1xuICBib3gtc2hhZG93OiAwIDFweCAycHggcmdiYSgwLCAwLCAwLCAwLjA1KTtcbiAgZm9udC1zaXplOiAwLjg3NXJlbTtcbiAgbGluZS1oZWlnaHQ6IDEuNTtcbiAgY29sb3I6ICMxZTNhOGE7XG59XG4uY29udmVyc2F0aW9uLWhpc3RvcnkgLm1lc3NhZ2UgLm1lc3NhZ2UtY29udGVudCBjb2RlIHtcbiAgZGlzcGxheTogYmxvY2s7XG4gIGZvbnQtZmFtaWx5OiBTRk1vbm8tUmVndWxhciwgTWVubG8sIE1vbmFjbywgQ29uc29sYXMsIFwiTGliZXJhdGlvbiBNb25vXCIsIFwiQ291cmllciBOZXdcIiwgbW9ub3NwYWNlO1xuICB3aGl0ZS1zcGFjZTogcHJlLXdyYXA7XG4gIHdvcmQtYnJlYWs6IGJyZWFrLXdvcmQ7XG4gIGJhY2tncm91bmQ6IG5vbmU7XG4gIGJvcmRlcjogbm9uZTtcbiAgcGFkZGluZzogMDtcbiAgY29sb3I6IGluaGVyaXQ7XG59XG4uY29udmVyc2F0aW9uLWhpc3RvcnkgLm1lc3NhZ2UgLm1lc3NhZ2UtY29udGVudDpsYXN0LWNoaWxkIHtcbiAgbWFyZ2luLWJvdHRvbTogMDtcbn1cbi5jb252ZXJzYXRpb24taGlzdG9yeSAubWVzc2FnZSAubWVzc2FnZS1jb250ZW50IC5tZXNzYWdlLWxhYmVsIHtcbiAgZGlzcGxheTogaW5saW5lLWJsb2NrO1xuICBmb250LXNpemU6IDAuNzVyZW07XG4gIGZvbnQtd2VpZ2h0OiA2MDA7XG4gIHRleHQtdHJhbnNmb3JtOiB1cHBlcmNhc2U7XG4gIGxldHRlci1zcGFjaW5nOiAwLjVweDtcbn1cblxuYm9keS5yYWlmLWFkbWluIHtcbiAgZm9udC1mYW1pbHk6IC1hcHBsZS1zeXN0ZW0sIEJsaW5rTWFjU3lzdGVtRm9udCwgXCJTZWdvZSBVSVwiLCBSb2JvdG8sIEhlbHZldGljYSwgQXJpYWwsIHNhbnMtc2VyaWY7XG4gIGNvbG9yOiAjNTI1ZjdmO1xuICBiYWNrZ3JvdW5kLWNvbG9yOiAjZjhmYWZjO1xuICBsaW5lLWhlaWdodDogMS41O1xuICBmb250LXNpemU6IDAuOTM3NXJlbTtcbn1cblxuLnJhaWYtYWRtaW4gaDEsXG4ucmFpZi1hZG1pbiBoMixcbi5yYWlmLWFkbWluIGgzLFxuLnJhaWYtYWRtaW4gaDQsXG4ucmFpZi1hZG1pbiBoNSxcbi5yYWlmLWFkbWluIGg2IHtcbiAgY29sb3I6ICMxZTNhOGE7XG4gIGZvbnQtd2VpZ2h0OiA2MDA7XG59XG4ucmFpZi1hZG1pbiBhIHtcbiAgY29sb3I6ICMzYjgyZjY7XG4gIHRleHQtZGVjb3JhdGlvbjogbm9uZTtcbiAgdHJhbnNpdGlvbjogYWxsIDAuMnMgZWFzZTtcbn1cbi5yYWlmLWFkbWluIGE6aG92ZXIge1xuICBjb2xvcjogcmdiKDExLjE1MTIxOTUxMjIsIDk5LjEyMTk1MTIxOTUsIDI0Mi44NDg3ODA0ODc4KTtcbn1cbi5yYWlmLWFkbWluIC5uYXZiYXIge1xuICBib3gtc2hhZG93OiAwIDJweCA1cHggMCByZ2JhKDUwLCA1MCwgOTMsIDAuMSk7XG4gIHBhZGRpbmc6IDAuNzVyZW0gMS41cmVtO1xufVxuLnJhaWYtYWRtaW4gLm5hdmJhci5uYXZiYXItZGFyayB7XG4gIGJhY2tncm91bmQ6IGxpbmVhci1ncmFkaWVudCgxMzVkZWcsICMxZTNhOGEsIHJnYigyMC44OTI4NTcxNDI5LCA0MC4zOTI4NTcxNDI5LCA5Ni4xMDcxNDI4NTcxKSkgIWltcG9ydGFudDtcbn1cbi5yYWlmLWFkbWluIC5uYXZiYXIgLm5hdmJhci1icmFuZCB7XG4gIGZvbnQtd2VpZ2h0OiA3MDA7XG4gIGxldHRlci1zcGFjaW5nOiAwLjVweDtcbn1cbi5yYWlmLWFkbWluIC5zaWRlYmFyIHtcbiAgYm94LXNoYWRvdzogaW5zZXQgLTFweCAwIDAgI2UyZThmMDtcbiAgYmFja2dyb3VuZC1jb2xvcjogd2hpdGUgIWltcG9ydGFudDtcbiAgcGFkZGluZy10b3A6IDFyZW07XG59XG4ucmFpZi1hZG1pbiAuc2lkZWJhciAubmF2LWxpbmsge1xuICBjb2xvcjogIzUyNWY3ZjtcbiAgYm9yZGVyLXJhZGl1czogNHB4O1xuICBtYXJnaW46IDAuMjVyZW0gMC43NXJlbTtcbiAgcGFkZGluZzogMC41cmVtIDAuNzVyZW07XG59XG4ucmFpZi1hZG1pbiAuc2lkZWJhciAubmF2LWxpbms6aG92ZXIge1xuICBiYWNrZ3JvdW5kLWNvbG9yOiAjZjBmOWZmO1xufVxuLnJhaWYtYWRtaW4gLnNpZGViYXIgLm5hdi1saW5rLmFjdGl2ZSB7XG4gIGJhY2tncm91bmQtY29sb3I6IHJnYmEoNTksIDEzMCwgMjQ2LCAwLjEpO1xuICBjb2xvcjogIzNiODJmNjtcbiAgZm9udC13ZWlnaHQ6IDUwMDtcbn1cbi5yYWlmLWFkbWluIG1haW4ge1xuICBwYWRkaW5nLXRvcDogMS41cmVtO1xuICBwYWRkaW5nLWJvdHRvbTogM3JlbTtcbn1cbi5yYWlmLWFkbWluIC5jYXJkIHtcbiAgYm9yZGVyOiBub25lO1xuICBib3JkZXItcmFkaXVzOiA0cHg7XG4gIGJveC1zaGFkb3c6IDAgNHB4IDZweCByZ2JhKDUwLCA1MCwgOTMsIDAuMTEpLCAwIDFweCAzcHggcmdiYSgwLCAwLCAwLCAwLjA4KTtcbiAgbWFyZ2luLWJvdHRvbTogMS41cmVtO1xuICBvdmVyZmxvdzogaGlkZGVuO1xufVxuLnJhaWYtYWRtaW4gLmNhcmQgLmNhcmQtaGVhZGVyIHtcbiAgYmFja2dyb3VuZC1jb2xvcjogd2hpdGU7XG4gIGJvcmRlci1ib3R0b206IDFweCBzb2xpZCAjZTJlOGYwO1xuICBwYWRkaW5nOiAxcmVtIDEuMjVyZW07XG59XG4ucmFpZi1hZG1pbiAuY2FyZCAuY2FyZC1oZWFkZXIgaDUge1xuICBtYXJnaW4tYm90dG9tOiAwO1xuICBmb250LXNpemU6IDFyZW07XG4gIGZvbnQtd2VpZ2h0OiA2MDA7XG59XG4ucmFpZi1hZG1pbiAuY2FyZCAuY2FyZC1ib2R5IHtcbiAgcGFkZGluZzogMS4yNXJlbTtcbn1cbi5yYWlmLWFkbWluIC5jYXJkLmNvbnZlcnNhdGlvbi1jYXJkIC5jYXJkLWJvZHkge1xuICBiYWNrZ3JvdW5kLWNvbG9yOiAjZjhmOWZhO1xufVxuLnJhaWYtYWRtaW4gLnRhYmxlIHtcbiAgbWFyZ2luLWJvdHRvbTogMDtcbn1cbi5yYWlmLWFkbWluIC50YWJsZSB0aCB7XG4gIGZvbnQtd2VpZ2h0OiA2MDA7XG4gIGNvbG9yOiAjMWUzYThhO1xuICBib3JkZXItdG9wOiBub25lO1xuICBwYWRkaW5nOiAxcmVtIDAuNzVyZW07XG4gIGZvbnQtc2l6ZTogMC44MTI1cmVtO1xuICB0ZXh0LXRyYW5zZm9ybTogdXBwZXJjYXNlO1xuICBsZXR0ZXItc3BhY2luZzogMC41cHg7XG59XG4ucmFpZi1hZG1pbiAudGFibGUgdGQge1xuICBwYWRkaW5nOiAxcmVtIDAuNzVyZW07XG4gIHZlcnRpY2FsLWFsaWduOiBtaWRkbGU7XG4gIGJvcmRlci1jb2xvcjogI2UyZThmMDtcbn1cbi5yYWlmLWFkbWluIC50YWJsZS50YWJsZS1ob3ZlciB0Ym9keSB0cjpob3ZlciB7XG4gIGJhY2tncm91bmQtY29sb3I6ICNmMGY5ZmY7XG59XG4ucmFpZi1hZG1pbiAudGFibGUudGFibGUtc3RyaXBlZCB0Ym9keSB0cjpudGgtb2YtdHlwZShvZGQpIHtcbiAgYmFja2dyb3VuZC1jb2xvcjogcmdiYSgyNDAsIDI0OSwgMjU1LCAwLjUpO1xufVxuLnJhaWYtYWRtaW4gLnRhYmxlLXJlc3BvbnNpdmUge1xuICBib3JkZXItcmFkaXVzOiA0cHg7XG4gIGJveC1zaGFkb3c6IDAgNHB4IDZweCByZ2JhKDUwLCA1MCwgOTMsIDAuMTEpLCAwIDFweCAzcHggcmdiYSgwLCAwLCAwLCAwLjA4KTtcbiAgYmFja2dyb3VuZC1jb2xvcjogd2hpdGU7XG4gIG92ZXJmbG93OiBoaWRkZW47XG59XG4ucmFpZi1hZG1pbiAuYmFkZ2Uge1xuICBmb250LXdlaWdodDogNTAwO1xuICBwYWRkaW5nOiAwLjRlbSAwLjY1ZW07XG4gIGZvbnQtc2l6ZTogMC43NWVtO1xuICBib3JkZXItcmFkaXVzOiA1MHJlbTtcbn1cbi5yYWlmLWFkbWluIC5iYWRnZS5iZy1zdWNjZXNzIHtcbiAgYmFja2dyb3VuZC1jb2xvcjogIzBlYTVlOSAhaW1wb3J0YW50O1xufVxuLnJhaWYtYWRtaW4gLmJhZGdlLmJnLWRhbmdlciB7XG4gIGJhY2tncm91bmQtY29sb3I6ICNlZjQ0NDQgIWltcG9ydGFudDtcbn1cbi5yYWlmLWFkbWluIC5iYWRnZS5iZy13YXJuaW5nIHtcbiAgYmFja2dyb3VuZC1jb2xvcjogI2Y1OWUwYiAhaW1wb3J0YW50O1xuICBjb2xvcjogd2hpdGUgIWltcG9ydGFudDtcbn1cbi5yYWlmLWFkbWluIC5iYWRnZS5iZy1zZWNvbmRhcnkge1xuICBiYWNrZ3JvdW5kLWNvbG9yOiAjODg5OGFhICFpbXBvcnRhbnQ7XG59XG4ucmFpZi1hZG1pbiAuYnRuIHtcbiAgZm9udC13ZWlnaHQ6IDUwMDtcbiAgcGFkZGluZzogMC41cmVtIDFyZW07XG4gIGJvcmRlci1yYWRpdXM6IDRweDtcbiAgdHJhbnNpdGlvbjogYWxsIDAuMnMgZWFzZTtcbiAgYm94LXNoYWRvdzogMCA0cHggNnB4IHJnYmEoNTAsIDUwLCA5MywgMC4xMSksIDAgMXB4IDNweCByZ2JhKDAsIDAsIDAsIDAuMDgpO1xufVxuLnJhaWYtYWRtaW4gLmJ0bi5idG4tb3V0bGluZS1zZWNvbmRhcnkge1xuICBjb2xvcjogIzUyNWY3ZjtcbiAgYm9yZGVyLWNvbG9yOiAjZTJlOGYwO1xufVxuLnJhaWYtYWRtaW4gLmJ0bi5idG4tb3V0bGluZS1zZWNvbmRhcnk6aG92ZXIge1xuICBiYWNrZ3JvdW5kLWNvbG9yOiAjZjBmOWZmO1xuICBjb2xvcjogIzFlM2E4YTtcbiAgYm9yZGVyLWNvbG9yOiAjZTJlOGYwO1xufVxuLnJhaWYtYWRtaW4gcHJlIHtcbiAgYmFja2dyb3VuZC1jb2xvcjogI2YwZjlmZjtcbiAgYm9yZGVyLXJhZGl1czogNHB4O1xuICBwYWRkaW5nOiAxcmVtO1xuICBtYXJnaW4tYm90dG9tOiAwO1xuICBmb250LXNpemU6IDAuODc1cmVtO1xuICBjb2xvcjogIzFlM2E4YTtcbiAgYm9yZGVyOiAxcHggc29saWQgI2UyZThmMDtcbn1cbi5yYWlmLWFkbWluIC5wcmUtd3JhcCB7XG4gIHdoaXRlLXNwYWNlOiBwcmUtd3JhcDtcbn1cbi5yYWlmLWFkbWluIC5saXN0LWdyb3VwLWl0ZW0ge1xuICBib3JkZXItY29sb3I6ICNlMmU4ZjA7XG4gIHBhZGRpbmc6IDFyZW07XG59XG4ucmFpZi1hZG1pbiAubGlzdC1ncm91cC1pdGVtOmZpcnN0LWNoaWxkIHtcbiAgYm9yZGVyLXRvcC1sZWZ0LXJhZGl1czogNHB4O1xuICBib3JkZXItdG9wLXJpZ2h0LXJhZGl1czogNHB4O1xufVxuLnJhaWYtYWRtaW4gLmxpc3QtZ3JvdXAtaXRlbTpsYXN0LWNoaWxkIHtcbiAgYm9yZGVyLWJvdHRvbS1sZWZ0LXJhZGl1czogNHB4O1xuICBib3JkZXItYm90dG9tLXJpZ2h0LXJhZGl1czogNHB4O1xufVxuLnJhaWYtYWRtaW4gLnBhZ2luYXRpb24gLnBhZ2UtbGluayB7XG4gIGNvbG9yOiAjNTI1ZjdmO1xuICBib3JkZXItY29sb3I6ICNlMmU4ZjA7XG4gIHBhZGRpbmc6IDAuNXJlbSAwLjc1cmVtO1xufVxuLnJhaWYtYWRtaW4gLnBhZ2luYXRpb24gLnBhZ2UtbGluazpob3ZlciB7XG4gIGJhY2tncm91bmQtY29sb3I6ICNmMGY5ZmY7XG4gIGJvcmRlci1jb2xvcjogI2UyZThmMDtcbiAgY29sb3I6ICMxZTNhOGE7XG59XG4ucmFpZi1hZG1pbiAucGFnaW5hdGlvbiAucGFnZS1pdGVtLmFjdGl2ZSAucGFnZS1saW5rIHtcbiAgYmFja2dyb3VuZC1jb2xvcjogIzNiODJmNjtcbiAgYm9yZGVyLWNvbG9yOiAjM2I4MmY2O1xufVxuLnJhaWYtYWRtaW4gLnJvdy5tYi0zIC5jb2wtbWQtMyBzdHJvbmcge1xuICBjb2xvcjogIzFlM2E4YTtcbn1cbi5yYWlmLWFkbWluIC5hbGVydCB7XG4gIGJvcmRlcjogbm9uZTtcbiAgYm9yZGVyLXJhZGl1czogNHB4O1xuICBib3gtc2hhZG93OiAwIDRweCA2cHggcmdiYSg1MCwgNTAsIDkzLCAwLjExKSwgMCAxcHggM3B4IHJnYmEoMCwgMCwgMCwgMC4wOCk7XG59XG4ucmFpZi1hZG1pbiAuYWxlcnQuYWxlcnQtaW5mbyB7XG4gIGJhY2tncm91bmQtY29sb3I6IHJnYmEoNTksIDEzMCwgMjQ2LCAwLjEpO1xuICBjb2xvcjogIzNiODJmNjtcbn1cbi5yYWlmLWFkbWluIC50ZXh0LW11dGVkIHtcbiAgY29sb3I6ICM4ODk4YWEgIWltcG9ydGFudDtcbn1cbkBtZWRpYSAobWF4LXdpZHRoOiA3NjcuOThweCkge1xuICAucmFpZi1hZG1pbiAuc2lkZWJhciB7XG4gICAgcG9zaXRpb246IHN0YXRpYztcbiAgICBoZWlnaHQ6IGF1dG87XG4gICAgcGFkZGluZy10b3A6IDA7XG4gIH1cbiAgLnJhaWYtYWRtaW4gLnNpZGViYXIgLm5hdi1saW5rIHtcbiAgICBtYXJnaW46IDA7XG4gICAgYm9yZGVyLXJhZGl1czogMDtcbiAgfVxuICAucmFpZi1hZG1pbiBtYWluIHtcbiAgICBwYWRkaW5nLXRvcDogMXJlbTtcbiAgfVxufVxuIl19 */
270
+ /*# sourceMappingURL=data:application/json;base64,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QUNBO0VBQ0UseUJBQXlCO0VBQ3pCLGNBQWM7RUFDZCxxQkFBcUI7QUFDdkI7QUFDQTtFQUNFLHlCQUF5QjtFQUN6QixrQkFBa0I7RUFDbEIsYUFBYTtFQUNiLGdCQUFnQjtFQUNoQixtQkFBbUI7RUFDbkIsY0FBYztFQUNkLHlCQUF5QjtBQUMzQjtBQUNBO0VBQ0UscUJBQXFCO0FBQ3ZCO0FBQ0E7RUFDRSxxQkFBcUI7RUFDckIsYUFBYTtBQUNmO0FBQ0E7RUFDRSwyQkFBMkI7RUFDM0IsNEJBQTRCO0FBQzlCO0FBQ0E7RUFDRSw4QkFBOEI7RUFDOUIsK0JBQStCO0FBQ2pDO0FBQ0E7RUFDRSxjQUFjO0VBQ2QscUJBQXFCO0VBQ3JCLHVCQUF1QjtBQUN6QjtBQUNBO0VBQ0UseUJBQXlCO0VBQ3pCLHFCQUFxQjtFQUNyQixjQUFjO0FBQ2hCO0FBQ0E7RUFDRSx5QkFBeUI7RUFDekIscUJBQXFCO0FBQ3ZCO0FBQ0E7RUFDRSxjQUFjO0FBQ2hCO0FBQ0E7RUFDRSxZQUFZO0VBQ1osa0JBQWtCO0VBQ2xCLDJFQUEyRTtBQUM3RTtBQUNBO0VBQ0UseUNBQXlDO0VBQ3pDLGNBQWM7QUFDaEI7QUFDQTtFQUNFLHlCQUF5QjtBQUMzQjtBQUNBO0VBQ0U7SUFDRSxnQkFBZ0I7SUFDaEIsWUFBWTtJQUNaLGNBQWM7RUFDaEI7RUFDQTtJQUNFLFNBQVM7SUFDVCxnQkFBZ0I7RUFDbEI7RUFDQTtJQUNFLGlCQUFpQjtFQUNuQjtBQUNGIiwiZmlsZSI6InJhaWZfYWRtaW4uY3NzIiwic291cmNlc0NvbnRlbnQiOlsiLmNvbnZlcnNhdGlvbi1jYXJkIC5jYXJkLWJvZHkge1xuICBiYWNrZ3JvdW5kLWNvbG9yOiAjZjhmOWZhO1xufVxuXG4uY29udmVyc2F0aW9uLWhpc3RvcnkgLm1lc3NhZ2Uge1xuICBwb3NpdGlvbjogcmVsYXRpdmU7XG59XG4uY29udmVyc2F0aW9uLWhpc3RvcnkgLm1lc3NhZ2U6bGFzdC1jaGlsZCB7XG4gIG1hcmdpbi1ib3R0b206IDA7XG59XG4uY29udmVyc2F0aW9uLWhpc3RvcnkgLm1lc3NhZ2UudXNlciB7XG4gIGJvcmRlci1sZWZ0OiAzcHggc29saWQgIzNiODJmNjtcbn1cbi5jb252ZXJzYXRpb24taGlzdG9yeSAubWVzc2FnZS51c2VyIC5tZXNzYWdlLWNvbnRlbnQge1xuICBiYWNrZ3JvdW5kLWNvbG9yOiAjZjBmOWZmO1xufVxuLmNvbnZlcnNhdGlvbi1oaXN0b3J5IC5tZXNzYWdlLmFzc2lzdGFudCB7XG4gIGJvcmRlci1sZWZ0OiAzcHggc29saWQgcmdiKDI1LCAxMzUsIDg0KTtcbn1cbi5jb252ZXJzYXRpb24taGlzdG9yeSAubWVzc2FnZS5hc3Npc3RhbnQgLm1lc3NhZ2UtY29udGVudCB7XG4gIGJhY2tncm91bmQtY29sb3I6IHJnYigyMzIsIDI1NSwgMjQ0KTtcbn1cbi5jb252ZXJzYXRpb24taGlzdG9yeSAubWVzc2FnZSAubWVzc2FnZS1jb250ZW50IHtcbiAgYm9yZGVyLXJhZGl1czogNHB4O1xuICBvdmVyZmxvdzogaGlkZGVuO1xuICBib3gtc2hhZG93OiAwIDFweCAycHggcmdiYSgwLCAwLCAwLCAwLjA1KTtcbiAgZm9udC1zaXplOiAwLjg3NXJlbTtcbiAgbGluZS1oZWlnaHQ6IDEuNTtcbiAgY29sb3I6ICMxZTNhOGE7XG59XG4uY29udmVyc2F0aW9uLWhpc3RvcnkgLm1lc3NhZ2UgLm1lc3NhZ2UtY29udGVudCBjb2RlIHtcbiAgZGlzcGxheTogYmxvY2s7XG4gIGZvbnQtZmFtaWx5OiBTRk1vbm8tUmVndWxhciwgTWVubG8sIE1vbmFjbywgQ29uc29sYXMsIFwiTGliZXJhdGlvbiBNb25vXCIsIFwiQ291cmllciBOZXdcIiwgbW9ub3NwYWNlO1xuICB3aGl0ZS1zcGFjZTogcHJlLXdyYXA7XG4gIHdvcmQtYnJlYWs6IGJyZWFrLXdvcmQ7XG4gIGJhY2tncm91bmQ6IG5vbmU7XG4gIGJvcmRlcjogbm9uZTtcbiAgcGFkZGluZzogMDtcbiAgY29sb3I6IGluaGVyaXQ7XG59XG4uY29udmVyc2F0aW9uLWhpc3RvcnkgLm1lc3NhZ2UgLm1lc3NhZ2UtY29udGVudDpsYXN0LWNoaWxkIHtcbiAgbWFyZ2luLWJvdHRvbTogMDtcbn1cbi5jb252ZXJzYXRpb24taGlzdG9yeSAubWVzc2FnZSAubWVzc2FnZS1jb250ZW50IC5tZXNzYWdlLWxhYmVsIHtcbiAgZGlzcGxheTogaW5saW5lLWJsb2NrO1xuICBmb250LXNpemU6IDAuNzVyZW07XG4gIGZvbnQtd2VpZ2h0OiA2MDA7XG4gIHRleHQtdHJhbnNmb3JtOiB1cHBlcmNhc2U7XG4gIGxldHRlci1zcGFjaW5nOiAwLjVweDtcbn1cblxuYm9keS5yYWlmLWFkbWluIHtcbiAgZm9udC1mYW1pbHk6IC1hcHBsZS1zeXN0ZW0sIEJsaW5rTWFjU3lzdGVtRm9udCwgXCJTZWdvZSBVSVwiLCBSb2JvdG8sIEhlbHZldGljYSwgQXJpYWwsIHNhbnMtc2VyaWY7XG4gIGNvbG9yOiAjNTI1ZjdmO1xuICBiYWNrZ3JvdW5kLWNvbG9yOiAjZjhmYWZjO1xuICBsaW5lLWhlaWdodDogMS41O1xuICBmb250LXNpemU6IDAuOTM3NXJlbTtcbn1cblxuLnJhaWYtYWRtaW4gLm5hdmJhci1icmFuZC1sb2dvIHtcbiAgd2lkdGg6IDEwMHB4O1xufVxuLnJhaWYtYWRtaW4gaDEsXG4ucmFpZi1hZG1pbiBoMixcbi5yYWlmLWFkbWluIGgzLFxuLnJhaWYtYWRtaW4gaDQsXG4ucmFpZi1hZG1pbiBoNSxcbi5yYWlmLWFkbWluIGg2IHtcbiAgY29sb3I6ICMxZTNhOGE7XG4gIGZvbnQtd2VpZ2h0OiA2MDA7XG59XG4ucmFpZi1hZG1pbiBhIHtcbiAgY29sb3I6ICMzYjgyZjY7XG4gIHRleHQtZGVjb3JhdGlvbjogbm9uZTtcbiAgdHJhbnNpdGlvbjogYWxsIDAuMnMgZWFzZTtcbn1cbi5yYWlmLWFkbWluIGE6aG92ZXIge1xuICBjb2xvcjogcmdiKDExLjE1MTIxOTUxMjIsIDk5LjEyMTk1MTIxOTUsIDI0Mi44NDg3ODA0ODc4KTtcbn1cbi5yYWlmLWFkbWluIC5uYXZiYXIge1xuICBib3gtc2hhZG93OiAwIDJweCA1cHggMCByZ2JhKDUwLCA1MCwgOTMsIDAuMSk7XG4gIHBhZGRpbmc6IDAuNzVyZW0gMS41cmVtO1xufVxuLnJhaWYtYWRtaW4gLm5hdmJhci5uYXZiYXItZGFyayB7XG4gIGJhY2tncm91bmQ6IGxpbmVhci1ncmFkaWVudCgxMzVkZWcsICMxZTNhOGEsIHJnYigyMC44OTI4NTcxNDI5LCA0MC4zOTI4NTcxNDI5LCA5Ni4xMDcxNDI4NTcxKSkgIWltcG9ydGFudDtcbn1cbi5yYWlmLWFkbWluIC5uYXZiYXIgLm5hdmJhci1icmFuZCB7XG4gIGZvbnQtd2VpZ2h0OiA3MDA7XG4gIGxldHRlci1zcGFjaW5nOiAwLjVweDtcbn1cbi5yYWlmLWFkbWluIC5zaWRlYmFyIHtcbiAgYm94LXNoYWRvdzogaW5zZXQgLTFweCAwIDAgI2UyZThmMDtcbiAgYmFja2dyb3VuZC1jb2xvcjogd2hpdGUgIWltcG9ydGFudDtcbiAgcGFkZGluZy10b3A6IDFyZW07XG4gIGhlaWdodDogMTAwdmg7XG59XG4ucmFpZi1hZG1pbiAuc2lkZWJhciAubmF2LWxpbmsge1xuICBjb2xvcjogIzUyNWY3ZjtcbiAgYm9yZGVyLXJhZGl1czogNHB4O1xuICBtYXJnaW46IDAuMjVyZW0gMC43NXJlbTtcbiAgcGFkZGluZzogMC41cmVtIDAuNzVyZW07XG59XG4ucmFpZi1hZG1pbiAuc2lkZWJhciAubmF2LWxpbms6aG92ZXIge1xuICBiYWNrZ3JvdW5kLWNvbG9yOiAjZjBmOWZmO1xufVxuLnJhaWYtYWRtaW4gLnNpZGViYXIgLm5hdi1saW5rLmFjdGl2ZSB7XG4gIGJhY2tncm91bmQtY29sb3I6IHJnYmEoNTksIDEzMCwgMjQ2LCAwLjEpO1xuICBjb2xvcjogIzNiODJmNjtcbiAgZm9udC13ZWlnaHQ6IDUwMDtcbn1cbi5yYWlmLWFkbWluIG1haW4ge1xuICBwYWRkaW5nLXRvcDogMS41cmVtO1xuICBwYWRkaW5nLWJvdHRvbTogM3JlbTtcbn1cbi5yYWlmLWFkbWluIC5jYXJkIHtcbiAgYm9yZGVyOiBub25lO1xuICBib3JkZXItcmFkaXVzOiA0cHg7XG4gIGJveC1zaGFkb3c6IDAgNHB4IDZweCByZ2JhKDUwLCA1MCwgOTMsIDAuMTEpLCAwIDFweCAzcHggcmdiYSgwLCAwLCAwLCAwLjA4KTtcbiAgbWFyZ2luLWJvdHRvbTogMS41cmVtO1xuICBvdmVyZmxvdzogaGlkZGVuO1xufVxuLnJhaWYtYWRtaW4gLmNhcmQgLmNhcmQtaGVhZGVyIHtcbiAgYmFja2dyb3VuZC1jb2xvcjogd2hpdGU7XG4gIGJvcmRlci1ib3R0b206IDFweCBzb2xpZCAjZTJlOGYwO1xuICBwYWRkaW5nOiAxcmVtIDEuMjVyZW07XG59XG4ucmFpZi1hZG1pbiAuY2FyZCAuY2FyZC1oZWFkZXIgaDUge1xuICBtYXJnaW4tYm90dG9tOiAwO1xuICBmb250LXNpemU6IDFyZW07XG4gIGZvbnQtd2VpZ2h0OiA2MDA7XG59XG4ucmFpZi1hZG1pbiAuY2FyZCAuY2FyZC1ib2R5IHtcbiAgcGFkZGluZzogMS4yNXJlbTtcbn1cbi5yYWlmLWFkbWluIC5jYXJkLmNvbnZlcnNhdGlvbi1jYXJkIC5jYXJkLWJvZHkge1xuICBiYWNrZ3JvdW5kLWNvbG9yOiAjZjhmOWZhO1xufVxuLnJhaWYtYWRtaW4gLnRhYmxlIHtcbiAgbWFyZ2luLWJvdHRvbTogMDtcbn1cbi5yYWlmLWFkbWluIC50YWJsZSB0aCB7XG4gIGZvbnQtd2VpZ2h0OiA2MDA7XG4gIGNvbG9yOiAjMWUzYThhO1xuICBib3JkZXItdG9wOiBub25lO1xuICBwYWRkaW5nOiAxcmVtIDAuNzVyZW07XG4gIGZvbnQtc2l6ZTogMC44MTI1cmVtO1xuICB0ZXh0LXRyYW5zZm9ybTogdXBwZXJjYXNlO1xuICBsZXR0ZXItc3BhY2luZzogMC41cHg7XG59XG4ucmFpZi1hZG1pbiAudGFibGUgdGQge1xuICBwYWRkaW5nOiAxcmVtIDAuNzVyZW07XG4gIHZlcnRpY2FsLWFsaWduOiBtaWRkbGU7XG4gIGJvcmRlci1jb2xvcjogI2UyZThmMDtcbn1cbi5yYWlmLWFkbWluIC50YWJsZS50YWJsZS1ob3ZlciB0Ym9keSB0cjpob3ZlciB7XG4gIGJhY2tncm91bmQtY29sb3I6ICNmMGY5ZmY7XG59XG4ucmFpZi1hZG1pbiAudGFibGUudGFibGUtc3RyaXBlZCB0Ym9keSB0cjpudGgtb2YtdHlwZShvZGQpIHtcbiAgYmFja2dyb3VuZC1jb2xvcjogcmdiYSgyNDAsIDI0OSwgMjU1LCAwLjUpO1xufVxuLnJhaWYtYWRtaW4gLnRhYmxlLXJlc3BvbnNpdmUge1xuICBib3JkZXItcmFkaXVzOiA0cHg7XG4gIGJveC1zaGFkb3c6IDAgNHB4IDZweCByZ2JhKDUwLCA1MCwgOTMsIDAuMTEpLCAwIDFweCAzcHggcmdiYSgwLCAwLCAwLCAwLjA4KTtcbiAgYmFja2dyb3VuZC1jb2xvcjogd2hpdGU7XG4gIG92ZXJmbG93OiBoaWRkZW47XG59XG4ucmFpZi1hZG1pbiAuYmFkZ2Uge1xuICBmb250LXdlaWdodDogNTAwO1xuICBwYWRkaW5nOiAwLjRlbSAwLjY1ZW07XG4gIGZvbnQtc2l6ZTogMC43NWVtO1xuICBib3JkZXItcmFkaXVzOiA1MHJlbTtcbn1cbi5yYWlmLWFkbWluIC5iYWRnZS5iZy1zdWNjZXNzIHtcbiAgYmFja2dyb3VuZC1jb2xvcjogIzBlYTVlOSAhaW1wb3J0YW50O1xufVxuLnJhaWYtYWRtaW4gLmJhZGdlLmJnLWRhbmdlciB7XG4gIGJhY2tncm91bmQtY29sb3I6ICNlZjQ0NDQgIWltcG9ydGFudDtcbn1cbi5yYWlmLWFkbWluIC5iYWRnZS5iZy13YXJuaW5nIHtcbiAgYmFja2dyb3VuZC1jb2xvcjogI2Y1OWUwYiAhaW1wb3J0YW50O1xuICBjb2xvcjogd2hpdGUgIWltcG9ydGFudDtcbn1cbi5yYWlmLWFkbWluIC5iYWRnZS5iZy1zZWNvbmRhcnkge1xuICBiYWNrZ3JvdW5kLWNvbG9yOiAjODg5OGFhICFpbXBvcnRhbnQ7XG59XG4ucmFpZi1hZG1pbiAuYnRuIHtcbiAgZm9udC13ZWlnaHQ6IDUwMDtcbiAgcGFkZGluZzogMC41cmVtIDFyZW07XG4gIGJvcmRlci1yYWRpdXM6IDRweDtcbiAgdHJhbnNpdGlvbjogYWxsIDAuMnMgZWFzZTtcbiAgYm94LXNoYWRvdzogMCA0cHggNnB4IHJnYmEoNTAsIDUwLCA5MywgMC4xMSksIDAgMXB4IDNweCByZ2JhKDAsIDAsIDAsIDAuMDgpO1xufVxuLnJhaWYtYWRtaW4gLmJ0bi5idG4tb3V0bGluZS1zZWNvbmRhcnkge1xuICBjb2xvcjogIzUyNWY3ZjtcbiAgYm9yZGVyLWNvbG9yOiAjZTJlOGYwO1xufVxuLnJhaWYtYWRtaW4gLmJ0bi5idG4tb3V0bGluZS1zZWNvbmRhcnk6aG92ZXIge1xuICBiYWNrZ3JvdW5kLWNvbG9yOiAjZjBmOWZmO1xuICBjb2xvcjogIzFlM2E4YTtcbiAgYm9yZGVyLWNvbG9yOiAjZTJlOGYwO1xufVxuLnJhaWYtYWRtaW4gcHJlIHtcbiAgYmFja2dyb3VuZC1jb2xvcjogI2YwZjlmZjtcbiAgYm9yZGVyLXJhZGl1czogNHB4O1xuICBwYWRkaW5nOiAxcmVtO1xuICBtYXJnaW4tYm90dG9tOiAwO1xuICBmb250LXNpemU6IDAuODc1cmVtO1xuICBjb2xvcjogIzFlM2E4YTtcbiAgYm9yZGVyOiAxcHggc29saWQgI2UyZThmMDtcbn1cbi5yYWlmLWFkbWluIC5wcmUtd3JhcCB7XG4gIHdoaXRlLXNwYWNlOiBwcmUtd3JhcDtcbn1cbi5yYWlmLWFkbWluIC5saXN0LWdyb3VwLWl0ZW0ge1xuICBib3JkZXItY29sb3I6ICNlMmU4ZjA7XG4gIHBhZGRpbmc6IDFyZW07XG59XG4ucmFpZi1hZG1pbiAubGlzdC1ncm91cC1pdGVtOmZpcnN0LWNoaWxkIHtcbiAgYm9yZGVyLXRvcC1sZWZ0LXJhZGl1czogNHB4O1xuICBib3JkZXItdG9wLXJpZ2h0LXJhZGl1czogNHB4O1xufVxuLnJhaWYtYWRtaW4gLmxpc3QtZ3JvdXAtaXRlbTpsYXN0LWNoaWxkIHtcbiAgYm9yZGVyLWJvdHRvbS1sZWZ0LXJhZGl1czogNHB4O1xuICBib3JkZXItYm90dG9tLXJpZ2h0LXJhZGl1czogNHB4O1xufVxuLnJhaWYtYWRtaW4gLnBhZ2luYXRpb24gLnBhZ2UtbGluayB7XG4gIGNvbG9yOiAjNTI1ZjdmO1xuICBib3JkZXItY29sb3I6ICNlMmU4ZjA7XG4gIHBhZGRpbmc6IDAuNXJlbSAwLjc1cmVtO1xufVxuLnJhaWYtYWRtaW4gLnBhZ2luYXRpb24gLnBhZ2UtbGluazpob3ZlciB7XG4gIGJhY2tncm91bmQtY29sb3I6ICNmMGY5ZmY7XG4gIGJvcmRlci1jb2xvcjogI2UyZThmMDtcbiAgY29sb3I6ICMxZTNhOGE7XG59XG4ucmFpZi1hZG1pbiAucGFnaW5hdGlvbiAucGFnZS1pdGVtLmFjdGl2ZSAucGFnZS1saW5rIHtcbiAgYmFja2dyb3VuZC1jb2xvcjogIzNiODJmNjtcbiAgYm9yZGVyLWNvbG9yOiAjM2I4MmY2O1xufVxuLnJhaWYtYWRtaW4gLnJvdy5tYi0zIC5jb2wtbWQtMyBzdHJvbmcge1xuICBjb2xvcjogIzFlM2E4YTtcbn1cbi5yYWlmLWFkbWluIC5hbGVydCB7XG4gIGJvcmRlcjogbm9uZTtcbiAgYm9yZGVyLXJhZGl1czogNHB4O1xuICBib3gtc2hhZG93OiAwIDRweCA2cHggcmdiYSg1MCwgNTAsIDkzLCAwLjExKSwgMCAxcHggM3B4IHJnYmEoMCwgMCwgMCwgMC4wOCk7XG59XG4ucmFpZi1hZG1pbiAuYWxlcnQuYWxlcnQtaW5mbyB7XG4gIGJhY2tncm91bmQtY29sb3I6IHJnYmEoNTksIDEzMCwgMjQ2LCAwLjEpO1xuICBjb2xvcjogIzNiODJmNjtcbn1cbi5yYWlmLWFkbWluIC50ZXh0LW11dGVkIHtcbiAgY29sb3I6ICM4ODk4YWEgIWltcG9ydGFudDtcbn1cbkBtZWRpYSAobWF4LXdpZHRoOiA3NjcuOThweCkge1xuICAucmFpZi1hZG1pbiAuc2lkZWJhciB7XG4gICAgcG9zaXRpb246IHN0YXRpYztcbiAgICBoZWlnaHQ6IGF1dG87XG4gICAgcGFkZGluZy10b3A6IDA7XG4gIH1cbiAgLnJhaWYtYWRtaW4gLnNpZGViYXIgLm5hdi1saW5rIHtcbiAgICBtYXJnaW46IDA7XG4gICAgYm9yZGVyLXJhZGl1czogMDtcbiAgfVxuICAucmFpZi1hZG1pbiBtYWluIHtcbiAgICBwYWRkaW5nLXRvcDogMXJlbTtcbiAgfVxufVxuIl19 */
@@ -0,0 +1,8 @@
1
+
2
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="3187.5" height="1328.6164850518683" viewBox="0 -93.75 3187.5 1328.6164850518683">
3
+
4
+ <g transform="scale(9.375) translate(10, 10)">
5
+ <defs id="SvgjsDefs4139"/><g id="SvgjsG4140" featureKey="5TMTKC-0" transform="matrix(-0.5092531442642212,0,0,0.5092531442642212,79.9527359008789,-10.000000953674316)" fill="#fff"><defs xmlns="http://www.w3.org/2000/svg"/><g xmlns="http://www.w3.org/2000/svg"><path d="M7 122c-8,20 -10,46 -2,81 1,-27 4,-54 2,-81z" style="fill: #3b82f6;"/><path d="M56 70c-10,5 -19,12 -27,19 3,40 -9,73 -18,110 4,-7 8,-15 12,-22 16,-32 33,-63 33,-107z" style="fill: #3b82f6;"/><path d="M114 36c-7,5 -14,9 -21,13 -6,60 -37,89 -68,134 40,-51 80,-74 89,-147z" style="fill: #3b82f6;"/><path d="M157 60c-2,-21 -6,-42 -12,-60 -5,88 -40,112 -97,166 44,-34 84,-51 109,-106z" style="fill: #3b82f6;"/><path d="M150 150c3,-11 6,-23 7,-36 -39,39 -94,49 -135,85 41,-25 86,-28 128,-49z" style="fill: #3b82f6;"/><path d="M105 208c10,-5 18,-13 26,-21 -38,5 -73,5 -110,19 29,-4 55,-1 84,2z" style="fill: #3b82f6;"/><path d="M9 213c25,6 46,8 64,5 -12,-5 -53,-10 -64,-5z" style="fill: #3b82f6;"/></g></g><g id="SvgjsG4141" featureKey="7UBp9i-0" transform="matrix(6.225240707397461,0,0,6.225240707397461,92.90322369982673,-21.50368198162188)" fill="#fff"><path d="M4.14 10.58 l0 2.92 l2.8 0 c0.92 0 1.46 -0.54 1.46 -1.46 s-0.54 -1.46 -1.46 -1.46 l-2.8 0 z M8.68 15.48 c0.56 0.76 1.12 1.52 1.7 2.26 c0.56 0.74 1.12 1.5 1.68 2.26 l-3.66 0 c-0.72 -0.96 -1.42 -1.92 -2.12 -2.88 c-0.7 -0.94 -1.42 -1.9 -2.14 -2.86 l0 5.74 l-3 0 l0 -12.24 l5.8 0 c2.28 0 4.22 1.74 4.22 4.04 c0 1.62 -1 3.04 -2.48 3.68 z M20.2 15 c-0.08 -1.46 -0.74 -2.32 -2.26 -2.32 c-0.42 0 -0.78 0.06 -1.08 0.18 c-0.92 0.42 -1.26 1.2 -1.26 2.16 c0 0.32 0.04 0.62 0.14 0.88 c0.3 1.04 1.2 1.42 2.2 1.42 c1.52 0 2.26 -0.82 2.26 -2.32 z M24 20 l-3 0 c-0.2 -0.5 -0.38 -1 -0.5 -1.52 c-0.68 1.12 -1.72 1.62 -3 1.62 c-2.86 0 -4.9 -2.4 -4.9 -5.14 c0 -3.16 2.36 -5.06 5.34 -5.06 c3.18 0 5.18 2.04 5.26 5.1 c0.02 0.26 0.02 0.56 0.02 0.92 c0 1.4 0.22 2.8 0.78 4.08 z M25.099999999999998 10 l3 0 l0 10 l-3 0 l0 -10 z M26.599999999999998 9.06 c-1.02 0 -1.74 -0.72 -1.74 -1.74 c0 -1.04 0.72 -1.72 1.74 -1.72 c1.04 0 1.72 0.68 1.72 1.72 c0 1.06 -0.66 1.74 -1.72 1.74 z M35.6 10.34 l0 2.78 l-2.46 0 l0 6.88 l-3 0 l0 -9.98 c0 -3.32 2.44 -5.12 5.52 -5.12 c0.1 0 0.24 0 0.38 0.02 s0.3 0.06 0.44 0.08 l0 2.9 c-0.1 -0.02 -0.22 -0.04 -0.36 -0.06 s-0.26 -0.04 -0.36 -0.04 c-0.5 0 -0.9 0.06 -1.24 0.16 c-0.56 0.2 -1.06 0.58 -1.24 1.18 c-0.1 0.26 -0.14 0.56 -0.14 0.86 l0 0.34 l2.46 0 z"/></g>
6
+ </g>
7
+ </svg>
8
+
@@ -25,6 +25,9 @@ body.raif-admin {
25
25
  }
26
26
 
27
27
  .raif-admin {
28
+ .navbar-brand-logo {
29
+ width: 100px;
30
+ }
28
31
 
29
32
  h1,
30
33
  h2,
@@ -65,6 +68,7 @@ body.raif-admin {
65
68
  box-shadow: inset -1px 0 0 $border-color;
66
69
  background-color: white !important;
67
70
  padding-top: 1rem;
71
+ height: 100vh;
68
72
 
69
73
  .nav-link {
70
74
  color: $text-color;
@@ -16,7 +16,7 @@ module Raif
16
16
  Turbo::StreamsChannel.broadcast_action_to(
17
17
  conversation,
18
18
  action: :raif_scroll_to_bottom,
19
- target: dom_id(conversation, :entries)
19
+ target: ActionView::RecordIdentifier.dom_id(conversation, :entries)
20
20
  )
21
21
  rescue StandardError => e
22
22
  logger.error "Error processing conversation entry: #{e.message}"
@@ -25,9 +25,8 @@ module Raif
25
25
 
26
26
  def extract_tag_content(tag_name)
27
27
  match = model_response_text.match(%r{<#{tag_name}>(.*?)</#{tag_name}>}m)
28
- match ? match[1].strip : nil
28
+ match && match[1] ? match[1].strip : nil
29
29
  end
30
-
31
30
  end
32
31
  end
33
32
  end
@@ -10,7 +10,7 @@ module Raif::Concerns::HasLlm
10
10
  end
11
11
 
12
12
  def default_llm_model_key
13
- Rails.env.test? ? :raif_test_llm : Raif.config.default_llm_model_key
13
+ Raif.config.default_llm_model_key
14
14
  end
15
15
 
16
16
  def llm
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Raif::Concerns::TaskRunArgs
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ class_attribute :_task_run_args, instance_writer: false, default: []
8
+ end
9
+
10
+ class_methods do
11
+ # DSL for declaring persistent task arguments that will be serialized to the database
12
+ # @param name [Symbol] The name of the argument
13
+ def task_run_arg(name)
14
+ # Ensure each class has its own array copy
15
+ self._task_run_args = _task_run_args.dup
16
+ _task_run_args << name.to_sym
17
+
18
+ # Define getter that pulls from task_run_args JSON
19
+ define_method(name) do
20
+ return instance_variable_get("@#{name}") if instance_variable_defined?("@#{name}")
21
+
22
+ value = task_run_args&.dig(name.to_s)
23
+ return unless value
24
+
25
+ # Deserialize GID if it's a string starting with gid://
26
+ deserialized = if value.is_a?(String) && value.start_with?("gid://")
27
+ begin
28
+ GlobalID::Locator.locate(value)
29
+ rescue ActiveRecord::RecordNotFound
30
+ nil
31
+ end
32
+ else
33
+ value
34
+ end
35
+
36
+ instance_variable_set("@#{name}", deserialized)
37
+ end
38
+
39
+ # Define setter that stores in memory (for use during run)
40
+ define_method("#{name}=") do |value|
41
+ instance_variable_set("@#{name}", value)
42
+ end
43
+ end
44
+
45
+ # Transform run args into a hash that can be stored in the task_run_args database column
46
+ def serialize_task_run_args(args)
47
+ serialized_args = {}
48
+ _task_run_args.each do |arg_name|
49
+ next unless args.key?(arg_name)
50
+
51
+ value = args[arg_name]
52
+ serialized_args[arg_name.to_s] = if value.respond_to?(:to_global_id)
53
+ value.to_global_id.to_s
54
+ else
55
+ value
56
+ end
57
+ end
58
+
59
+ serialized_args
60
+ end
61
+ end
62
+ end
@@ -34,6 +34,10 @@ class Raif::Conversation < Raif::ApplicationRecord
34
34
  I18n.t("#{self.class.name.underscore.gsub("/", ".")}.initial_chat_message")
35
35
  end
36
36
 
37
+ def initial_chat_message_partial_path
38
+ "raif/conversations/initial_chat_message"
39
+ end
40
+
37
41
  def prompt_model_for_entry_response(entry:, &block)
38
42
  update(system_prompt: build_system_prompt)
39
43
 
@@ -47,6 +51,8 @@ class Raif::Conversation < Raif::ApplicationRecord
47
51
  )
48
52
  rescue StandardError => e
49
53
  Rails.logger.error("Error processing conversation entry ##{entry.id}. #{e.message}")
54
+ Rails.logger.error(e.backtrace.join("\n"))
55
+
50
56
  entry.failed!
51
57
 
52
58
  if defined?(Airbrake)
@@ -56,6 +62,8 @@ class Raif::Conversation < Raif::ApplicationRecord
56
62
 
57
63
  Airbrake.notify(notice)
58
64
  end
65
+
66
+ nil
59
67
  end
60
68
 
61
69
  def process_model_response_message(message:, entry:)
@@ -17,7 +17,6 @@ class Raif::ConversationEntry < Raif::ApplicationRecord
17
17
 
18
18
  delegate :available_model_tools, to: :raif_conversation
19
19
  delegate :system_prompt, :llm_model_key, :citations, to: :raif_model_completion, allow_nil: true
20
- delegate :json_response_schema, to: :class
21
20
 
22
21
  accepts_nested_attributes_for :raif_user_tool_invocation
23
22
 
@@ -64,7 +63,7 @@ class Raif::ConversationEntry < Raif::ApplicationRecord
64
63
  broadcast_replace_to raif_conversation
65
64
  end
66
65
 
67
- if raif_model_completion.parsed_response.present? || raif_model_completion.response_tool_calls.present?
66
+ if raif_model_completion.present? && (raif_model_completion.parsed_response.present? || raif_model_completion.response_tool_calls.present?)
68
67
  extract_message_and_invoke_tools!
69
68
  create_entry_for_observation! if triggers_observation_to_model?
70
69
  else
@@ -84,7 +83,7 @@ class Raif::ConversationEntry < Raif::ApplicationRecord
84
83
  def create_entry_for_observation!
85
84
  follow_up_entry = raif_conversation.entries.create!(creator: creator)
86
85
  Raif::ConversationEntryJob.perform_later(conversation_entry: follow_up_entry)
87
- follow_up_entry.broadcast_append_to raif_conversation, target: dom_id(raif_conversation, :entries)
86
+ follow_up_entry.broadcast_append_to raif_conversation, target: ActionView::RecordIdentifier.dom_id(raif_conversation, :entries)
88
87
  end
89
88
 
90
89
  private
@@ -95,13 +94,11 @@ private
95
94
  self.model_response_message = raif_conversation.process_model_response_message(message: raif_model_completion.parsed_response, entry: self)
96
95
  save!
97
96
 
98
- if raif_model_completion.response_tool_calls.present?
99
- raif_model_completion.response_tool_calls.each do |tool_call|
100
- tool_klass = available_model_tools_map[tool_call["name"]]
101
- next if tool_klass.nil?
97
+ raif_model_completion.response_tool_calls&.each do |tool_call|
98
+ tool_klass = available_model_tools_map[tool_call["name"]]
99
+ next if tool_klass.nil?
102
100
 
103
- tool_klass.invoke_tool(tool_arguments: tool_call["arguments"], source: self)
104
- end
101
+ tool_klass.invoke_tool(tool_arguments: tool_call["arguments"], source: self)
105
102
  end
106
103
 
107
104
  completed!
@@ -77,7 +77,7 @@ module Raif
77
77
  temperature ||= default_temperature
78
78
  max_completion_tokens ||= default_max_completion_tokens
79
79
 
80
- model_completion = Raif::ModelCompletion.new(
80
+ model_completion = Raif::ModelCompletion.create!(
81
81
  messages: format_messages(messages),
82
82
  system_prompt: system_prompt,
83
83
  response_format: response_format,
@@ -38,9 +38,16 @@ private
38
38
  end
39
39
 
40
40
  def update_model_completion(model_completion, response_json)
41
+ raw_response = if model_completion.response_format_json?
42
+ extract_json_response(response_json)
43
+ else
44
+ extract_text_response(response_json)
45
+ end
46
+
41
47
  model_completion.update!(
48
+ response_id: response_json["id"],
42
49
  response_tool_calls: extract_response_tool_calls(response_json),
43
- raw_response: response_json.dig("choices", 0, "message", "content"),
50
+ raw_response: raw_response,
44
51
  response_array: response_json["choices"],
45
52
  completion_tokens: response_json.dig("usage", "completion_tokens"),
46
53
  prompt_tokens: response_json.dig("usage", "prompt_tokens"),
@@ -63,6 +70,20 @@ private
63
70
 
64
71
  if supports_native_tool_use?
65
72
  tools = build_tools_parameter(model_completion)
73
+
74
+ if model_completion.json_response_schema.present?
75
+ validate_json_schema!(model_completion.json_response_schema)
76
+
77
+ tools << {
78
+ type: "function",
79
+ function: {
80
+ name: "json_response",
81
+ description: "Generate a structured JSON response based on the provided schema.",
82
+ parameters: model_completion.json_response_schema
83
+ }
84
+ }
85
+ end
86
+
66
87
  params[:tools] = tools unless tools.blank?
67
88
  end
68
89
 
@@ -72,7 +93,9 @@ private
72
93
  params[:stream_options] = { include_usage: true }
73
94
  end
74
95
 
75
- if model_completion.response_format_json?
96
+ # OpenRouter will sometimes complain about combining response_format json and tool calling.
97
+ # If we're telling it to use the json_response tool, then the json_object response_format should be irrelevant.
98
+ if model_completion.response_format_json? && params[:tools].blank?
76
99
  params[:response_format] = { type: "json_object" }
77
100
  model_completion.response_format_parameter = "json_object"
78
101
  end
@@ -80,10 +103,30 @@ private
80
103
  params
81
104
  end
82
105
 
106
+ def extract_text_response(resp)
107
+ resp&.dig("choices", 0, "message", "content")
108
+ end
109
+
110
+ def extract_json_response(resp)
111
+ tool_calls = resp.dig("choices", 0, "message", "tool_calls")
112
+ return extract_text_response(resp) if tool_calls.blank?
113
+
114
+ tool_response = tool_calls.find do |tool_call|
115
+ tool_call["function"]["name"] == "json_response"
116
+ end
117
+
118
+ if tool_response&.dig("function", "arguments")
119
+ tool_response["function"]["arguments"]
120
+ else
121
+ extract_text_response(resp)
122
+ end
123
+ end
124
+
83
125
  def extract_response_tool_calls(resp)
84
- return if resp.dig("choices", 0, "message", "tool_calls").blank?
126
+ tool_calls = resp.dig("choices", 0, "message", "tool_calls")
127
+ return if tool_calls.blank?
85
128
 
86
- resp.dig("choices", 0, "message", "tool_calls").map do |tool_call|
129
+ tool_calls.map do |tool_call|
87
130
  {
88
131
  "name" => tool_call["function"]["name"],
89
132
  "arguments" => JSON.parse(tool_call["function"]["arguments"])
@@ -9,10 +9,13 @@ module Raif
9
9
  include Raif::Concerns::LlmResponseParsing
10
10
  include Raif::Concerns::LlmTemperature
11
11
  include Raif::Concerns::JsonSchemaDefinition
12
+ include Raif::Concerns::TaskRunArgs
12
13
 
13
14
  llm_temperature 0.7
14
15
 
15
- belongs_to :creator, polymorphic: true
16
+ belongs_to :creator, polymorphic: true, optional: true
17
+
18
+ validates :creator, presence: true, unless: -> { Raif.config.task_creator_optional }
16
19
 
17
20
  has_one :raif_model_completion, as: :source, dependent: :destroy, class_name: "Raif::ModelCompletion"
18
21
 
@@ -32,6 +35,7 @@ module Raif
32
35
  attr_accessor :files, :images
33
36
 
34
37
  after_initialize -> { self.available_model_tools ||= [] }
38
+ after_initialize -> { self.task_run_args ||= {} }
35
39
 
36
40
  def status
37
41
  if completed_at?
@@ -48,15 +52,24 @@ module Raif
48
52
  # The primary interface for running a task. It will hit the LLM with the task's prompt and system prompt and return a Raif::Task object.
49
53
  # It will also create a new Raif::ModelCompletion record.
50
54
  #
51
- # @param creator [Object] The creator of the task (polymorphic association)
55
+ # @param creator [Object, nil] The creator of the task (polymorphic association), optional
52
56
  # @param available_model_tools [Array<Class>] Optional array of model tool classes that will be provided to the LLM for it to invoke.
53
57
  # @param llm_model_key [Symbol, String] Optional key for the LLM model to use. If blank, Raif.config.default_llm_model_key will be used.
54
58
  # @param images [Array] Optional array of Raif::ModelImageInput objects to include with the prompt.
55
59
  # @param files [Array] Optional array of Raif::ModelFileInput objects to include with the prompt.
56
60
  # @param args [Hash] Additional arguments to pass to the instance of the task that is created.
57
61
  # @return [Raif::Task, nil] The task instance that was created and run.
58
- def self.run(creator:, available_model_tools: [], llm_model_key: nil, images: [], files: [], **args)
59
- task = new(creator:, llm_model_key:, available_model_tools:, started_at: Time.current, images: images, files: files, **args)
62
+ def self.run(creator: nil, available_model_tools: [], llm_model_key: nil, images: [], files: [], **args)
63
+ task = new(
64
+ creator: creator,
65
+ llm_model_key: llm_model_key,
66
+ available_model_tools: available_model_tools,
67
+ started_at: Time.current,
68
+ images: images,
69
+ files: files,
70
+ task_run_args: serialize_task_run_args(args),
71
+ **args
72
+ )
60
73
 
61
74
  task.save!
62
75
  task.run
@@ -109,19 +122,19 @@ module Raif
109
122
 
110
123
  # Returns the LLM prompt for the task.
111
124
  #
112
- # @param creator [Object] The creator of the task (polymorphic association)
125
+ # @param creator [Object, nil] The creator of the task (polymorphic association), optional
113
126
  # @param args [Hash] Additional arguments to pass to the instance of the task that is created.
114
127
  # @return [String] The LLM prompt for the task.
115
- def self.prompt(creator:, **args)
128
+ def self.prompt(creator: nil, **args)
116
129
  new(creator:, **args).build_prompt
117
130
  end
118
131
 
119
132
  # Returns the LLM system prompt for the task.
120
133
  #
121
- # @param creator [Object] The creator of the task (polymorphic association)
134
+ # @param creator [Object, nil] The creator of the task (polymorphic association), optional
122
135
  # @param args [Hash] Additional arguments to pass to the instance of the task that is created.
123
136
  # @return [String] The LLM system prompt for the task.
124
- def self.system_prompt(creator:, **args)
137
+ def self.system_prompt(creator: nil, **args)
125
138
  new(creator:, **args).build_system_prompt
126
139
  end
127
140
 
@@ -170,7 +183,7 @@ module Raif
170
183
  end
171
184
 
172
185
  def populate_prompts
173
- self.requested_language_key ||= creator.preferred_language_key if creator.respond_to?(:preferred_language_key)
186
+ self.requested_language_key ||= creator&.preferred_language_key if creator&.respond_to?(:preferred_language_key)
174
187
  self.prompt = build_prompt
175
188
  self.system_prompt = build_system_prompt
176
189
  end
@@ -15,7 +15,9 @@
15
15
  <body class="raif-admin">
16
16
  <nav class="navbar navbar-expand-md navbar-dark bg-dark">
17
17
  <div class="container-fluid">
18
- <a class="navbar-brand fw-bold" href="<%= raif.admin_tasks_path %>"><%= t("raif.admin.layouts.admin.title") %></a>
18
+ <a class="navbar-brand fw-bold" href="<%= raif.admin_tasks_path %>">
19
+ <%= image_tag "raif-logo-white.svg", class: "navbar-brand-logo" %>
20
+ </a>
19
21
  <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
20
22
  <span class="navbar-toggler-icon"></span>
21
23
  </button>
@@ -10,7 +10,7 @@
10
10
  <% end %>
11
11
  <% end %>
12
12
 
13
- <div class="d-flex px-2">
13
+ <div class="d-flex px-2 align-items-center">
14
14
  <%= f.text_field :user_message,
15
15
  class: "form-control me-2",
16
16
  placeholder: conversation_entry.raif_user_tool_invocation&.message_input_placeholder.presence || t("raif.common.type_your_message"),
@@ -1,14 +1,11 @@
1
1
  <%= turbo_stream_from conversation %>
2
2
 
3
- <div id="<%= dom_id(conversation, :entries) %>" class="flex-grow-1 overflow-auto" data-controller="raif--conversations">
4
- <%= render "raif/conversation_entries/message",
5
- content: conversation.initial_chat_message,
6
- message_type: :model_response %>
7
-
3
+ <div id="<%= dom_id(conversation, :entries) %>" class="flex-grow-1 overflow-auto raif-conversation-entries-container" data-controller="raif--conversations">
4
+ <%= render conversation.initial_chat_message_partial_path, conversation: conversation %>
8
5
  <%= render conversation.entries.oldest_first %>
9
6
  </div>
10
7
 
11
- <div id="<%= dom_id(conversation, :entry_input) %>">
8
+ <div id="<%= dom_id(conversation, :entry_input) %>" class="raif-conversation-entry-input-container">
12
9
  <%= render "raif/conversation_entries/form_with_available_tools",
13
10
  conversation: conversation,
14
11
  conversation_entry: Raif::ConversationEntry.new %>
@@ -0,0 +1,5 @@
1
+ <%# locals: (conversation:) %>
2
+
3
+ <%= render "raif/conversation_entries/message",
4
+ content: conversation.initial_chat_message,
5
+ message_type: :model_response %>
@@ -68,6 +68,9 @@ en:
68
68
  open_ai_gpt_4_1_nano: OpenAI GPT-4.1 Nano
69
69
  open_ai_gpt_4o: OpenAI GPT-4o
70
70
  open_ai_gpt_4o_mini: OpenAI GPT-4o Mini
71
+ open_ai_gpt_5: OpenAI GPT-5
72
+ open_ai_gpt_5_mini: OpenAI GPT-5 Mini
73
+ open_ai_gpt_5_nano: OpenAI GPT-5 Nano
71
74
  open_ai_o1: OpenAI o1
72
75
  open_ai_o1_mini: OpenAI o1 Mini
73
76
  open_ai_o3: OpenAI o3
@@ -79,6 +82,9 @@ en:
79
82
  open_ai_responses_gpt_4_1_nano: OpenAI GPT-4.1 Nano (Responses API)
80
83
  open_ai_responses_gpt_4o: OpenAI GPT-4o (Responses API)
81
84
  open_ai_responses_gpt_4o_mini: OpenAI GPT-4o Mini (Responses API)
85
+ open_ai_responses_gpt_5: OpenAI GPT-5 (Responses API)
86
+ open_ai_responses_gpt_5_mini: OpenAI GPT-5 Mini (Responses API)
87
+ open_ai_responses_gpt_5_nano: OpenAI GPT-5 Nano (Responses API)
82
88
  open_ai_responses_o1: OpenAI o1 (Responses API)
83
89
  open_ai_responses_o1_mini: OpenAI o1 Mini (Responses API)
84
90
  open_ai_responses_o1_pro: OpenAI o1 Pro (Responses API)
@@ -93,4 +99,6 @@ en:
93
99
  open_router_llama_3_3_70b_instruct: Meta Llama 3.3 70B Instruct (via OpenRouter)
94
100
  open_router_llama_4_maverick: Meta Llama 4 Maverick (via OpenRouter)
95
101
  open_router_llama_4_scout: Meta Llama 4 Scout (via OpenRouter)
102
+ open_router_open_ai_gpt_oss_120b: OpenAI GPT-OSS 120B (via OpenRouter)
103
+ open_router_open_ai_gpt_oss_20b: OpenAI GPT-OSS 20B (via OpenRouter)
96
104
  raif_test_llm: Raif Test LLM
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddTaskRunArgsToRaifTasks < ActiveRecord::Migration[7.1]
4
+ def change
5
+ json_column_type = if connection.adapter_name.downcase.include?("postgresql")
6
+ :jsonb
7
+ else
8
+ :json
9
+ end
10
+
11
+ add_column :raif_tasks, :task_run_args, json_column_type
12
+ end
13
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MakeRaifTaskCreatorOptional < ActiveRecord::Migration[7.1]
4
+ def change
5
+ change_column_null :raif_tasks, :creator_id, true
6
+ change_column_null :raif_tasks, :creator_type, true
7
+ end
8
+ end
data/exe/raif ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/raif/cli"
5
+
6
+ # Run the CLI
7
+ Raif::CLI::Runner.new(ARGV).run
@@ -1,32 +1,47 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../base_generator"
4
+
3
5
  module Raif
4
6
  module Generators
5
- class AgentGenerator < Rails::Generators::NamedBase
7
+ class AgentGenerator < BaseGenerator
6
8
  source_root File.expand_path("templates", __dir__)
7
9
  desc "Creates a new Raif::Agent subclass in app/models/raif/agents"
8
10
 
11
+ class_option :skip_eval_set,
12
+ type: :boolean,
13
+ default: false,
14
+ desc: "Skip generating the corresponding eval set"
15
+
9
16
  def create_application_agent
10
17
  template "application_agent.rb.tt", "app/models/raif/application_agent.rb" unless File.exist?("app/models/raif/application_agent.rb")
11
18
  end
12
19
 
13
20
  def create_agent
14
- template "agent.rb.tt", "app/models/raif/agents/#{file_name}.rb"
21
+ template "agent.rb.tt", File.join("app/models/raif/agents", class_path, "#{file_name}.rb")
15
22
  end
16
23
 
17
24
  def create_directory
18
25
  empty_directory "app/models/raif/agents" unless File.directory?("app/models/raif/agents")
19
26
  end
20
27
 
21
- private
28
+ def create_eval_set
29
+ return if options[:skip_eval_set]
30
+
31
+ template "agent_eval_set.rb.tt", eval_set_file_path
32
+ end
22
33
 
23
- def class_name
24
- name.classify
34
+ def show_instructions
35
+ say "\nAgent created!"
36
+ say ""
25
37
  end
26
38
 
27
- def file_name
28
- name.underscore
39
+ private
40
+
41
+ def eval_set_file_path
42
+ File.join("raif_evals", "eval_sets", "agents", class_path, "#{file_name}_eval_set.rb")
29
43
  end
44
+
30
45
  end
31
46
  end
32
47
  end