funcrunner 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.idea/.gitignore +8 -0
- data/.idea/aws.xml +17 -0
- data/.idea/funcrunner.iml +90 -0
- data/.idea/inspectionProfiles/Project_Default.xml +17 -0
- data/.idea/misc.xml +4 -0
- data/.idea/modules.xml +8 -0
- data/.idea/vcs.xml +6 -0
- data/.standard.yml +3 -0
- data/README.md +35 -0
- data/Rakefile +10 -0
- data/lib/func_runner/application.rb +236 -0
- data/lib/func_runner/function_definition.rb +31 -0
- data/lib/func_runner/function_execution.rb +13 -0
- data/lib/func_runner/function_execution_payload.rb +13 -0
- data/lib/func_runner/message.rb +18 -0
- data/lib/func_runner/run_result.rb +17 -0
- data/lib/func_runner/version.rb +5 -0
- data/lib/func_runner.rb +48 -0
- data/sig/funcrunner.rbs +4 -0
- metadata +90 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 3adde37e5f80ed54c2a0e9c25b02c3dfd2d36f93309fe3d345f1281f4a2b9c35
|
4
|
+
data.tar.gz: d0669779e63f9bdb6e8833cf24b899894b43dd71d1a0e92bf7f131ecfed274b8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 53ecaf8b424eee154588455a1ab64d9df78d33fb3e9aa2ac09e67f544b26d6fa7c47d8080989428ae13a4f110671edd9d41ac90cd788a90cc532a81f7c01c308
|
7
|
+
data.tar.gz: 9cfcee3a74b6fab0292c90491ee3786402f8889ecfa76b3a124d6a1adf4e8523cf8f86807d2e04a4dfee20d3663e9c9733354bf9fd0a941e0fe2c57bbe04c71f
|
data/.idea/.gitignore
ADDED
data/.idea/aws.xml
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<project version="4">
|
3
|
+
<component name="accountSettings">
|
4
|
+
<option name="activeProfile" value="profile:default" />
|
5
|
+
<option name="activeRegion" value="us-east-1" />
|
6
|
+
<option name="recentlyUsedProfiles">
|
7
|
+
<list>
|
8
|
+
<option value="profile:default" />
|
9
|
+
</list>
|
10
|
+
</option>
|
11
|
+
<option name="recentlyUsedRegions">
|
12
|
+
<list>
|
13
|
+
<option value="us-east-1" />
|
14
|
+
</list>
|
15
|
+
</option>
|
16
|
+
</component>
|
17
|
+
</project>
|
@@ -0,0 +1,90 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<module type="RUBY_MODULE" version="4">
|
3
|
+
<component name="ModuleRunConfigurationManager">
|
4
|
+
<shared />
|
5
|
+
</component>
|
6
|
+
<component name="NewModuleRootManager">
|
7
|
+
<content url="file://$MODULE_DIR$">
|
8
|
+
<sourceFolder url="file://$MODULE_DIR$/features" isTestSource="true" />
|
9
|
+
<sourceFolder url="file://$MODULE_DIR$/spec" isTestSource="true" />
|
10
|
+
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
11
|
+
</content>
|
12
|
+
<orderEntry type="inheritedJdk" />
|
13
|
+
<orderEntry type="sourceFolder" forTests="false" />
|
14
|
+
<orderEntry type="library" scope="PROVIDED" name="ast (v2.4.2, RVM: ruby-3.3.4 [global]) [gem]" level="application" />
|
15
|
+
<orderEntry type="library" scope="PROVIDED" name="bundler (v2.5.23, RVM: ruby-3.3.4 [global]) [gem]" level="application" />
|
16
|
+
<orderEntry type="library" scope="PROVIDED" name="concurrent-ruby (v1.3.4, RVM: ruby-3.3.4 [global]) [gem]" level="application" />
|
17
|
+
<orderEntry type="library" scope="PROVIDED" name="faraday (v2.12.0, RVM: ruby-3.3.4 [global]) [gem]" level="application" />
|
18
|
+
<orderEntry type="library" scope="PROVIDED" name="faraday-net_http (v3.3.0, RVM: ruby-3.3.4 [global]) [gem]" level="application" />
|
19
|
+
<orderEntry type="library" scope="PROVIDED" name="json (v2.8.1, RVM: ruby-3.3.4 [global]) [gem]" level="application" />
|
20
|
+
<orderEntry type="library" scope="PROVIDED" name="language_server-protocol (v3.17.0.3, RVM: ruby-3.3.4 [global]) [gem]" level="application" />
|
21
|
+
<orderEntry type="library" scope="PROVIDED" name="lint_roller (v1.1.0, RVM: ruby-3.3.4 [global]) [gem]" level="application" />
|
22
|
+
<orderEntry type="library" scope="PROVIDED" name="logger (v1.6.1, RVM: ruby-3.3.4 [global]) [gem]" level="application" />
|
23
|
+
<orderEntry type="library" scope="PROVIDED" name="minitest (v5.25.1, RVM: ruby-3.3.4 [global]) [gem]" level="application" />
|
24
|
+
<orderEntry type="library" scope="PROVIDED" name="net-http (v0.5.0, RVM: ruby-3.3.4 [global]) [gem]" level="application" />
|
25
|
+
<orderEntry type="library" scope="PROVIDED" name="parallel (v1.26.3, RVM: ruby-3.3.4 [global]) [gem]" level="application" />
|
26
|
+
<orderEntry type="library" scope="PROVIDED" name="parser (v3.3.6.0, RVM: ruby-3.3.4 [global]) [gem]" level="application" />
|
27
|
+
<orderEntry type="library" scope="PROVIDED" name="racc (v1.8.1, RVM: ruby-3.3.4 [global]) [gem]" level="application" />
|
28
|
+
<orderEntry type="library" scope="PROVIDED" name="rainbow (v3.1.1, RVM: ruby-3.3.4 [global]) [gem]" level="application" />
|
29
|
+
<orderEntry type="library" scope="PROVIDED" name="rake (v13.2.1, RVM: ruby-3.3.4 [global]) [gem]" level="application" />
|
30
|
+
<orderEntry type="library" scope="PROVIDED" name="regexp_parser (v2.9.2, RVM: ruby-3.3.4 [global]) [gem]" level="application" />
|
31
|
+
<orderEntry type="library" scope="PROVIDED" name="rubocop (v1.66.1, RVM: ruby-3.3.4 [global]) [gem]" level="application" />
|
32
|
+
<orderEntry type="library" scope="PROVIDED" name="rubocop-ast (v1.34.1, RVM: ruby-3.3.4 [global]) [gem]" level="application" />
|
33
|
+
<orderEntry type="library" scope="PROVIDED" name="rubocop-performance (v1.22.1, RVM: ruby-3.3.4 [global]) [gem]" level="application" />
|
34
|
+
<orderEntry type="library" scope="PROVIDED" name="ruby-progressbar (v1.13.0, RVM: ruby-3.3.4 [global]) [gem]" level="application" />
|
35
|
+
<orderEntry type="library" scope="PROVIDED" name="semantic_logger (v4.16.1, RVM: ruby-3.3.4 [global]) [gem]" level="application" />
|
36
|
+
<orderEntry type="library" scope="PROVIDED" name="standard (v1.41.1, RVM: ruby-3.3.4 [global]) [gem]" level="application" />
|
37
|
+
<orderEntry type="library" scope="PROVIDED" name="standard-custom (v1.0.2, RVM: ruby-3.3.4 [global]) [gem]" level="application" />
|
38
|
+
<orderEntry type="library" scope="PROVIDED" name="standard-performance (v1.5.0, RVM: ruby-3.3.4 [global]) [gem]" level="application" />
|
39
|
+
<orderEntry type="library" scope="PROVIDED" name="unicode-display_width (v2.6.0, RVM: ruby-3.3.4 [global]) [gem]" level="application" />
|
40
|
+
<orderEntry type="library" scope="PROVIDED" name="uri (v1.0.1, RVM: ruby-3.3.4 [global]) [gem]" level="application" />
|
41
|
+
</component>
|
42
|
+
<component name="RakeTasksCache-v2">
|
43
|
+
<option name="myRootTask">
|
44
|
+
<RakeTaskImpl id="rake">
|
45
|
+
<subtasks>
|
46
|
+
<RakeTaskImpl description="Build funcrunner-0.1.0.gem into the pkg directory" fullCommand="build" id="build" />
|
47
|
+
<RakeTaskImpl id="build">
|
48
|
+
<subtasks>
|
49
|
+
<RakeTaskImpl description="Generate SHA512 checksum of funcrunner-0.1.0.gem into the checksums directory" fullCommand="build:checksum" id="checksum" />
|
50
|
+
</subtasks>
|
51
|
+
</RakeTaskImpl>
|
52
|
+
<RakeTaskImpl description="Remove any temporary products" fullCommand="clean" id="clean" />
|
53
|
+
<RakeTaskImpl description="Remove any generated files" fullCommand="clobber" id="clobber" />
|
54
|
+
<RakeTaskImpl description="Build and install funcrunner-0.1.0.gem into system gems" fullCommand="install" id="install" />
|
55
|
+
<RakeTaskImpl id="install">
|
56
|
+
<subtasks>
|
57
|
+
<RakeTaskImpl description="Build and install funcrunner-0.1.0.gem into system gems without network access" fullCommand="install:local" id="local" />
|
58
|
+
</subtasks>
|
59
|
+
</RakeTaskImpl>
|
60
|
+
<RakeTaskImpl description="Create tag v0.1.0 and build and push funcrunner-0.1.0.gem to rubygems.org" fullCommand="release[remote]" id="release[remote]" />
|
61
|
+
<RakeTaskImpl description="Lint with the Standard Ruby style guide" fullCommand="standard" id="standard" />
|
62
|
+
<RakeTaskImpl id="standard">
|
63
|
+
<subtasks>
|
64
|
+
<RakeTaskImpl description="Lint and automatically make safe fixes with the Standard Ruby style guide" fullCommand="standard:fix" id="fix" />
|
65
|
+
<RakeTaskImpl description="Lint and automatically make fixes (even unsafe ones" fullCommand="standard:fix_unsafely" id="fix_unsafely" />
|
66
|
+
</subtasks>
|
67
|
+
</RakeTaskImpl>
|
68
|
+
<RakeTaskImpl description="Run the test suite" fullCommand="test" id="test" />
|
69
|
+
<RakeTaskImpl id="test">
|
70
|
+
<subtasks>
|
71
|
+
<RakeTaskImpl description="Print out the test command" fullCommand="test:cmd" id="cmd" />
|
72
|
+
<RakeTaskImpl description="Show which test files fail when run in isolation" fullCommand="test:isolated" id="isolated" />
|
73
|
+
<RakeTaskImpl description="Run the test suite and report the slowest 25 tests" fullCommand="test:slow" id="slow" />
|
74
|
+
<RakeTaskImpl description="" fullCommand="test:deps" id="deps" />
|
75
|
+
</subtasks>
|
76
|
+
</RakeTaskImpl>
|
77
|
+
<RakeTaskImpl description="" fullCommand="default" id="default" />
|
78
|
+
<RakeTaskImpl description="" fullCommand="release" id="release" />
|
79
|
+
<RakeTaskImpl id="release">
|
80
|
+
<subtasks>
|
81
|
+
<RakeTaskImpl description="" fullCommand="release:guard_clean" id="guard_clean" />
|
82
|
+
<RakeTaskImpl description="" fullCommand="release:rubygem_push" id="rubygem_push" />
|
83
|
+
<RakeTaskImpl description="" fullCommand="release:source_control_push" id="source_control_push" />
|
84
|
+
</subtasks>
|
85
|
+
</RakeTaskImpl>
|
86
|
+
</subtasks>
|
87
|
+
</RakeTaskImpl>
|
88
|
+
</option>
|
89
|
+
</component>
|
90
|
+
</module>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<component name="InspectionProjectProfileManager">
|
2
|
+
<profile version="1.0">
|
3
|
+
<option name="myName" value="Project Default" />
|
4
|
+
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
5
|
+
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
6
|
+
<option name="ignoredPackages">
|
7
|
+
<value>
|
8
|
+
<list size="1">
|
9
|
+
<item index="0" class="java.lang.String" itemvalue="dj_database_url" />
|
10
|
+
</list>
|
11
|
+
</value>
|
12
|
+
</option>
|
13
|
+
</inspection_tool>
|
14
|
+
<inspection_tool class="RbsMissingTypeSignature" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
15
|
+
<inspection_tool class="RubyQuotedStringsInspection" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
16
|
+
</profile>
|
17
|
+
</component>
|
data/.idea/misc.xml
ADDED
data/.idea/modules.xml
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<project version="4">
|
3
|
+
<component name="ProjectModuleManager">
|
4
|
+
<modules>
|
5
|
+
<module fileurl="file://$PROJECT_DIR$/.idea/funcrunner.iml" filepath="$PROJECT_DIR$/.idea/funcrunner.iml" />
|
6
|
+
</modules>
|
7
|
+
</component>
|
8
|
+
</project>
|
data/.idea/vcs.xml
ADDED
data/.standard.yml
ADDED
data/README.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# Funcrunner
|
2
|
+
|
3
|
+
TODO: Delete this and the text below, and describe your gem
|
4
|
+
|
5
|
+
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/funcrunner`. To experiment with that code, run `bin/console` for an interactive prompt.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
|
10
|
+
|
11
|
+
Install the gem and add to the application's Gemfile by executing:
|
12
|
+
|
13
|
+
```bash
|
14
|
+
bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
|
15
|
+
```
|
16
|
+
|
17
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
18
|
+
|
19
|
+
```bash
|
20
|
+
gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
|
21
|
+
```
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
TODO: Write usage instructions here
|
26
|
+
|
27
|
+
## Development
|
28
|
+
|
29
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
30
|
+
|
31
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
32
|
+
|
33
|
+
## Contributing
|
34
|
+
|
35
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/funcrunner.
|
data/Rakefile
ADDED
@@ -0,0 +1,236 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FuncRunner
|
4
|
+
class Application
|
5
|
+
attr_accessor :function_registry, :is_running, :logger, :config
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@function_registry = {}
|
9
|
+
@is_running = false
|
10
|
+
@logger = FuncRunner.config.logger
|
11
|
+
@config = FuncRunner.config
|
12
|
+
if @config.api_key.nil?
|
13
|
+
raise "Missing Func Runner API key. Set FUNCRUNNER_API_KEY environment variable or use config.api_key="
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def function(name, &definition)
|
18
|
+
function_definition = FunctionDefinition.new(name)
|
19
|
+
function_definition.instance_eval(&definition)
|
20
|
+
@function_registry[name] = function_definition.to_h
|
21
|
+
logger.info("Registered function '#{name}' with params #{function_definition.param_types}")
|
22
|
+
end
|
23
|
+
|
24
|
+
def fetch_run_data(message)
|
25
|
+
url = "https://proxy.funcrunner.com/v1/threads/#{message.thread_id}/runs/#{message.run_id}"
|
26
|
+
logger.debug("Fetching run data from URL: #{url}", message_id: message.id, run_id: message.run_id, correlation_id: message.correlation_id)
|
27
|
+
response = make_request(:get, url, message)
|
28
|
+
return JSON.parse(response.body) if response&.status == 200
|
29
|
+
|
30
|
+
logger.error("Failed to fetch run data", url: url)
|
31
|
+
nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def make_request(method, url, message = nil, data = nil)
|
35
|
+
headers = {"Authorization" => "Bearer #{@config.api_key}"}
|
36
|
+
Faraday.send(method, url, data, headers)
|
37
|
+
rescue Faraday::Error => e
|
38
|
+
logger.error("HTTP request failed", method: method, url: url, error: e.message, correlation_id: message&.correlation_id || "N/A")
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def process_queue_message(message)
|
43
|
+
logger.info("Processing queue message", message_id: message.id, correlation_id: message.correlation_id)
|
44
|
+
run = fetch_run_data(message)
|
45
|
+
return if run.nil?
|
46
|
+
|
47
|
+
tool_calls = extract_tool_calls(run)
|
48
|
+
function_executions = process_tool_calls(tool_calls)
|
49
|
+
|
50
|
+
result = RunResult.new(run_id: message.run_id, thread_id: message.thread_id, tool_outputs: [])
|
51
|
+
function_executions.each do |fe|
|
52
|
+
output = execute_function(fe, message)
|
53
|
+
if output.is_a?(String) || output.nil?
|
54
|
+
result.tool_outputs << {tool_call_id: fe.tool_call_id, output: output}
|
55
|
+
else
|
56
|
+
logger.error("#{fe.name} returned non-string value. OpenAI expects functions to return a string.")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
result
|
61
|
+
end
|
62
|
+
|
63
|
+
def execute_function(fe, message)
|
64
|
+
unless @function_registry.key?(fe.name)
|
65
|
+
logger.error("Function not found in registry", function_name: fe.name, available_functions: @function_registry.keys, message: message.to_h, correlation_id: message.correlation_id)
|
66
|
+
raise NotImplementedError, "Function '#{fe.name}' not found in registry."
|
67
|
+
end
|
68
|
+
|
69
|
+
function = @function_registry[fe.name]
|
70
|
+
logger.info("Executing function", function_name: fe.name, arguments: fe.arguments, correlation_id: message.correlation_id)
|
71
|
+
function.call(**fe.arguments)
|
72
|
+
rescue ArgumentError => e
|
73
|
+
logger.error("Argument error when calling function", function_name: fe.name, error: e.message, message: message.to_h, correlation_id: message.correlation_id)
|
74
|
+
raise "Argument error when calling '#{fe.name}': #{e.message}"
|
75
|
+
end
|
76
|
+
|
77
|
+
def extract_tool_calls(run)
|
78
|
+
tool_calls = run.dig("required_action", "submit_tool_outputs", "tool_calls") || []
|
79
|
+
tool_calls.is_a?(Array) ? tool_calls : []
|
80
|
+
end
|
81
|
+
|
82
|
+
def process_tool_calls(tool_calls)
|
83
|
+
tool_calls.map do |tool_call|
|
84
|
+
name = tool_call.dig("function", "name")
|
85
|
+
logger.info("Processing function call request", function_name: name, tool_call_id: tool_call["id"])
|
86
|
+
arguments = begin
|
87
|
+
JSON.parse(tool_call.dig("function", "arguments"))
|
88
|
+
rescue
|
89
|
+
{}
|
90
|
+
end
|
91
|
+
FunctionExecution.new(name: name, arguments: arguments, tool_call_id: tool_call["id"])
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def dequeue_message
|
96
|
+
response = make_request(:get, "https://queue.funcrunner.com/messages")
|
97
|
+
return unless response&.status == 200
|
98
|
+
|
99
|
+
messages = JSON.parse(response.body)
|
100
|
+
unless messages.empty?
|
101
|
+
logger.info("Message dequeued", message_id: messages[0]["id"])
|
102
|
+
Message.new(messages[0])
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def submit_function_results(result)
|
107
|
+
logger.info("Submitting function results", run_id: result.run_id, correlation_id: result.thread_id)
|
108
|
+
url = "https://proxy.funcrunner.com/v1/threads/#{result.thread_id}/runs/#{result.run_id}/submit_tool_outputs"
|
109
|
+
response = make_request(:post, url, nil, result.dump_submission_response)
|
110
|
+
if response&.status == 200
|
111
|
+
logger.info("Successfully submitted function results", run_id: result.run_id, correlation_id: result.thread_id)
|
112
|
+
true
|
113
|
+
else
|
114
|
+
logger.error("Failed to submit function results", run_id: result.run_id, correlation_id: result.thread_id)
|
115
|
+
false
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def delete_message(message_id, correlation_id)
|
120
|
+
logger.info("Deleting message from the queue", message_id: message_id, correlation_id: correlation_id)
|
121
|
+
response = make_request(:delete, "https://queue.funcrunner.com/messages/#{message_id}")
|
122
|
+
if response&.status == 200
|
123
|
+
logger.info("Successfully deleted queue message", message_id: message_id, correlation_id: correlation_id)
|
124
|
+
else
|
125
|
+
logger.error("Failed to delete message from the queue", message_id: message_id, correlation_id: correlation_id)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def configure_assistant
|
130
|
+
logger.info("Updating assistant functions...", assistant_id: config.assistant_id)
|
131
|
+
|
132
|
+
url = "https://proxy.funcrunner.com/v1/assistants/#{config.assistant_id}"
|
133
|
+
response = make_request(:get, url)
|
134
|
+
|
135
|
+
unless response&.status == 200
|
136
|
+
logger.error("Failed to fetch assistant", url: url, error: response&.body)
|
137
|
+
return
|
138
|
+
end
|
139
|
+
|
140
|
+
assistant = JSON.parse(response.body)
|
141
|
+
|
142
|
+
# Filter and update existing tools
|
143
|
+
updated_tools = assistant["tools"].select { |tool| %w[code_interpreter file_search].any? { |t| tool.include?(t) } }
|
144
|
+
|
145
|
+
# Generate function specs and append them to the assistant's tools
|
146
|
+
function_specs = function_registry.values.map { |func| generate_function_spec(func) }
|
147
|
+
assistant["tools"] = updated_tools + function_specs
|
148
|
+
|
149
|
+
# Remove read-only attributes to avoid errors in the update request
|
150
|
+
%w[id object created_at].each { |attr| assistant.delete(attr) }
|
151
|
+
assistant.delete("description") if assistant["description"].nil?
|
152
|
+
|
153
|
+
# Update assistant with new tools
|
154
|
+
update_response = make_request(:post, url, nil, assistant.to_json)
|
155
|
+
if update_response&.status == 200
|
156
|
+
logger.info("Successfully updated assistant functions.", assistant_id: config.assistant_id)
|
157
|
+
else
|
158
|
+
logger.error("Failed to update assistant", url: url, status_code: update_response&.status, error: update_response&.body)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def run
|
163
|
+
logger.info("Starting Func Runner application...")
|
164
|
+
logger.info("Available functions: #{function_registry.keys.join(", ")}") unless function_registry.empty?
|
165
|
+
logger.info("No functions registered.") if function_registry.empty?
|
166
|
+
@is_running = true
|
167
|
+
|
168
|
+
configure_assistant if config.auto_update && config.assistant_id
|
169
|
+
|
170
|
+
begin
|
171
|
+
while @is_running
|
172
|
+
message = dequeue_message
|
173
|
+
run_result = process_queue_message(message) if message
|
174
|
+
submit_function_results(run_result) if run_result
|
175
|
+
delete_message(message.id, message.correlation_id) if run_result
|
176
|
+
sleep(config.polling_interval)
|
177
|
+
end
|
178
|
+
rescue Interrupt
|
179
|
+
logger.info("Shutting down gracefully...")
|
180
|
+
@is_running = false
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def stop
|
185
|
+
@is_running = false
|
186
|
+
logger.info("Func Runner application stopped")
|
187
|
+
end
|
188
|
+
|
189
|
+
private
|
190
|
+
|
191
|
+
# Generate function specification compatible with OpenAI
|
192
|
+
def generate_function_spec(func)
|
193
|
+
func_name = func[:name]
|
194
|
+
{
|
195
|
+
"type" => "function",
|
196
|
+
"function" => {
|
197
|
+
"name" => func_name,
|
198
|
+
"description" => func[:desc],
|
199
|
+
"parameters" => generate_parameters_schema(func),
|
200
|
+
"strict" => true
|
201
|
+
}
|
202
|
+
}
|
203
|
+
end
|
204
|
+
|
205
|
+
# Generates JSON schema for function parameters
|
206
|
+
def generate_parameters_schema(func)
|
207
|
+
parameters_schema = {"type" => "object", "properties" => {}}
|
208
|
+
required_params = []
|
209
|
+
|
210
|
+
func[:params]&.each do |name, type|
|
211
|
+
parameters_schema["properties"][name] = if type.to_s.downcase.to_sym == :array
|
212
|
+
{type: "array", items: {type: "string"}, description: "#{name} parameter"}
|
213
|
+
else
|
214
|
+
{type: map_type_to_json(type.to_s.downcase.to_sym), description: "#{name} parameter"}
|
215
|
+
end
|
216
|
+
required_params << name
|
217
|
+
end
|
218
|
+
|
219
|
+
parameters_schema["required"] = required_params unless required_params.empty?
|
220
|
+
parameters_schema["additionalProperties"] = false
|
221
|
+
parameters_schema
|
222
|
+
end
|
223
|
+
|
224
|
+
# Helper to map Ruby types to JSON-compatible types
|
225
|
+
def map_type_to_json(type)
|
226
|
+
case type
|
227
|
+
when :string then "string"
|
228
|
+
when :integer then "number"
|
229
|
+
when :float then "number"
|
230
|
+
when :boolean then "boolean"
|
231
|
+
when :hash then "object"
|
232
|
+
else "string"
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FuncRunner
|
4
|
+
class FunctionDefinition
|
5
|
+
attr_reader :name, :desc, :param_types, :function_body
|
6
|
+
|
7
|
+
def initialize(name)
|
8
|
+
@name = name
|
9
|
+
@param_types = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
# Define expected parameter types
|
13
|
+
def params(types)
|
14
|
+
@param_types = types
|
15
|
+
end
|
16
|
+
|
17
|
+
def description(desc)
|
18
|
+
@desc = desc
|
19
|
+
end
|
20
|
+
|
21
|
+
# Define the function body
|
22
|
+
def body(&block)
|
23
|
+
@function_body = block
|
24
|
+
end
|
25
|
+
|
26
|
+
# Convert to hash for storage
|
27
|
+
def to_h
|
28
|
+
{name: name, desc: desc, params: param_types, function: function_body}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FuncRunner
|
4
|
+
class FunctionExecution
|
5
|
+
attr_accessor :name, :arguments, :tool_call_id
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
@name = options[:name]
|
9
|
+
@arguments = options[:arguments] || {}
|
10
|
+
@tool_call_id = options[:tool_call_id]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FuncRunner
|
4
|
+
class FunctionExecutionPayload
|
5
|
+
attr_accessor :thread_id, :run_id, :function_executions
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
@thread_id = options[:thread_id]
|
9
|
+
@run_id = options[:run_id]
|
10
|
+
@function_executions = options[:function_executions] || []
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FuncRunner
|
4
|
+
class Message
|
5
|
+
attr_accessor :id, :run_id, :thread_id, :integration_id, :correlation_id, :expires_at, :visible_at, :in_flight
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
@id = options[:id]
|
9
|
+
@run_id = options[:run_id]
|
10
|
+
@thread_id = options[:thread_id]
|
11
|
+
@integration_id = options[:integration_id]
|
12
|
+
@correlation_id = options[:correlation_id]
|
13
|
+
@expires_at = options[:expires_at]
|
14
|
+
@visible_at = options[:visible_at]
|
15
|
+
@in_flight = options[:in_flight]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FuncRunner
|
4
|
+
class RunResult
|
5
|
+
attr_accessor :run_id, :thread_id, :tool_outputs
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
@run_id = options[:run_id]
|
9
|
+
@thread_id = options[:thread_id]
|
10
|
+
@tool_outputs = options[:tool_outputs] || []
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_json
|
14
|
+
{ tool_outputs: @tool_outputs }.to_json
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/func_runner.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "semantic_logger"
|
4
|
+
SemanticLogger.add_appender(io: STDOUT, formatter: :json)
|
5
|
+
|
6
|
+
require_relative "func_runner/version"
|
7
|
+
require_relative "func_runner/application"
|
8
|
+
require_relative "func_runner/function_definition"
|
9
|
+
require_relative "func_runner/function_execution"
|
10
|
+
require_relative "func_runner/function_execution_payload"
|
11
|
+
require_relative "func_runner/message"
|
12
|
+
require_relative "func_runner/run_result"
|
13
|
+
|
14
|
+
module FuncRunner
|
15
|
+
class Error < StandardError; end
|
16
|
+
|
17
|
+
class Configuration
|
18
|
+
attr_accessor :api_key, :assistant_id, :polling_interval, :auto_update, :logger
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
@api_key = ENV["FUNCRUNNER_API_KEY"]
|
22
|
+
@assistant_id = nil
|
23
|
+
@polling_interval = 5.0
|
24
|
+
@auto_update = true
|
25
|
+
@logger = SemanticLogger["Func Runner Application"]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Class-level access to configuration
|
30
|
+
class << self
|
31
|
+
attr_accessor :configuration, :app
|
32
|
+
|
33
|
+
# Access the configuration instance
|
34
|
+
def config
|
35
|
+
@configuration ||= Configuration.new
|
36
|
+
end
|
37
|
+
|
38
|
+
def application
|
39
|
+
@application ||= FuncRunner::Application.new
|
40
|
+
yield @application if block_given?
|
41
|
+
end
|
42
|
+
|
43
|
+
# Allow block-based configuration
|
44
|
+
def configure
|
45
|
+
yield config if block_given?
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/sig/funcrunner.rbs
ADDED
metadata
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: funcrunner
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Matthew Orahood
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-11-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: faraday
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.12'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.12'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: semantic_logger
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '4.16'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '4.16'
|
41
|
+
description: Ruby wrapper for Func Runner.
|
42
|
+
email:
|
43
|
+
- matthew@funcrunner.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- ".idea/.gitignore"
|
49
|
+
- ".idea/aws.xml"
|
50
|
+
- ".idea/funcrunner.iml"
|
51
|
+
- ".idea/inspectionProfiles/Project_Default.xml"
|
52
|
+
- ".idea/misc.xml"
|
53
|
+
- ".idea/modules.xml"
|
54
|
+
- ".idea/vcs.xml"
|
55
|
+
- ".standard.yml"
|
56
|
+
- README.md
|
57
|
+
- Rakefile
|
58
|
+
- lib/func_runner.rb
|
59
|
+
- lib/func_runner/application.rb
|
60
|
+
- lib/func_runner/function_definition.rb
|
61
|
+
- lib/func_runner/function_execution.rb
|
62
|
+
- lib/func_runner/function_execution_payload.rb
|
63
|
+
- lib/func_runner/message.rb
|
64
|
+
- lib/func_runner/run_result.rb
|
65
|
+
- lib/func_runner/version.rb
|
66
|
+
- sig/funcrunner.rbs
|
67
|
+
homepage: https://funcrunner.com
|
68
|
+
licenses: []
|
69
|
+
metadata:
|
70
|
+
homepage_uri: https://funcrunner.com
|
71
|
+
post_install_message:
|
72
|
+
rdoc_options: []
|
73
|
+
require_paths:
|
74
|
+
- lib
|
75
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: 3.0.0
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
85
|
+
requirements: []
|
86
|
+
rubygems_version: 3.5.23
|
87
|
+
signing_key:
|
88
|
+
specification_version: 4
|
89
|
+
summary: Ruby wrapper for Func Runner.
|
90
|
+
test_files: []
|