Skip to content

Commit 4bffaa5

Browse files
committed
refactor: improve code quality and readability
- Reduce nested code in flow.ex, runtime.ex, primitives.ex, and tools.ex - Convert explicit try blocks to implicit try patterns - Remove trailing whitespace throughout codebase - Extract helper functions to improve readability and reduce complexity - Fix all Credo strict mode warnings
1 parent 6af4c25 commit 4bffaa5

File tree

16 files changed

+239
-202
lines changed

16 files changed

+239
-202
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ jobs:
4646
- name: Check Formatting
4747
run: mix format --check-formatted
4848

49+
- name: Code Quality Check
50+
run: mix credo --strict
51+
4952
- name: Compile (with warnings as errors)
5053
run: mix compile --warnings-as-errors
5154

lib/agent_forge.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ defmodule AgentForge do
2525
# result.data will be "Got: Hello"
2626
"""
2727

28-
alias AgentForge.{Signal, Runtime}
28+
alias AgentForge.{Runtime, Signal}
2929

3030
@doc """
3131
Creates a new flow with the given handlers and options.

lib/agent_forge/config.ex

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ defmodule AgentForge.Config do
44
Supports loading workflows from YAML or JSON.
55
"""
66

7-
alias AgentForge.{Flow, Primitives, Tools, Signal, Runtime}
7+
alias AgentForge.{Flow, Primitives, Runtime, Signal, Tools}
88

99
@doc """
1010
Loads a workflow from a YAML or JSON string.
@@ -30,17 +30,11 @@ defmodule AgentForge.Config do
3030
true
3131
"""
3232
def load_from_string(content) when is_binary(content) do
33-
case parse_config(content) do
34-
{:ok, config} ->
35-
case build_flow(config) do
36-
{:ok, handlers} ->
37-
# Return an executable flow function
38-
Runtime.configure(handlers)
39-
40-
{:error, reason} ->
41-
fn _signal -> {:error, reason} end
42-
end
43-
33+
with {:ok, config} <- parse_config(content),
34+
{:ok, handlers} <- build_flow(config) do
35+
# Return an executable flow function
36+
Runtime.configure(handlers)
37+
else
4438
{:error, reason} ->
4539
fn _signal -> {:error, reason} end
4640
end
@@ -69,14 +63,12 @@ defmodule AgentForge.Config do
6963
# Private functions
7064

7165
defp parse_config(content) do
72-
cond do
73-
String.starts_with?(String.trim(content), "{") ->
74-
# Looks like JSON
75-
parse_json(content)
76-
77-
true ->
78-
# Assume YAML
79-
parse_yaml(content)
66+
if String.starts_with?(String.trim(content), "{") do
67+
# Looks like JSON
68+
parse_json(content)
69+
else
70+
# Assume YAML
71+
parse_yaml(content)
8072
end
8173
end
8274

@@ -152,7 +144,7 @@ defmodule AgentForge.Config do
152144
{result, _} = Code.eval_string(custom, data: data)
153145
result
154146
rescue
155-
e -> raise "Transform evaluation error: #{Exception.message(e)}"
147+
e -> reraise "Transform evaluation error: #{Exception.message(e)}", __STACKTRACE__
156148
end
157149
end
158150

lib/agent_forge/execution_stats.ex

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,6 @@ defmodule AgentForge.ExecutionStats do
126126
defp get_state_size(_), do: 0
127127

128128
defp format_counters(counters) do
129-
counters
130-
|> Enum.map(fn {key, count} -> "#{key}: #{count}" end)
131-
|> Enum.join(", ")
129+
Enum.map_join(counters, ", ", fn {key, count} -> "#{key}: #{count}" end)
132130
end
133131
end

lib/agent_forge/flow.ex

Lines changed: 67 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,21 @@ defmodule AgentForge.Flow do
1616
@type flow :: (Signal.t(), map() -> {term(), map()}) | [(Signal.t(), map() -> {term(), map()})]
1717

1818
def process(handlers, signal, state) when is_list(handlers) do
19-
try do
20-
# Call process_with_limits with default option to not return statistics
21-
# This ensures backward compatibility with existing code
22-
case process_with_limits(handlers, signal, state, return_stats: false) do
23-
{:ok, result, new_state} ->
24-
{:ok, result, new_state}
25-
26-
{:error, reason, _state} ->
27-
# Maintain original error format for backward compatibility
28-
{:error, reason}
29-
30-
other ->
31-
other
32-
end
33-
catch
34-
_kind, error -> {:error, "Flow processing error: #{inspect(error)}"}
19+
# Call process_with_limits with default option to not return statistics
20+
# This ensures backward compatibility with existing code
21+
case process_with_limits(handlers, signal, state, return_stats: false) do
22+
{:ok, result, new_state} ->
23+
{:ok, result, new_state}
24+
25+
{:error, reason, _state} ->
26+
# Maintain original error format for backward compatibility
27+
{:error, reason}
28+
29+
other ->
30+
other
3531
end
32+
rescue
33+
error -> {:error, "Flow processing error: #{inspect(error)}"}
3634
end
3735

3836
def get_last_execution_stats, do: Process.get(@last_execution_stats_key)
@@ -69,27 +67,15 @@ defmodule AgentForge.Flow do
6967
"""
7068
def process_with_limits(handlers, signal, state, opts \\ []) when is_list(handlers) do
7169
# Extract options
72-
timeout_ms = Keyword.get(opts, :timeout_ms, 30000)
70+
timeout_ms = Keyword.get(opts, :timeout_ms, 30_000)
7371
collect_stats = Keyword.get(opts, :collect_stats, true)
7472
return_stats = Keyword.get(opts, :return_stats, false)
7573

