mojentic 0.4.2__tar.gz → 0.5.0__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.
- {mojentic-0.4.2/src/mojentic.egg-info → mojentic-0.5.0}/PKG-INFO +2 -1
- {mojentic-0.4.2 → mojentic-0.5.0}/pyproject.toml +2 -1
- mojentic-0.5.0/src/_examples/current_datetime_tool_example.py +28 -0
- mojentic-0.5.0/src/_examples/design_analysis.py +42 -0
- mojentic-0.5.0/src/_examples/ensures_files_exist.py +58 -0
- mojentic-0.5.0/src/_examples/file_deduplication.py +38 -0
- mojentic-0.5.0/src/_examples/image_broker.py +14 -0
- mojentic-0.5.0/src/_examples/image_broker_splat.py +50 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/list_models.py +9 -1
- mojentic-0.5.0/src/_examples/raw.py +21 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/__init__.py +1 -0
- mojentic-0.5.0/src/mojentic/llm/gateways/anthropic.py +52 -0
- mojentic-0.5.0/src/mojentic/llm/gateways/anthropic_messages_adapter.py +72 -0
- mojentic-0.5.0/src/mojentic/llm/gateways/file_gateway.py +55 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/gateways/openai.py +1 -1
- mojentic-0.5.0/src/mojentic/llm/message_composers.py +276 -0
- mojentic-0.5.0/src/mojentic/llm/message_composers_spec.py +298 -0
- mojentic-0.5.0/src/mojentic/llm/tools/current_datetime.py +47 -0
- {mojentic-0.4.2 → mojentic-0.5.0/src/mojentic.egg-info}/PKG-INFO +2 -1
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic.egg-info/SOURCES.txt +13 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic.egg-info/requires.txt +1 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/LICENSE.md +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/README.md +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/setup.cfg +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/__init__.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/broker_as_tool.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/broker_examples.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/characterize_ollama.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/characterize_openai.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/chat_session.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/chat_session_with_tool.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/coding_file_tool.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/embeddings.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/file_tool.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/image_analysis.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/iterative_solver.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/react/__init__.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/react/agents/__init__.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/react/agents/decisioning_agent.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/react/agents/thinking_agent.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/react/formatters.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/react/models/__init__.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/react/models/base.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/react/models/events.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/react.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/recursive_agent.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/routed_send_response.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/simple_llm.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/simple_llm_repl.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/simple_structured.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/simple_tool.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/solver_chat_session.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/streaming.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/working_memory.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/__init__.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/agents/__init__.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/agents/agent_broker.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/agents/base_agent.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/agents/base_llm_agent.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/agents/base_llm_agent_spec.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/agents/correlation_aggregator_agent.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/agents/iterative_problem_solver.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/agents/output_agent.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/agents/simple_recursive_agent.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/audit/event_store.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/audit/event_store_spec.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/context/__init__.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/context/shared_working_memory.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/dispatcher.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/event.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/chat_session.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/chat_session_spec.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/gateways/__init__.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/gateways/embeddings_gateway.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/gateways/llm_gateway.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/gateways/models.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/gateways/ollama.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/gateways/ollama_messages_adapter.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/gateways/ollama_messages_adapter_spec.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/gateways/openai_message_adapter_spec.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/gateways/openai_messages_adapter.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/gateways/tokenizer_gateway.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/llm_broker.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/llm_broker_spec.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/registry/__init__.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/registry/llm_registry.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/registry/models.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/registry/populate_registry_from_ollama.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/tools/__init__.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/tools/ask_user_tool.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/tools/date_resolver.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/tools/date_resolver_spec.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/tools/file_manager.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/tools/llm_tool.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/tools/tool_wrapper.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/tools/tool_wrapper_spec.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/tools/web_search.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/router.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/router_spec.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/utils/__init__.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/utils/formatting.py +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic.egg-info/dependency_links.txt +0 -0
- {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mojentic
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Mojentic is an agentic framework that aims to provide a simple and flexible way to assemble teams of agents to solve complex problems.
|
|
5
5
|
Author-email: Stacey Vetzal <stacey@vetzal.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/mojility/mojentic
|
|
@@ -15,6 +15,7 @@ Requires-Dist: pydantic
|
|
|
15
15
|
Requires-Dist: structlog
|
|
16
16
|
Requires-Dist: ollama
|
|
17
17
|
Requires-Dist: openai
|
|
18
|
+
Requires-Dist: anthropic
|
|
18
19
|
Requires-Dist: tiktoken
|
|
19
20
|
Requires-Dist: parsedatetime
|
|
20
21
|
Requires-Dist: pytz
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "mojentic"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.5.0"
|
|
4
4
|
authors = [
|
|
5
5
|
{ name = "Stacey Vetzal", email = "stacey@vetzal.com" },
|
|
6
6
|
]
|
|
@@ -18,6 +18,7 @@ dependencies = [
|
|
|
18
18
|
"structlog",
|
|
19
19
|
"ollama",
|
|
20
20
|
"openai",
|
|
21
|
+
"anthropic",
|
|
21
22
|
"tiktoken",
|
|
22
23
|
"parsedatetime",
|
|
23
24
|
"pytz",
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from mojentic.llm.gateways.models import LLMMessage
|
|
2
|
+
from mojentic.llm.llm_broker import LLMBroker
|
|
3
|
+
from mojentic.llm.tools.current_datetime import CurrentDateTimeTool
|
|
4
|
+
|
|
5
|
+
# Create an LLM broker with a specified model
|
|
6
|
+
# You can change the model to any supported model
|
|
7
|
+
llm = LLMBroker(model="qwen2.5:7b") # Using the same model as in simple_tool.py
|
|
8
|
+
|
|
9
|
+
# Create our custom tool
|
|
10
|
+
datetime_tool = CurrentDateTimeTool()
|
|
11
|
+
|
|
12
|
+
# Generate a response with tool assistance
|
|
13
|
+
result = llm.generate(
|
|
14
|
+
messages=[LLMMessage(content="What time is it right now? Also, what day of the week is it today?")],
|
|
15
|
+
tools=[datetime_tool]
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
print("LLM Response:")
|
|
19
|
+
print(result)
|
|
20
|
+
|
|
21
|
+
# You can also try with a custom format string
|
|
22
|
+
result = llm.generate(
|
|
23
|
+
messages=[LLMMessage(content="Tell me the current date in a friendly format, like 'Monday, January 1, 2023'")],
|
|
24
|
+
tools=[datetime_tool]
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
print("\nLLM Response with custom format:")
|
|
28
|
+
print(result)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from mojentic.llm.gateways import OllamaGateway
|
|
4
|
+
from mojentic.llm.gateways.models import LLMMessage
|
|
5
|
+
|
|
6
|
+
llmg = OllamaGateway()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def complete(prompt: str):
|
|
10
|
+
response = llmg.complete(
|
|
11
|
+
model="gemma3:27b",
|
|
12
|
+
messages=[
|
|
13
|
+
LLMMessage(
|
|
14
|
+
content=prompt.strip(),
|
|
15
|
+
image_paths=[
|
|
16
|
+
str(Path.cwd() / "images" / "screen_cap.png")
|
|
17
|
+
]
|
|
18
|
+
)
|
|
19
|
+
],
|
|
20
|
+
)
|
|
21
|
+
return response.content
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# It's quite good at extracting text from images
|
|
25
|
+
result = complete("Extract the text from the attached image in markdown format.")
|
|
26
|
+
print(result)
|
|
27
|
+
|
|
28
|
+
# It's not bad at extracting and structuring the text it finds in images
|
|
29
|
+
result = complete("""
|
|
30
|
+
Extract the title and text boxes from the attached image in the form of a JSON object.
|
|
31
|
+
Use descriptive key names like `title` or `bullet` or `footer`. In the values, use html
|
|
32
|
+
but don't attempt to convert all of the visual formatting, just simple formatting like
|
|
33
|
+
paragraphs, bold, or italic.
|
|
34
|
+
""")
|
|
35
|
+
print(result)
|
|
36
|
+
|
|
37
|
+
# Gemma3 is not very good at determining hex colours :)
|
|
38
|
+
result = complete("""
|
|
39
|
+
Extract the colours from the attached image in the form of a JSON object, where the key
|
|
40
|
+
is the description of how the colour is used, and the value is the colour in hex format.
|
|
41
|
+
""")
|
|
42
|
+
print(result)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import sys
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from mojentic.llm.message_composers import MessageBuilder
|
|
6
|
+
|
|
7
|
+
# Get the project root directory (2 levels up from the current script)
|
|
8
|
+
project_root = Path(os.path.dirname(os.path.abspath(__file__))).parent.parent
|
|
9
|
+
|
|
10
|
+
# Test adding non-existent image
|
|
11
|
+
print("Testing adding non-existent image:")
|
|
12
|
+
non_existent_image = project_root / 'src' / '_examples' / 'images' / 'non_existent_image.jpg'
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
message_builder = MessageBuilder("Testing non-existent image")
|
|
16
|
+
message_builder.add_image(non_existent_image)
|
|
17
|
+
print("ERROR: Expected FileNotFoundError was not raised")
|
|
18
|
+
sys.exit(1)
|
|
19
|
+
except FileNotFoundError as e:
|
|
20
|
+
print(f"Success: Caught expected exception: {e}")
|
|
21
|
+
|
|
22
|
+
# Test adding non-existent file
|
|
23
|
+
print("\nTesting adding non-existent file:")
|
|
24
|
+
non_existent_file = project_root / 'src' / '_examples' / 'non_existent_file.py'
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
message_builder = MessageBuilder("Testing non-existent file")
|
|
28
|
+
message_builder.add_file(non_existent_file)
|
|
29
|
+
print("ERROR: Expected FileNotFoundError was not raised")
|
|
30
|
+
sys.exit(1)
|
|
31
|
+
except FileNotFoundError as e:
|
|
32
|
+
print(f"Success: Caught expected exception: {e}")
|
|
33
|
+
|
|
34
|
+
# Test adding existing image
|
|
35
|
+
print("\nTesting adding existing image:")
|
|
36
|
+
existing_image = project_root / 'src' / '_examples' / 'images' / 'xbox-one.jpg'
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
message_builder = MessageBuilder("Testing existing image")
|
|
40
|
+
message_builder.add_image(existing_image)
|
|
41
|
+
print("Success: Added existing image without exception")
|
|
42
|
+
except FileNotFoundError as e:
|
|
43
|
+
print(f"ERROR: Unexpected exception: {e}")
|
|
44
|
+
sys.exit(1)
|
|
45
|
+
|
|
46
|
+
# Test adding existing file
|
|
47
|
+
print("\nTesting adding existing file:")
|
|
48
|
+
existing_file = Path(__file__) # This file itself (absolute path)
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
message_builder = MessageBuilder("Testing existing file")
|
|
52
|
+
message_builder.add_file(existing_file)
|
|
53
|
+
print("Success: Added existing file without exception")
|
|
54
|
+
except FileNotFoundError as e:
|
|
55
|
+
print(f"ERROR: Unexpected exception: {e}")
|
|
56
|
+
sys.exit(1)
|
|
57
|
+
|
|
58
|
+
print("\nAll tests passed!")
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from mojentic.llm.message_composers import MessageBuilder
|
|
4
|
+
|
|
5
|
+
# Test de-duplication of images
|
|
6
|
+
print("Testing image de-duplication:")
|
|
7
|
+
image_path = Path.cwd() / 'src' / '_examples' / 'images' / 'xbox-one.jpg'
|
|
8
|
+
|
|
9
|
+
# Create a message builder and add the same image multiple times
|
|
10
|
+
message_builder = MessageBuilder("Testing image de-duplication")
|
|
11
|
+
message_builder.add_image(image_path)
|
|
12
|
+
message_builder.add_image(image_path) # Adding the same image again
|
|
13
|
+
message_builder.add_images(image_path, image_path) # Adding the same image twice more
|
|
14
|
+
|
|
15
|
+
# Build the message and check the number of images
|
|
16
|
+
message = message_builder.build()
|
|
17
|
+
print(f"Number of images in message: {len(message.image_paths)}")
|
|
18
|
+
print(f"Expected number of images: 1")
|
|
19
|
+
print(f"De-duplication working: {len(message.image_paths) == 1}")
|
|
20
|
+
|
|
21
|
+
# Test de-duplication of files
|
|
22
|
+
print("\nTesting file de-duplication:")
|
|
23
|
+
file_path = Path('file_deduplication.py') # This file itself
|
|
24
|
+
|
|
25
|
+
# Create a message builder and add the same file multiple times
|
|
26
|
+
message_builder = MessageBuilder("Testing file de-duplication")
|
|
27
|
+
message_builder.add_file(file_path)
|
|
28
|
+
message_builder.add_file(file_path) # Adding the same file again
|
|
29
|
+
message_builder.add_files(file_path, file_path) # Adding the same file twice more
|
|
30
|
+
|
|
31
|
+
# Build the message and check the number of files
|
|
32
|
+
message = message_builder.build()
|
|
33
|
+
# Since we're using the file content in the message, we need to check file_paths directly
|
|
34
|
+
print(f"Number of files in message_builder.file_paths: {len(message_builder.file_paths)}")
|
|
35
|
+
print(f"Expected number of files: 1")
|
|
36
|
+
print(f"De-duplication working: {len(message_builder.file_paths) == 1}")
|
|
37
|
+
|
|
38
|
+
print("\nTest completed.")
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from mojentic.llm import LLMBroker
|
|
4
|
+
from mojentic.llm.message_composers import MessagesBuilder, MessageBuilder
|
|
5
|
+
|
|
6
|
+
llm = LLMBroker(model="gemma3:27b")
|
|
7
|
+
|
|
8
|
+
message = MessageBuilder("What is in this image?") \
|
|
9
|
+
.add_image(Path.cwd() / 'images' / 'xbox-one.jpg') \
|
|
10
|
+
.build()
|
|
11
|
+
|
|
12
|
+
result = llm.generate(messages=[message])
|
|
13
|
+
|
|
14
|
+
print(result)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from mojentic.llm import LLMBroker
|
|
4
|
+
from mojentic.llm.message_composers import MessageBuilder
|
|
5
|
+
|
|
6
|
+
# Initialize the LLM broker
|
|
7
|
+
llm = LLMBroker(model="gemma3:27b")
|
|
8
|
+
|
|
9
|
+
# Example 1: Adding multiple specific images using the splat operator
|
|
10
|
+
print("Example 1: Adding multiple specific images")
|
|
11
|
+
message1 = MessageBuilder("What are in these images?") \
|
|
12
|
+
.add_images(
|
|
13
|
+
Path.cwd() / 'images' / 'flash_rom.jpg',
|
|
14
|
+
Path.cwd() / 'images' / 'screen_cap.png' # Assuming this file exists
|
|
15
|
+
) \
|
|
16
|
+
.build()
|
|
17
|
+
|
|
18
|
+
# Example 2: Adding all JPG images from a directory
|
|
19
|
+
print("\nExample 2: Adding all JPG images from a directory")
|
|
20
|
+
images_dir = Path.cwd() / 'images'
|
|
21
|
+
message2 = MessageBuilder("Describe all these images:") \
|
|
22
|
+
.add_images(images_dir) \
|
|
23
|
+
.build()
|
|
24
|
+
|
|
25
|
+
# Example 3: Using a glob pattern to add specific types of images
|
|
26
|
+
print("\nExample 3: Using a glob pattern")
|
|
27
|
+
message3 = MessageBuilder("What can you tell me about these images?") \
|
|
28
|
+
.add_images(Path.cwd() / 'images' / '*.jpg') \
|
|
29
|
+
.build()
|
|
30
|
+
|
|
31
|
+
# Example 4: Combining different ways to specify images
|
|
32
|
+
print("\nExample 4: Combining different ways to specify images")
|
|
33
|
+
message4 = MessageBuilder("Analyze these images:") \
|
|
34
|
+
.add_images(
|
|
35
|
+
Path.cwd() / 'images' / 'xbox-one.jpg', # Specific image
|
|
36
|
+
images_dir, # All JPGs in directory
|
|
37
|
+
Path.cwd() / 'images' / '*.png' # All PNGs using glob pattern
|
|
38
|
+
) \
|
|
39
|
+
.build()
|
|
40
|
+
|
|
41
|
+
# Print the number of images added in each example
|
|
42
|
+
print(f"Example 1: Added {len(message1.image_paths)} image(s)")
|
|
43
|
+
print(f"Example 2: Added {len(message2.image_paths)} image(s)")
|
|
44
|
+
print(f"Example 3: Added {len(message3.image_paths)} image(s)")
|
|
45
|
+
print(f"Example 4: Added {len(message4.image_paths)} image(s)")
|
|
46
|
+
|
|
47
|
+
# Generate a response using one of the messages (e.g., message1)
|
|
48
|
+
print("\nGenerating response for Example 1...")
|
|
49
|
+
result = llm.generate(messages=[message1])
|
|
50
|
+
print(result)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
|
|
3
3
|
from mojentic.llm.gateways import OllamaGateway, OpenAIGateway
|
|
4
|
+
from mojentic.llm.gateways.anthropic import AnthropicGateway
|
|
4
5
|
|
|
5
6
|
ollama = OllamaGateway()
|
|
6
7
|
print("Ollama Models:")
|
|
@@ -12,4 +13,11 @@ print()
|
|
|
12
13
|
openai = OpenAIGateway(os.environ["OPENAI_API_KEY"])
|
|
13
14
|
print("OpenAI Models:")
|
|
14
15
|
for model in openai.get_available_models():
|
|
15
|
-
print(f"- {model}")
|
|
16
|
+
print(f"- {model}")
|
|
17
|
+
|
|
18
|
+
print()
|
|
19
|
+
|
|
20
|
+
anthropic = AnthropicGateway(os.environ["ANTHROPIC_API_KEY"])
|
|
21
|
+
print("Anthropic Models:")
|
|
22
|
+
for model in anthropic.get_available_models():
|
|
23
|
+
print(f"- {model}")
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from anthropic import Anthropic
|
|
4
|
+
|
|
5
|
+
client = Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY"))
|
|
6
|
+
|
|
7
|
+
anthropic_args = {
|
|
8
|
+
'model': "claude-3-5-haiku-20241022",
|
|
9
|
+
'system': "You are a helpful assistant.",
|
|
10
|
+
'messages': [
|
|
11
|
+
{
|
|
12
|
+
"role": "user",
|
|
13
|
+
"content": "Say hello world",
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
'max_tokens': 2000,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
response = client.messages.create(**anthropic_args)
|
|
20
|
+
|
|
21
|
+
print(response.content[0].text)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
import structlog
|
|
4
|
+
from anthropic import Anthropic
|
|
5
|
+
|
|
6
|
+
from mojentic.llm.gateways import LLMGateway
|
|
7
|
+
from mojentic.llm.gateways.models import LLMGatewayResponse, LLMToolCall, MessageRole
|
|
8
|
+
from mojentic.llm.gateways.anthropic_messages_adapter import adapt_messages_to_anthropic
|
|
9
|
+
|
|
10
|
+
logger = structlog.get_logger()
|
|
11
|
+
|
|
12
|
+
class AnthropicGateway(LLMGateway):
|
|
13
|
+
def __init__(self, api_key: str):
|
|
14
|
+
self.client = Anthropic(api_key=api_key)
|
|
15
|
+
|
|
16
|
+
def complete(self, **args) -> LLMGatewayResponse:
|
|
17
|
+
|
|
18
|
+
messages = args.get('messages')
|
|
19
|
+
|
|
20
|
+
system_messages = [m for m in messages if m.role == MessageRole.System]
|
|
21
|
+
user_messages = [m for m in messages if m.role == MessageRole.User]
|
|
22
|
+
|
|
23
|
+
anthropic_args = {
|
|
24
|
+
'model': args['model'],
|
|
25
|
+
'system': " " . join([m.content for m in system_messages]) if system_messages else None,
|
|
26
|
+
'messages': adapt_messages_to_anthropic(user_messages),
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
response = self.client.messages.create(
|
|
30
|
+
**anthropic_args,
|
|
31
|
+
temperature=args.get('temperature', 1.0),
|
|
32
|
+
max_tokens=args.get('num_predict', 2000),
|
|
33
|
+
# thinking={
|
|
34
|
+
# "type": "enabled",
|
|
35
|
+
# "budget_tokens": 32768,
|
|
36
|
+
# }
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
object = None
|
|
40
|
+
tool_calls: List[LLMToolCall] = []
|
|
41
|
+
|
|
42
|
+
return LLMGatewayResponse(
|
|
43
|
+
content=response.content[0].text,
|
|
44
|
+
object=object,
|
|
45
|
+
tool_calls=tool_calls,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
def get_available_models(self) -> List[str]:
|
|
49
|
+
return sorted([m.id for m in self.client.models.list()])
|
|
50
|
+
|
|
51
|
+
def calculate_embeddings(self, text: str, model: str = "voyage-3-large") -> List[float]:
|
|
52
|
+
raise NotImplementedError("The Anthropic API does not support embedding generation.")
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
from typing import List, Any
|
|
5
|
+
|
|
6
|
+
import structlog
|
|
7
|
+
|
|
8
|
+
from mojentic.llm.gateways.models import LLMMessage, MessageRole
|
|
9
|
+
|
|
10
|
+
logger = structlog.get_logger()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def adapt_messages_to_anthropic(messages: List[LLMMessage]):
|
|
14
|
+
new_messages: List[dict[str, Any]] = []
|
|
15
|
+
for m in messages:
|
|
16
|
+
if m.role == MessageRole.System:
|
|
17
|
+
# System messages aren't supported by Anthropic
|
|
18
|
+
pass
|
|
19
|
+
elif m.role == MessageRole.User:
|
|
20
|
+
if m.image_paths is not None and len(m.image_paths) > 0:
|
|
21
|
+
# Create a content structure with text and images
|
|
22
|
+
content = []
|
|
23
|
+
if m.content:
|
|
24
|
+
content.append({"type": "text", "text": m.content})
|
|
25
|
+
|
|
26
|
+
# Add each image as a base64-encoded URL
|
|
27
|
+
for image_path in m.image_paths:
|
|
28
|
+
try:
|
|
29
|
+
with open(image_path, "rb") as image_file:
|
|
30
|
+
base64_image = base64.b64encode(image_file.read()).decode('utf-8')
|
|
31
|
+
|
|
32
|
+
# Determine image type from file extension
|
|
33
|
+
_, ext = os.path.splitext(image_path)
|
|
34
|
+
image_type = ext.lstrip('.').lower()
|
|
35
|
+
if image_type not in ['jpeg', 'jpg', 'png', 'gif', 'webp']:
|
|
36
|
+
image_type = 'jpeg' # Default to jpeg if unknown extension
|
|
37
|
+
|
|
38
|
+
content.append({
|
|
39
|
+
"type": "image",
|
|
40
|
+
"source": {
|
|
41
|
+
"type": "base64",
|
|
42
|
+
"media_type": f"image/{image_type}",
|
|
43
|
+
"data": base64_image
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
except Exception as e:
|
|
47
|
+
logger.error("Failed to encode image", error=str(e), image_path=image_path)
|
|
48
|
+
|
|
49
|
+
new_messages.append({'role': 'user', 'content': content})
|
|
50
|
+
else:
|
|
51
|
+
new_messages.append({'role': 'user', 'content': m.content})
|
|
52
|
+
elif m.role == MessageRole.Assistant:
|
|
53
|
+
msg = {'role': 'assistant', 'content': m.content or ''}
|
|
54
|
+
# if m.tool_calls is not None:
|
|
55
|
+
# msg['tool_calls'] = [{
|
|
56
|
+
# 'id': m.tool_calls[0].id,
|
|
57
|
+
# 'type': 'function',
|
|
58
|
+
# 'function': {
|
|
59
|
+
# 'name': m.tool_calls[0].name,
|
|
60
|
+
# 'arguments': json.dumps({k: v for k, v in m.tool_calls[0].arguments.items()})
|
|
61
|
+
# }
|
|
62
|
+
# }]
|
|
63
|
+
new_messages.append(msg)
|
|
64
|
+
# elif m.role == MessageRole.Tool:
|
|
65
|
+
# new_messages.append({
|
|
66
|
+
# 'role': 'tool',
|
|
67
|
+
# 'content': m.content,
|
|
68
|
+
# 'tool_call_id': m.tool_calls[0].id
|
|
69
|
+
# })
|
|
70
|
+
else:
|
|
71
|
+
logger.error("Unknown message role", role=m.role)
|
|
72
|
+
return new_messages
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Union
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class FileGateway:
|
|
6
|
+
|
|
7
|
+
def read(self, path: Union[Path, str]):
|
|
8
|
+
if isinstance(path, str):
|
|
9
|
+
path = Path(path)
|
|
10
|
+
with open(path, 'r') as file:
|
|
11
|
+
return file.read()
|
|
12
|
+
|
|
13
|
+
def exists(self, path: Union[Path, str]) -> bool:
|
|
14
|
+
"""
|
|
15
|
+
Check if a file exists.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
path : Union[Path, str]
|
|
20
|
+
Path to the file
|
|
21
|
+
|
|
22
|
+
Returns
|
|
23
|
+
-------
|
|
24
|
+
bool
|
|
25
|
+
True if the file exists, False otherwise
|
|
26
|
+
"""
|
|
27
|
+
if isinstance(path, str):
|
|
28
|
+
path = Path(path)
|
|
29
|
+
return path.exists()
|
|
30
|
+
|
|
31
|
+
def is_binary(self, path: Union[Path, str]) -> bool:
|
|
32
|
+
"""
|
|
33
|
+
Check if a file is binary.
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
path : Union[Path, str]
|
|
38
|
+
Path to the file
|
|
39
|
+
|
|
40
|
+
Returns
|
|
41
|
+
-------
|
|
42
|
+
bool
|
|
43
|
+
True if the file is binary, False otherwise
|
|
44
|
+
"""
|
|
45
|
+
if isinstance(path, str):
|
|
46
|
+
path = Path(path)
|
|
47
|
+
try:
|
|
48
|
+
with open(path, 'rb') as file:
|
|
49
|
+
# Read the first 1024 bytes
|
|
50
|
+
chunk = file.read(1024)
|
|
51
|
+
# Check for null bytes, which are typically present in binary files
|
|
52
|
+
return b'\0' in chunk
|
|
53
|
+
except Exception:
|
|
54
|
+
# If there's an error reading the file, assume it's binary
|
|
55
|
+
return True
|