chinf 0.0.1__tar.gz

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.
chinf-0.0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alexander Fedotov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
chinf-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,161 @@
1
+ Metadata-Version: 2.4
2
+ Name: chinf
3
+ Version: 0.0.1
4
+ Summary: Package description
5
+ Author-email: Machina Ratiocinatrix <machina.ratio@gmail.com>, Alexander Fedotov <alex.fedotov@aol.com>
6
+ Project-URL: Homepage, https://github.com/alxfed/chinf
7
+ Keywords: chinf
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: pyyaml==6.0.3
15
+ Dynamic: license-file
16
+
17
+ # Chinf
18
+ API calls to Cheap Inference without dependencies.
19
+ <pre>
20
+ pip install chinf
21
+ </pre>
22
+ Then:
23
+ ```Python
24
+ # Python
25
+ from yaml import safe_load as yl
26
+ from chinf.chat import chat_complete as cc
27
+
28
+ kwargs = """ # this is a string in YAML format
29
+ max_tokens: 32000
30
+ stop_sequences:
31
+ - STOP
32
+ - "\nTitle"
33
+ temperature: 1.0
34
+ top_k: 10
35
+ top_p: 0.5
36
+ reasoning_effort: high
37
+ """
38
+
39
+ instruction = 'You are a helpful assistant. Do not use markdown or lists in your responses.'
40
+
41
+ weather_tool = """ # YAML definition of a function (old format)
42
+ type: function
43
+ function:
44
+ name: get_weather
45
+ description: Determine weather in a location
46
+ parameters:
47
+ type: object
48
+ properties:
49
+ location:
50
+ type: string
51
+ description: The city and state, e.g. San Francisco, CA
52
+ additionalProperties: false
53
+ required:
54
+ - location
55
+ """
56
+
57
+ tools = [yl(weather_tool)]
58
+
59
+ msgs = [{'role': 'user', 'content': 'What is the weather in Chicago, IL and Paris, France?'}]
60
+
61
+ thoughts, text = cc(
62
+ messages=msgs,
63
+ instructions=instruction,
64
+ tools=tools,
65
+ **yl(kwargs)
66
+ )
67
+ ```
68
+ or
69
+ ```Python
70
+ from yaml import safe_load as yl
71
+ from chinf.messages import message
72
+
73
+
74
+ kwargs = """ # this is a string in YAML format
75
+ max_tokens: 32000
76
+ stop_sequences:
77
+ - STOP
78
+ - "\nTitle"
79
+ temperature: 1.0
80
+ top_k: 10
81
+ top_p: 0.5
82
+ thinking:
83
+ type: enabled
84
+ budget_tokens: 24576
85
+ display: summarized
86
+ tool_choice:
87
+ type: auto
88
+ disable_parallel_tool_use: false
89
+ """
90
+
91
+ instruction = 'You are a helpful assistant. Do not use markdown or lists in your responses.'
92
+
93
+ get_weather_tool_str = """ # YAML definition of a function
94
+ name: get_weather
95
+ description: Determine weather in a location
96
+ input_schema:
97
+ type: object
98
+ properties:
99
+ location:
100
+ type: string
101
+ description: The city and state, e.g. San Francisco, CA
102
+ additionalProperties: true
103
+ required:
104
+ - location
105
+ """
106
+
107
+ tools = [yl(get_weather_tool_str)]
108
+
109
+ msg = [{'role': 'user', 'content': 'What is the weather in Chicago, IL and Paris, France?'}]
110
+
111
+ thoughts, text = message(
112
+ messages=msg,
113
+ instructions=instruction,
114
+ tools=tools,
115
+ **yl(kwargs)
116
+ )
117
+ ```
118
+ or
119
+
120
+ ```Python
121
+ from yaml import safe_load as yl
122
+ from chinf.responses import respond
123
+
124
+
125
+ kwargs = """ # this is a string in YAML format
126
+ max_tokens: 64000
127
+ temperature: 1.0
128
+ """
129
+
130
+ msgs = [{'role': 'user', 'content': 'What is the weather in Chicago, IL and Paris, France?'}]
131
+
132
+ weather_tool = """ # YAML definition of a function (new format)
133
+ type: function
134
+ name: get_weather
135
+ description: Determine weather in a location
136
+ parameters:
137
+ type: object
138
+ properties:
139
+ location:
140
+ type: string
141
+ description: The city and state, e.g. San Francisco, CA
142
+ additionalProperties: false
143
+ required:
144
+ - location
145
+ """
146
+
147
+ tools = [yl(weather_tool)]
148
+
149
+ instructions = """
150
+ You are a helpful assistant.
151
+ Rubric: respond in plain text without any markdown, emphasis or lists;
152
+ all paragraphs except the first one should begin with a newline and a tab.
153
+ """
154
+
155
+ thougts, text = respond(
156
+ messages=msgs,
157
+ instructions=instructions,
158
+ tools=tools,
159
+ **yl(kwargs)
160
+ )
161
+ ```
chinf-0.0.1/README.md ADDED
@@ -0,0 +1,145 @@
1
+ # Chinf
2
+ API calls to Cheap Inference without dependencies.
3
+ <pre>
4
+ pip install chinf
5
+ </pre>
6
+ Then:
7
+ ```Python
8
+ # Python
9
+ from yaml import safe_load as yl
10
+ from chinf.chat import chat_complete as cc
11
+
12
+ kwargs = """ # this is a string in YAML format
13
+ max_tokens: 32000
14
+ stop_sequences:
15
+ - STOP
16
+ - "\nTitle"
17
+ temperature: 1.0
18
+ top_k: 10
19
+ top_p: 0.5
20
+ reasoning_effort: high
21
+ """
22
+
23
+ instruction = 'You are a helpful assistant. Do not use markdown or lists in your responses.'
24
+
25
+ weather_tool = """ # YAML definition of a function (old format)
26
+ type: function
27
+ function:
28
+ name: get_weather
29
+ description: Determine weather in a location
30
+ parameters:
31
+ type: object
32
+ properties:
33
+ location:
34
+ type: string
35
+ description: The city and state, e.g. San Francisco, CA
36
+ additionalProperties: false
37
+ required:
38
+ - location
39
+ """
40
+
41
+ tools = [yl(weather_tool)]
42
+
43
+ msgs = [{'role': 'user', 'content': 'What is the weather in Chicago, IL and Paris, France?'}]
44
+
45
+ thoughts, text = cc(
46
+ messages=msgs,
47
+ instructions=instruction,
48
+ tools=tools,
49
+ **yl(kwargs)
50
+ )
51
+ ```
52
+ or
53
+ ```Python
54
+ from yaml import safe_load as yl
55
+ from chinf.messages import message
56
+
57
+
58
+ kwargs = """ # this is a string in YAML format
59
+ max_tokens: 32000
60
+ stop_sequences:
61
+ - STOP
62
+ - "\nTitle"
63
+ temperature: 1.0
64
+ top_k: 10
65
+ top_p: 0.5
66
+ thinking:
67
+ type: enabled
68
+ budget_tokens: 24576
69
+ display: summarized
70
+ tool_choice:
71
+ type: auto
72
+ disable_parallel_tool_use: false
73
+ """
74
+
75
+ instruction = 'You are a helpful assistant. Do not use markdown or lists in your responses.'
76
+
77
+ get_weather_tool_str = """ # YAML definition of a function
78
+ name: get_weather
79
+ description: Determine weather in a location
80
+ input_schema:
81
+ type: object
82
+ properties:
83
+ location:
84
+ type: string
85
+ description: The city and state, e.g. San Francisco, CA
86
+ additionalProperties: true
87
+ required:
88
+ - location
89
+ """
90
+
91
+ tools = [yl(get_weather_tool_str)]
92
+
93
+ msg = [{'role': 'user', 'content': 'What is the weather in Chicago, IL and Paris, France?'}]
94
+
95
+ thoughts, text = message(
96
+ messages=msg,
97
+ instructions=instruction,
98
+ tools=tools,
99
+ **yl(kwargs)
100
+ )
101
+ ```
102
+ or
103
+
104
+ ```Python
105
+ from yaml import safe_load as yl
106
+ from chinf.responses import respond
107
+
108
+
109
+ kwargs = """ # this is a string in YAML format
110
+ max_tokens: 64000
111
+ temperature: 1.0
112
+ """
113
+
114
+ msgs = [{'role': 'user', 'content': 'What is the weather in Chicago, IL and Paris, France?'}]
115
+
116
+ weather_tool = """ # YAML definition of a function (new format)
117
+ type: function
118
+ name: get_weather
119
+ description: Determine weather in a location
120
+ parameters:
121
+ type: object
122
+ properties:
123
+ location:
124
+ type: string
125
+ description: The city and state, e.g. San Francisco, CA
126
+ additionalProperties: false
127
+ required:
128
+ - location
129
+ """
130
+
131
+ tools = [yl(weather_tool)]
132
+
133
+ instructions = """
134
+ You are a helpful assistant.
135
+ Rubric: respond in plain text without any markdown, emphasis or lists;
136
+ all paragraphs except the first one should begin with a newline and a tab.
137
+ """
138
+
139
+ thougts, text = respond(
140
+ messages=msgs,
141
+ instructions=instructions,
142
+ tools=tools,
143
+ **yl(kwargs)
144
+ )
145
+ ```
@@ -0,0 +1,27 @@
1
+ [build-system]
2
+ requires = ["setuptools>=67.0"]
3
+ build-backend = "setuptools.build_meta"
4
+ [project]
5
+ name = "chinf"
6
+ version = "0.0.1"
7
+ authors = [
8
+ {name="Machina Ratiocinatrix", email="machina.ratio@gmail.com"},
9
+ {name="Alexander Fedotov", email="alex.fedotov@aol.com"}
10
+ ]
11
+ description = "Package description"
12
+ readme = "README.md"
13
+ requires-python = ">=3.10"
14
+ classifiers=[
15
+ "Programming Language :: Python :: 3",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Operating System :: OS Independent",
18
+ ]
19
+
20
+ keywords = ["chinf"]
21
+
22
+ dependencies = [
23
+ "pyyaml == 6.0.3"
24
+ ]
25
+
26
+ [project.urls]
27
+ "Homepage" = "https://github.com/alxfed/chinf"
chinf-0.0.1/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,34 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Python
3
+
4
+ """Copyright (c) Alexander Fedotov.
5
+ This source code is licensed under the license found in the
6
+ LICENSE file in the root directory of this source tree.
7
+ """
8
+ from .utils import (query,
9
+ headers,
10
+ get_function,
11
+ get_func_args,
12
+ call_function,
13
+ decode_output,
14
+ default_model)
15
+ from .chat import chat_complete
16
+ from .completion import complete
17
+ from .responses import respond
18
+ from .messages import message
19
+
20
+ __all__ = [
21
+ "chat_complete",
22
+ "complete",
23
+ "respond",
24
+ "message",
25
+ # The API calling
26
+ "query",
27
+ "headers",
28
+ # and all the function calling stuff
29
+ "get_function",
30
+ "get_func_args",
31
+ "call_function",
32
+ "decode_output",
33
+ "default_model"
34
+ ]
@@ -0,0 +1,191 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Python
3
+
4
+ """Copyright (c) Alexander Fedotov.
5
+ This source code is licensed under the license found in the
6
+ LICENSE file in the root directory of this source tree.
7
+ """
8
+ import json
9
+ from os import environ
10
+ from .utils import (query,
11
+ default_model,
12
+ get_function,
13
+ get_func_args,
14
+ call_function)
15
+
16
+
17
+ def get_weather(location):
18
+ print(f"Executing weather tool for location: {location}")
19
+ return {"temperature": "72F", "condition": "Sunny"}
20
+ #
21
+ #
22
+ # def query(payload):
23
+ # # Convert data dictionary to JSON and encode it to bytes
24
+ # data_bytes = json.dumps(payload).encode('utf-8')
25
+ # # Create the Request object
26
+ # req = urllib.request.Request(
27
+ # f'{api_base}/chat/completions',
28
+ # data=data_bytes,
29
+ # headers=headers,
30
+ # method="POST")
31
+ # # Try to query
32
+ # try:
33
+ # # Execute the request
34
+ # with urllib.request.urlopen(req, timeout=3000) as response:
35
+ # response_data = response.read().decode('utf-8')
36
+ # output = json.loads(response_data)
37
+ # return output
38
+ #
39
+ # except urllib.error.HTTPError as e:
40
+ # # Handle HTTP errors (e.g., 401 Unauthorized, 400 Bad Request)
41
+ # error_info = e.read().decode('utf-8', errors='ignore')
42
+ # print(f"HTTP Error {e.code}: {e.reason}")
43
+ # print(f"Error Details: {error_info}")
44
+ # return {}
45
+ #
46
+ # except urllib.error.URLError as e:
47
+ # # Handle network/connection errors
48
+ # print(f"Failed to reach the server: {e.reason}")
49
+ # return {}
50
+
51
+
52
+ def chat_complete(messages=None, instructions=None, tools=None, **kwargs):
53
+ """ All parameters should be in kwargs, but they are optional
54
+ """
55
+ # Receive the instruction
56
+ instruction = kwargs.get('system_instruction', instructions)
57
+ first_message = [dict(role='system', content=instruction)] if instruction else []
58
+
59
+ # add contents and user text to the first (instruction) message
60
+ first_message.extend(messages)
61
+ instruction_and_contents = first_message
62
+
63
+ # Define the initial payload
64
+ payload = {
65
+ "model": kwargs.get("model", default_model),
66
+ "messages": instruction_and_contents,
67
+ "max_tokens": kwargs.get("max_tokens", 132000),
68
+ "reasoning_effort": "max",
69
+ }
70
+ # Tools if there are some
71
+ if tools:
72
+ payload['tools'] = tools
73
+ payload['tool_choice'] = 'auto'
74
+
75
+ while True:
76
+ # Query the API
77
+ result = query(payload, '/chat/completions')
78
+ message = result['choices'][0]['message']
79
+ instruction_and_contents.append(message)
80
+ thoughts = message['reasoning_content']
81
+ text = message['content']
82
+ function_calls = message.get('tool_calls', [])
83
+
84
+ if function_calls:
85
+ for function_call in function_calls:
86
+ call_id = function_call.get('id')
87
+ func = function_call.get('function')
88
+ func_name = func.get('name')
89
+ func_args_str = func.get('arguments', '{}')
90
+
91
+ try:
92
+ if isinstance(func_args_str, str):
93
+ func_args = json.loads(func_args_str)
94
+ else:
95
+ func_args = func_args_str
96
+ except Exception as e:
97
+ func_args = {}
98
+ print(f"Error parsing tool arguments for {func_name}: {e}")
99
+
100
+ # Look up tool by name in globals and caller frames
101
+ func = globals().get(func_name)
102
+ # if not func:
103
+ # import inspect
104
+ # frame = inspect.currentframe().f_back
105
+ # while frame:
106
+ # if func_name in frame.f_globals:
107
+ # func = frame.f_globals[func_name]
108
+ # break
109
+ # frame = frame.f_back
110
+
111
+ if func and callable(func):
112
+ try:
113
+ tool_result = func(**func_args)
114
+ if isinstance(tool_result, (dict, list)):
115
+ result = json.dumps(tool_result)
116
+ else:
117
+ result = str(tool_result)
118
+ except Exception as e:
119
+ result = f"Error executing tool {func_name}: {str(e)}"
120
+ print(result)
121
+ else:
122
+ result = f"Error: Tool function {func_name} not found."
123
+ print(result)
124
+
125
+ tool_message = {
126
+ "role": "tool",
127
+ "tool_call_id": call_id,
128
+ "content": result
129
+ }
130
+ # Add the response
131
+ instruction_and_contents.append(tool_message)
132
+ else:
133
+ break
134
+ return thoughts, text
135
+
136
+
137
+ if __name__ == "__main__":
138
+
139
+ def get_weather(location):
140
+ print(f"Executing weather tool for location: {location}")
141
+ return {"temperature": "72F", "condition": "Sunny"}
142
+
143
+ get_weather_tool = {
144
+ "type": "function",
145
+ "function": {
146
+ "name": "get_weather",
147
+ "description": "Determine weather in my location",
148
+ "parameters": {
149
+ "type": "object",
150
+ "properties": {
151
+ "location": {
152
+ "type": "string",
153
+ "description": "The city and state, e.g. San Francisco, CA"
154
+ },
155
+ },
156
+ "additionalProperties": False,
157
+ "required": [
158
+ "location"
159
+ ]
160
+ }
161
+ }
162
+ }
163
+
164
+ tools = [get_weather_tool]
165
+ messages = [
166
+ {"role": "user", "content": "Compare the weather in Oakland, CA and weather in Paris, France. Use the tool for learning both."}
167
+ ]
168
+ instructions = "You are a helpful assistant with tools."
169
+
170
+ thought, txt = respond(messages, instructions, tools)
171
+ print(thought, txt)
172
+ ...
173
+ """
174
+ {
175
+ "type": "function",
176
+ "function": {
177
+ "name": "submit_arbitrary_data",
178
+ "description": "CRITICAL: You must invent and generate all keys for this payload based on the context. Do not send an empty object. Every key you create must map to a string value.",
179
+ "strict": false,
180
+ "parameters": {
181
+ "type": "object",
182
+ "properties": {},
183
+ "additionalProperties": {
184
+ "type": "string"
185
+ }
186
+ }
187
+ }
188
+ }
189
+
190
+ Payload Rule: When executing the submit_arbitrary_data tool, you must dynamically generate the entire payload structure yourself. Analyze the user's input, extract all relevant variables, invent descriptive key names for them, and assign them values. Every single value must be a string.
191
+ """
@@ -0,0 +1,59 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Python
3
+
4
+ """Copyright (c) Alexander Fedotov.
5
+ This source code is licensed under the license found in the
6
+ LICENSE file in the root directory of this source tree.
7
+ """
8
+ from .utils import (default_model,
9
+ query)
10
+
11
+
12
+ def complete(text, **kwargs):
13
+ """A completions endpoint call through requests.
14
+ kwargs:
15
+ temperature = 0 to 1.0
16
+ top_p = 0.0 to 1.0
17
+ n = 1 to 128
18
+ best_of = 4
19
+ frequency_penalty = -2.0 to 2.0
20
+ presence_penalty = -2.0 to 2.0
21
+ max_tokens = number of tokens
22
+ logprobs = number up to 5
23
+ stop = ["stop"] array of up to 4 sequences
24
+ logit_bias = map token: bias -1.0 to 1.0 (restrictive -100 to 100)
25
+ """
26
+ responses = []
27
+ payload = {
28
+ "model": kwargs.get("model", default_model),
29
+ "prompt": kwargs.get("prompt", text),
30
+ "response_format": kwargs.get('response_format', {'type': 'text'}),
31
+ "reasoning_effort": kwargs.get("reasoning_effort", "high"),
32
+ "reasoning_history":kwargs.get("reasoning_history", "interleaved"),
33
+ "thinking": kwargs.get("thinking", None),
34
+ "max_tokens": kwargs.get("max_tokens", 5),
35
+ "n": kwargs.get("n", 1),
36
+ "stop": kwargs.get("stop_sequences", ["stop"]),
37
+ # "seed": kwargs.get("seed", None),
38
+ "frequency_penalty":kwargs.get("frequency_penalty", 0),
39
+ "presence_penalty": kwargs.get("presence_penalty", 0),
40
+ # "logit_bias": kwargs.get("logit_bias", None),
41
+ "logprobs": kwargs.get("logprobs", None),
42
+ # "top_logprobs": kwargs.get("top_logprobs", None),
43
+ "temperature": kwargs.get("temperature", 1),
44
+ "top_p": kwargs.get("top_p", 1),
45
+ 'top_k': kwargs.get('top_k', 50),
46
+ 'stream': False,
47
+ # "user": kwargs.get("user", None)
48
+ }
49
+
50
+ responses = query(payload, '/completions')
51
+ response = responses['choices'][0]
52
+ text = response['text']
53
+ thoughts = ''
54
+
55
+ return thoughts, text
56
+
57
+
58
+ if __name__ == '__main__':
59
+ ...
@@ -0,0 +1,96 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Python
3
+
4
+ """Copyright (c) Alexander Fedotov.
5
+ This source code is licensed under the license found in the
6
+ LICENSE file in the root directory of this source tree.
7
+ """
8
+ from .utils import (query,
9
+ decode,
10
+ default_model,
11
+ get_function,
12
+ get_func_args,
13
+ call_function)
14
+
15
+
16
+ def get_weather(location):
17
+ # print(f"Executing weather tool for location: {location}")
18
+ return {"temperature": "72F", "condition": "Sunny"}
19
+
20
+
21
+ def message(messages=None, instructions=None, tools=None, **kwargs):
22
+ """A continuation of text with a given context and instruction.
23
+ kwargs:
24
+ temperature = 0 to 1.0
25
+ top_p = 0.0 to 1.0
26
+ top_k = The maximum number of tokens to consider when sampling.
27
+ n = 1 to ...
28
+ max_tokens = number of tokens
29
+ stop = ['stop'] array of up to 4 sequences
30
+ """
31
+ # instruction = kwargs.get('system_instruction', instructions)
32
+ # first_message = [dict(role='system', content=instruction)] if instruction else []
33
+
34
+ # contents can come in kwards or as an argument
35
+ messages = kwargs.get('messages', messages)
36
+
37
+ # add contents and user text to the first (instruction) message
38
+ # first_message.extend(messages)
39
+ # instruction_and_contents = first_message
40
+
41
+ payload = {
42
+ 'model': kwargs.get('model', default_model),
43
+ 'system': kwargs.get('system', instructions),
44
+ 'messages': kwargs.get('messages', messages),
45
+ 'output_config': kwargs.get('output_config',{'effort': 'high'}),
46
+ 'thinking': kwargs.get('thinking', {
47
+ 'type': 'enabled',
48
+ 'budget_tokens': 10000,
49
+ }),
50
+ 'tool_choice': kwargs.get('tool_choice', {'type': 'auto', 'disable_parallel_tool_use': False}),
51
+ 'max_tokens': kwargs.get('max_tokens', 4096),
52
+ 'prompt_truncate_len': kwargs.get('prompt_truncate_len', 100000),
53
+ 'n': kwargs.get('n', 1),
54
+ 'top_p': kwargs.get('top_p', 0.9),
55
+ 'top_k': kwargs.get('top_k', 10),
56
+ 'stream': False
57
+ }
58
+ if tools:
59
+ payload['tools'] = tools
60
+ payload['parallel_tool_calls'] = True
61
+ payload['tool_choice'] = 'auto'
62
+
63
+ while True:
64
+ result = query(payload, '/messages')
65
+ completion_message = result['content']
66
+ thoughts, text, function_calls = decode(completion_message)
67
+ if function_calls:
68
+ payload['messages'].append({"role": "assistant", "content": completion_message})
69
+ tools_results = []
70
+ # Call all requested functions and create response messages.
71
+ for function_call in function_calls:
72
+ call_id = function_call.get('id')
73
+ func_name = function_call.get('name', '')
74
+ func_args_def = function_call.get('input', '{}')
75
+ # Look up tool by name in globals and caller frames
76
+ func = get_function(func_name)
77
+ func_args = get_func_args(func_args_def)
78
+ result = call_function(func, func_args)
79
+ tool_message = {
80
+ "type": "tool_result",
81
+ "tool_use_id": call_id,
82
+ "content": result
83
+ }
84
+ tools_results.append(tool_message)
85
+
86
+ # Add results to payload and make a query.
87
+ payload['messages'].append({"role": "user", "content": tools_results})
88
+
89
+ else:
90
+ break
91
+
92
+ return thoughts, text
93
+
94
+
95
+ if __name__ == '__main__':
96
+ ...
@@ -0,0 +1,41 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Python
3
+
4
+ """Copyright (c) Alexander Fedotov.
5
+ This source code is licensed under the license found in the
6
+ LICENSE file in the root directory of this source tree.
7
+ """
8
+ from .utils import (query,
9
+ default_model,
10
+ decode_output)
11
+
12
+
13
+ def respond(messages=None, instructions=None, **kwargs):
14
+ """ All parameters should be in kwargs, but they are optional
15
+ """
16
+ # Receive the instruction
17
+ instruction = kwargs.get('system_instruction', instructions)
18
+
19
+ # Define the initial payload
20
+ payload = {
21
+ "model": kwargs.get("model", default_model),
22
+ "instructions": instruction,
23
+ "input": messages,
24
+ "previous_response_id": kwargs.get("previous_response_id", None),
25
+ "max_output_tokens": kwargs.get("max_tokens", 132000),
26
+ "prompt_cache_retention": "in_memory",
27
+ "include": ["reasoning.encrypted_content"],
28
+ "reasoning": {
29
+ "effort": "high",
30
+ "summary": "detailed"
31
+ }
32
+ }
33
+ # Query the API
34
+ result = query(payload, '/responses')
35
+ thoughts, text, _ = decode_output(result.get('output', {}))
36
+
37
+ return thoughts, text
38
+
39
+
40
+ if __name__ == "__main__":
41
+ ...
@@ -0,0 +1,85 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Python
3
+
4
+ """Copyright (c) Alexander Fedotov.
5
+ This source code is licensed under the license found in the
6
+ LICENSE file in the root directory of this source tree.
7
+ """
8
+ from .utils import (query,
9
+ default_model,
10
+ get_function,
11
+ get_func_args,
12
+ call_function,
13
+ decode_output)
14
+
15
+
16
+ def get_weather(location):
17
+ # print(f"Executing weather tool for location: {location}")
18
+ return {"temperature": "72F", "condition": "Sunny"}
19
+
20
+
21
+ def respond(messages=None, instructions=None, tools=None, **kwargs):
22
+ """ All parameters should be in kwargs, but they are optional
23
+ """
24
+ # Receive the instruction
25
+ instruction = kwargs.get('system_instruction', instructions)
26
+
27
+ # Define the initial payload
28
+ payload = {
29
+ "model": kwargs.get("model", default_model),
30
+ "instructions": instruction,
31
+ "input": messages,
32
+ "previous_response_id": kwargs.get("previous_response_id", None),
33
+ "max_output_tokens": kwargs.get("max_tokens", 132000),
34
+ "prompt_cache_retention": "in_memory",
35
+ "include": ["reasoning.encrypted_content"],
36
+ "reasoning": {
37
+ "effort": "high",
38
+ "summary": "detailed"
39
+ }
40
+ }
41
+ # Tools if there are some
42
+ if tools:
43
+ payload['tools'] = tools
44
+ payload['parallel_tool_calls'] = True
45
+ payload['max_tool_calls'] = kwargs.get("max_tool_calls", None)
46
+ payload['tool_choice'] = 'auto'
47
+
48
+ while True:
49
+ # Query the API
50
+ result = query(payload, '/responses')
51
+ # id of the response
52
+ response_id = result['id']
53
+ thoughts, text, function_calls = decode_output(result.get('output', {}))
54
+
55
+ if function_calls:
56
+ function_outputs_messages = []
57
+ for function_call in function_calls:
58
+ call_id = function_call.get('call_id')
59
+ func_name = function_call.get('name')
60
+ func_args_str = function_call.get('arguments', {})
61
+
62
+ # Look up tool by name in globals and in caller frames
63
+ func = get_function(func_name)
64
+ func_args = get_func_args(func_args_str)
65
+ result = call_function(func, func_args)
66
+
67
+ tool_message = {
68
+ "type": "function_call_output",
69
+ "call_id": call_id,
70
+ "output": result
71
+ }
72
+ function_outputs_messages.append(tool_message)
73
+
74
+ # Now that all responses have been gathered
75
+ # we can change the payload and send them back
76
+ payload['previous_response_id'] = response_id
77
+ payload['input'] = function_outputs_messages
78
+ else:
79
+ break
80
+
81
+ return thoughts, text
82
+
83
+
84
+ if __name__ == "__main__":
85
+ ...
@@ -0,0 +1,131 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Python
3
+
4
+ """Copyright (c) Alexander Fedotov.
5
+ This source code is licensed under the license found in the
6
+ LICENSE file in the root directory of this source tree.
7
+ """
8
+ import json
9
+ import urllib.request
10
+ import urllib.error
11
+ from os import environ
12
+
13
+
14
+ # The configuration.
15
+ api_key = environ.get("CHINF_API_KEY", '')
16
+ default_model = environ.get("CHINF_DEFAULT_MODEL", 'gpt-5.6-luna')
17
+ api_base = environ.get("CHINF_API_BASE", "https://api.cheaperinference.com/v1")
18
+
19
+ # Set the mandatory headers
20
+ headers = {
21
+ "Content-Type": "application/json",
22
+ "Authorization": f"Bearer {api_key}",
23
+ "User-Agent": "chinf"
24
+ }
25
+
26
+
27
+ def get_function(func_name):
28
+ # Look up tool by name in globals
29
+ func = globals().get(func_name)
30
+ # Look up in the caller frames
31
+ if not func:
32
+ import inspect
33
+ frame = inspect.currentframe().f_back
34
+ while frame:
35
+ if func_name in frame.f_globals:
36
+ func = frame.f_globals[func_name]
37
+ break
38
+ frame = frame.f_back
39
+
40
+ return func
41
+
42
+
43
+ def get_func_args(func_args_str):
44
+ try:
45
+ if isinstance(func_args_str, str):
46
+ func_args = json.loads(func_args_str)
47
+ else:
48
+ func_args = func_args_str
49
+ except Exception as e:
50
+ func_args = {}
51
+ print(f"Error parsing tool arguments: {e}")
52
+
53
+ return func_args
54
+
55
+
56
+ def call_function(func, func_args):
57
+ if func and callable(func):
58
+ try:
59
+ tool_result = func(**func_args)
60
+ if isinstance(tool_result, (dict, list)):
61
+ result = json.dumps(tool_result)
62
+ else:
63
+ result = str(tool_result)
64
+ except Exception as e:
65
+ result = f"Error executing tool: {str(e)}"
66
+ print(result)
67
+ else:
68
+ result = f"Error: Tool function not callable."
69
+ print(result)
70
+
71
+ return result
72
+
73
+
74
+ def query(payload, url_suffix):
75
+ # Convert data dictionary to JSON and encode it to bytes
76
+ data_bytes = json.dumps(payload).encode('utf-8')
77
+ # Create the Request object
78
+ req = urllib.request.Request(
79
+ f'{api_base}{url_suffix}',
80
+ data=data_bytes,
81
+ headers=headers,
82
+ method="POST")
83
+ # Try to query
84
+ try:
85
+ # Execute the request
86
+ with urllib.request.urlopen(req, timeout=3000) as response:
87
+ response_data = response.read().decode('utf-8')
88
+ output = json.loads(response_data)
89
+ return output
90
+
91
+ except urllib.error.HTTPError as e:
92
+ # Handle HTTP errors (e.g., 401 Unauthorized, 400 Bad Request)
93
+ error_info = e.read().decode('utf-8', errors='ignore')
94
+ print(f"HTTP Error {e.code}: {e.reason}")
95
+ print(f"Error Details: {error_info}")
96
+ return {}
97
+
98
+ except urllib.error.URLError as e:
99
+ # Handle network/connection errors
100
+ print(f"Failed to reach the server: {e.reason}")
101
+ return {}
102
+
103
+
104
+ def decode_output(output):
105
+ # Parse the result
106
+ text = ''; thoughts = ''
107
+ for part in output:
108
+ part_type = part.get('type', None)
109
+ if part_type == 'message':
110
+ text = " ".join([chunk['text'] for chunk in part['content'] if chunk['type'] == 'output_text'])
111
+ elif part_type == 'reasoning':
112
+ thoughts = " ".join([chunk['text'] for chunk in part['summary'] if chunk['type'] == 'summary_text'])
113
+ function_calls = [part for part in output if part['type'] == 'function_call']
114
+ return thoughts, text, function_calls
115
+
116
+
117
+ def decode(output):
118
+ text = ''
119
+ thoughts = ''
120
+ for chunk in output:
121
+ chunk_type = chunk.get('type', '')
122
+ if chunk_type == 'text':
123
+ addition = chunk.get('text', '')
124
+ if addition not in ('\n\n', '\n'):
125
+ text += addition
126
+
127
+ elif chunk_type == 'thinking':
128
+ thoughts += chunk.get('thinking', '')
129
+ function_calls = [part for part in output if part['type'] == 'tool_use']
130
+
131
+ return thoughts, text, function_calls
@@ -0,0 +1,161 @@
1
+ Metadata-Version: 2.4
2
+ Name: chinf
3
+ Version: 0.0.1
4
+ Summary: Package description
5
+ Author-email: Machina Ratiocinatrix <machina.ratio@gmail.com>, Alexander Fedotov <alex.fedotov@aol.com>
6
+ Project-URL: Homepage, https://github.com/alxfed/chinf
7
+ Keywords: chinf
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: pyyaml==6.0.3
15
+ Dynamic: license-file
16
+
17
+ # Chinf
18
+ API calls to Cheap Inference without dependencies.
19
+ <pre>
20
+ pip install chinf
21
+ </pre>
22
+ Then:
23
+ ```Python
24
+ # Python
25
+ from yaml import safe_load as yl
26
+ from chinf.chat import chat_complete as cc
27
+
28
+ kwargs = """ # this is a string in YAML format
29
+ max_tokens: 32000
30
+ stop_sequences:
31
+ - STOP
32
+ - "\nTitle"
33
+ temperature: 1.0
34
+ top_k: 10
35
+ top_p: 0.5
36
+ reasoning_effort: high
37
+ """
38
+
39
+ instruction = 'You are a helpful assistant. Do not use markdown or lists in your responses.'
40
+
41
+ weather_tool = """ # YAML definition of a function (old format)
42
+ type: function
43
+ function:
44
+ name: get_weather
45
+ description: Determine weather in a location
46
+ parameters:
47
+ type: object
48
+ properties:
49
+ location:
50
+ type: string
51
+ description: The city and state, e.g. San Francisco, CA
52
+ additionalProperties: false
53
+ required:
54
+ - location
55
+ """
56
+
57
+ tools = [yl(weather_tool)]
58
+
59
+ msgs = [{'role': 'user', 'content': 'What is the weather in Chicago, IL and Paris, France?'}]
60
+
61
+ thoughts, text = cc(
62
+ messages=msgs,
63
+ instructions=instruction,
64
+ tools=tools,
65
+ **yl(kwargs)
66
+ )
67
+ ```
68
+ or
69
+ ```Python
70
+ from yaml import safe_load as yl
71
+ from chinf.messages import message
72
+
73
+
74
+ kwargs = """ # this is a string in YAML format
75
+ max_tokens: 32000
76
+ stop_sequences:
77
+ - STOP
78
+ - "\nTitle"
79
+ temperature: 1.0
80
+ top_k: 10
81
+ top_p: 0.5
82
+ thinking:
83
+ type: enabled
84
+ budget_tokens: 24576
85
+ display: summarized
86
+ tool_choice:
87
+ type: auto
88
+ disable_parallel_tool_use: false
89
+ """
90
+
91
+ instruction = 'You are a helpful assistant. Do not use markdown or lists in your responses.'
92
+
93
+ get_weather_tool_str = """ # YAML definition of a function
94
+ name: get_weather
95
+ description: Determine weather in a location
96
+ input_schema:
97
+ type: object
98
+ properties:
99
+ location:
100
+ type: string
101
+ description: The city and state, e.g. San Francisco, CA
102
+ additionalProperties: true
103
+ required:
104
+ - location
105
+ """
106
+
107
+ tools = [yl(get_weather_tool_str)]
108
+
109
+ msg = [{'role': 'user', 'content': 'What is the weather in Chicago, IL and Paris, France?'}]
110
+
111
+ thoughts, text = message(
112
+ messages=msg,
113
+ instructions=instruction,
114
+ tools=tools,
115
+ **yl(kwargs)
116
+ )
117
+ ```
118
+ or
119
+
120
+ ```Python
121
+ from yaml import safe_load as yl
122
+ from chinf.responses import respond
123
+
124
+
125
+ kwargs = """ # this is a string in YAML format
126
+ max_tokens: 64000
127
+ temperature: 1.0
128
+ """
129
+
130
+ msgs = [{'role': 'user', 'content': 'What is the weather in Chicago, IL and Paris, France?'}]
131
+
132
+ weather_tool = """ # YAML definition of a function (new format)
133
+ type: function
134
+ name: get_weather
135
+ description: Determine weather in a location
136
+ parameters:
137
+ type: object
138
+ properties:
139
+ location:
140
+ type: string
141
+ description: The city and state, e.g. San Francisco, CA
142
+ additionalProperties: false
143
+ required:
144
+ - location
145
+ """
146
+
147
+ tools = [yl(weather_tool)]
148
+
149
+ instructions = """
150
+ You are a helpful assistant.
151
+ Rubric: respond in plain text without any markdown, emphasis or lists;
152
+ all paragraphs except the first one should begin with a newline and a tab.
153
+ """
154
+
155
+ thougts, text = respond(
156
+ messages=msgs,
157
+ instructions=instructions,
158
+ tools=tools,
159
+ **yl(kwargs)
160
+ )
161
+ ```
@@ -0,0 +1,15 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/chinf/__init__.py
5
+ src/chinf/chat.py
6
+ src/chinf/completion.py
7
+ src/chinf/messages.py
8
+ src/chinf/resp_simplified.py
9
+ src/chinf/responses.py
10
+ src/chinf/utils.py
11
+ src/chinf.egg-info/PKG-INFO
12
+ src/chinf.egg-info/SOURCES.txt
13
+ src/chinf.egg-info/dependency_links.txt
14
+ src/chinf.egg-info/requires.txt
15
+ src/chinf.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ pyyaml==6.0.3
@@ -0,0 +1 @@
1
+ chinf