7674
# Initialize statistics if enabled
7775
stats = if collect_stats, do: ExecutionStats.new(), else: nil
7876

7977
# Create a task to process the signal with timeout
80-
# Use try-catch to wrap processing logic to ensure exceptions are properly caught
81-
task =
82-
Task.async(fn ->
83-
try do
84-
process_with_stats(handlers, signal, state, stats)
85-
catch
86-
# Explicitly catch exceptions and convert them to appropriate error results
87-
_kind, error ->
88-
error_message = "Flow processing error: #{inspect(error)}"
89-
error_result = {:error, error_message, state, stats}
90-
{error_result, stats}
91-
end
92-
end)
78+
task = Task.async(fn -> execute_flow_safely(handlers, signal, state, stats) end)
9379

9480
# Wait for the task to complete or timeout
9581
case Task.yield(task, timeout_ms) || Task.shutdown(task) do
@@ -120,33 +106,12 @@ defmodule AgentForge.Flow do
120106
updated_stats =
121107
ExecutionStats.record_step(current_stats, handler, current_signal, current_state)
122108

123-
# Process handler
124-
case process_handler(handler, current_signal, current_state) do
125-
{{:emit, new_signal}, new_state} ->
126-
{:cont, {:ok, new_signal, new_state, updated_stats}}
127-
128-
{{:emit_many, signals}, new_state} when is_list(signals) ->
129-
# When multiple signals are emitted, use the last one for continuation
130-
{:cont, {:ok, List.last(signals), new_state, updated_stats}}
131-
132-
{:skip, new_state} ->
133-
{:halt, {:ok, nil, new_state, updated_stats}}
134-
135-
{:halt, data} ->
136-
{:halt, {:ok, data, current_state, updated_stats}}
137-
138-
{{:halt, data}, _state} ->
139-
{:halt, {:ok, data, current_state, updated_stats}}
140-
141-
{{:error, reason}, new_state} ->
142-
{:halt, {:error, reason, new_state, updated_stats}}
143-
144-
{other, _} ->
145-
raise "Invalid handler result: #{inspect(other)}"
146-
147-
other ->
148-
raise "Invalid handler result: #{inspect(other)}"
149-
end
109+
# Process handler and handle result
110+
handle_process_result(
111+
process_handler(handler, current_signal, current_state),
112+
current_state,
113+
updated_stats
114+
)
150115
end)
151116

152117
# Extract stats from result
@@ -307,4 +272,49 @@ defmodule AgentForge.Flow do
307272
{:skip, Map.put(state, key, signal.data)}
308273
end
309274
end
275+
276+
# Safely executes a flow with exception handling
277+
defp execute_flow_safely(handlers, signal, state, stats) do
278+
process_with_stats(handlers, signal, state, stats)
279+
rescue
280+
error ->
281+
error_message = "Flow processing error: #{inspect(error)}"
282+
error_result = {:error, error_message, state, stats}
283+
{error_result, stats}
284+
catch
285+
_kind, error ->
286+
error_message = "Flow processing error: #{inspect(error)}"
287+
error_result = {:error, error_message, state, stats}
288+
{error_result, stats}
289+
end
290+
291+
# Helper function to handle results from process_handler
292+
defp handle_process_result(result, current_state, updated_stats) do
293+
case result do
294+
{{:emit, new_signal}, new_state} ->
295+
{:cont, {:ok, new_signal, new_state, updated_stats}}
296+
297+
{{:emit_many, signals}, new_state} when is_list(signals) ->
298+
# When multiple signals are emitted, use the last one for continuation
299+
{:cont, {:ok, List.last(signals), new_state, updated_stats}}
300+
301+
{:skip, new_state} ->
302+
{:halt, {:ok, nil, new_state, updated_stats}}
303+
304+
{:halt, data} ->
305+
{:halt, {:ok, data, current_state, updated_stats}}
306+
307+
{{:halt, data}, _state} ->
308+
{:halt, {:ok, data, current_state, updated_stats}}
309+
310+
{{:error, reason}, new_state} ->
311+
{:halt, {:error, reason, new_state, updated_stats}}
312+
313+
{other, _} ->
314+
raise "Invalid handler result: #{inspect(other)}"
315+
316+
other ->
317+
raise "Invalid handler result: #{inspect(other)}"
318+
end
319+
end
310320
end

lib/agent_forge/plugins/http.ex

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,7 @@ defmodule AgentForge.Plugins.HTTP do
4343
# Tool implementations
4444

4545
defp http_get(params) do
46-
if not Code.ensure_loaded?(Finch) do
47-
%{error: :finch_not_installed, message: "Finch dependency is required for HTTP operations"}
48-
else
46+
if Code.ensure_loaded?(Finch) do
4947
url = Map.fetch!(params, "url")
5048
headers = Map.get(params, "headers", %{})
5149

@@ -62,13 +60,13 @@ defmodule AgentForge.Plugins.HTTP do
6260
{:error, reason} ->
6361
%{error: reason}
6462
end
63+
else
64+
%{error: :finch_not_installed, message: "Finch dependency is required for HTTP operations"}
6565
end
6666
end
6767

6868
defp http_post(params) do
69-
if not Code.ensure_loaded?(Finch) do
70-
%{error: :finch_not_installed, message: "Finch dependency is required for HTTP operations"}
71-
else
69+
if Code.ensure_loaded?(Finch) do
7270
url = Map.fetch!(params, "url")
7371
headers = Map.get(params, "headers", %{})
7472
body = Map.get(params, "body", "")
@@ -86,6 +84,8 @@ defmodule AgentForge.Plugins.HTTP do
8684
{:error, reason} ->
8785
%{error: reason}
8886
end
87+
else
88+
%{error: :finch_not_installed, message: "Finch dependency is required for HTTP operations"}
8989
end
9090
end
9191

lib/agent_forge/primitives.ex

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,16 @@ defmodule AgentForge.Primitives do
2929
"""
3030
def branch(condition, then_flow, else_flow) when is_function(condition, 2) do
3131
fn signal, state ->
32-
if condition.(signal, state) do
33-
case Flow.process(then_flow, signal, state) do
34-
{:ok, result, new_state} -> {{:emit, result}, new_state}
35-
{:error, reason} -> {{:emit, Signal.new(:error, reason)}, state}
36-
end
37-
else
38-
case Flow.process(else_flow, signal, state) do
39-
{:ok, result, new_state} -> {{:emit, result}, new_state}
40-
{:error, reason} -> {{:emit, Signal.new(:error, reason)}, state}
41-
end
42-
end
32+
flow_to_use = if condition.(signal, state), do: then_flow, else: else_flow
33+
process_branch_flow(flow_to_use, signal, state)
34+
end
35+
end
36+
37+
# Helper function to process the selected branch flow
38+
defp process_branch_flow(flow, signal, state) do
39+
case Flow.process(flow, signal, state) do
40+
{:ok, result, new_state} -> {{:emit, result}, new_state}
41+
{:error, reason} -> {{:emit, Signal.new(:error, reason)}, state}
4342
end
4443
end
4544

@@ -86,9 +85,7 @@ defmodule AgentForge.Primitives do
8685
fn signal, state ->
8786
items = signal.data
8887

89-
if not is_list(items) do
90-
{{:emit, Signal.new(:error, "Loop data must be a list")}, state}
91-
else
88+
if is_list(items) do
9289
try do
9390
{results, final_state} =
9491
Enum.reduce(items, {[], state}, fn item, {results, current_state} ->
@@ -104,8 +101,10 @@ defmodule AgentForge.Primitives do
104101
{{:emit_many, Enum.reverse(results)}, final_state}
105102
catch
106103
{:halt, result, new_state} -> {{:halt, result}, new_state}
107-
{:error, reason} -> {{:emit, Signal.new(:error, reason)}, state}
104+
{:error, reason} -> {{:error, reason}, state}
108105
end
106+
else
107+
{{:emit, Signal.new(:error, "Loop data must be a list")}, state}
109108
end
110109
end
111110
end
@@ -207,24 +206,29 @@ defmodule AgentForge.Primitives do
207206

208207
# Process each channel
209208
Enum.each(channels, fn channel_name ->
210-
case AgentForge.Notification.Registry.get_channel(channel_name) do
211-
{:ok, channel_module} ->
212-
# Get channel-specific config
213-
channel_config = Map.get(config, channel_name, %{})
214-
# Send notification
215-
channel_module.send(message, channel_config)
216-
217-
:error when channel_name == :console ->
218-
# Built-in console support for backward compatibility
219-
IO.puts("[Notification] #{message}")
220-
221-
:error ->
222-
# Channel not found, log warning
223-
IO.warn("Notification channel not found: #{inspect(channel_name)}")
224-
end
209+
send_notification(channel_name, message, config)
225210
end)
226211

227212
{{:emit, Signal.new(:notification, message)}, state}
228213
end
229214
end
215+
216+
# Helper function to send a notification to a specific channel
217+
defp send_notification(channel_name, message, config) do
218+
case AgentForge.Notification.Registry.get_channel(channel_name) do
219+
{:ok, channel_module} ->
220+
# Get channel-specific config
221+
channel_config = Map.get(config, channel_name, %{})
222+
# Send notification
223+
channel_module.send(message, channel_config)
224+
225+
:error when channel_name == :console ->
226+
# Built-in console support for backward compatibility
227+
IO.puts("[Notification] #{message}")
228+
229+
:error ->
230+
# Channel not found, log warning
231+
IO.warn("Notification channel not found: #{inspect(channel_name)}")
232+
end
233+
end
230234
end

0 commit comments

Comments
 (0)