Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Source2synth #1289

Open
wants to merge 69 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 66 commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
0e31d55
Add logger and use it
CaelumF Jul 3, 2024
ea73cbb
LOGLEVEL env var
CaelumF Jul 3, 2024
a8be4a4
Better handling of animated logging
CaelumF Jul 3, 2024
3141161
Configure logging for capturing
CaelumF Jul 4, 2024
87c6bb8
Merge branch 'master' into feature/add-logging
CaelumF Jul 4, 2024
3b627e0
Try more flexible PrintLogger
CaelumF Jul 4, 2024
205af73
Merge remote-tracking branch 'origin/feature/add-logging' into featur…
CaelumF Jul 4, 2024
62e5728
Fix log output capturing
CaelumF Jul 4, 2024
10f17aa
Add string format specifier
CaelumF Jul 8, 2024
e5d961f
Merge branch 'master' into feature/add-logging
CaelumF Jul 8, 2024
760cafd
Remove unused function
CaelumF Jul 8, 2024
36e3005
Add format specifier
CaelumF Jul 8, 2024
4d7e244
Merge master changes in
CaelumF Jul 12, 2024
586babb
Add logging to refactored twitter toolkit, update test
CaelumF Jul 12, 2024
c9812d1
Change to twitter_toolkit for create tweet test
CaelumF Jul 12, 2024
2473ccd
Merge branch 'master' into feature/add-logging
CaelumF Jul 15, 2024
f96d726
Move configure logging call to logging.py, import exported functions
CaelumF Jul 15, 2024
5f86331
Improve doccomments
CaelumF Jul 15, 2024
640ba82
Remove logger for CLI usage
CaelumF Jul 15, 2024
40014fe
Configure on logger import
CaelumF Jul 15, 2024
a7d87a2
Add no case
CaelumF Jul 22, 2024
2330c6e
Merge branch 'master' into feature/add-logging
CaelumF Jul 22, 2024
9b21bae
Merge branch 'master' into feature/add-logging
CaelumF Jul 22, 2024
3816c24
Validate log level
CaelumF Jul 26, 2024
9d7d76a
Initial source2synth port
CaelumF Nov 29, 2024
6deb08a
Translate
CaelumF Nov 29, 2024
3c90f59
Example usage changes
CaelumF Nov 29, 2024
3548528
Separate out files
CaelumF Dec 2, 2024
d15cccb
Merge master
CaelumF Dec 2, 2024
9e8fe98
Merge
CaelumF Dec 2, 2024
7adcc74
feat: Use CAMEL_LOGGING_DISABLED env var for logging control
CaelumF Dec 2, 2024
ba5d5a9
Merge fixes
CaelumF Dec 2, 2024
62602d7
Sort imports, explicitly use default logger class
CaelumF Dec 2, 2024
a0c9cae
Formatting fixes
CaelumF Dec 2, 2024
727982b
docs: Update logger doc comments for consistency with guidelines
CaelumF Dec 2, 2024
16eb58e
Use new doccomment format
CaelumF Dec 2, 2024
02b87e8
Merge branch 'feature/add-logging' into source2synth
CaelumF Dec 2, 2024
bf90830
fix the bug
zjrwtx Dec 6, 2024
8800962
fixt the bug
zjrwtx Dec 6, 2024
19f1ab3
fix the bug
zjrwtx Dec 6, 2024
95cd598
Merge remote-tracking branch 'origin/source2synth' into source2synth
CaelumF Dec 6, 2024
e7131f4
WIP programmatic agent interface
CaelumF Dec 6, 2024
c0bc6b5
feat: Add Pydantic models for multi-hop QA data generation
CaelumF Dec 6, 2024
da461a8
refactor: Enhance data structures with Pydantic models for validation
CaelumF Dec 6, 2024
2ae2cdc
fix: Define system_message and import ReasoningStep in ai_model_handl…
CaelumF Dec 6, 2024
e527a9b
refactor: Remove duplicate ReasoningStep class definition
CaelumF Dec 6, 2024
301e803
feat: Add descriptions to fields in Pydantic models for clarity
CaelumF Dec 6, 2024
8be1a92
feat: Update MultiHopGeneratorAgent to use structured output format
CaelumF Dec 6, 2024
510c63e
feat: Change return type of generate_multi_hop_qa to ProgrammedAgentI…
CaelumF Dec 6, 2024
aa20f1b
Typed response
CaelumF Dec 6, 2024
98a40a3
fix: Validate response content and raise error on no response from agent
CaelumF Dec 6, 2024
b020c91
refactor: Move MultiHopGeneratorAgent to its own file for better orga…
CaelumF Dec 6, 2024
dd7d14b
fix: Import 'Any' from typing to resolve undefined name error
CaelumF Dec 6, 2024
a77b778
Add licenses
CaelumF Dec 6, 2024
2a82479
feat: Integrate MultiHopGeneratorAgent into data processing workflow
CaelumF Dec 6, 2024
7e2499c
Merge
CaelumF Dec 13, 2024
aae9d8f
Merge remote-tracking branch 'origin/master' into source2synth
CaelumF Dec 13, 2024
836e798
Merge remote-tracking branch 'origin/master' into source2synth
CaelumF Dec 14, 2024
21362cc
Fix pydantic issues
CaelumF Dec 16, 2024
90735d6
Merge remote-tracking branch 'origin/master' into source2synth
CaelumF Dec 16, 2024
0f4a96b
Fix last message role
CaelumF Dec 16, 2024
4b38f50
Remove unnecessary code
CaelumF Dec 16, 2024
0f423f6
Fix linting
CaelumF Dec 16, 2024
0e01d86
Merge branch 'master' into source2synth
CaelumF Dec 16, 2024
9871f52
Fix lint issue
CaelumF Dec 17, 2024
dfc0f9f
better typing
CaelumF Dec 27, 2024
2667f2c
Update camel/agents/programmed_agent_instruction.py
CaelumF Dec 27, 2024
41aaf8e
Update camel/agents/programmed_agent_instruction.py
CaelumF Dec 27, 2024
ccb5a32
Update camel/agents/programmed_agent_instruction.py
CaelumF Dec 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions camel/agents/multi_hop_generator_agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========

from typing import Any

from pydantic import ConfigDict

from camel.agents.programmed_agent_instruction import (
ProgrammableChatAgent,
ProgrammedAgentInstructionResult,
programmable_capability,
)
from camel.messages import BaseMessage
from camel.synthetic_datagen.source2synth.models import (
ContextPrompt,
MultiHopQA,
)


class MultiHopGeneratorAgent(ProgrammableChatAgent):
model_config = ConfigDict(arbitrary_types_allowed=True)

def __init__(self, **kwargs: Any):
super().__init__(**kwargs)

system_text: str = """You are an expert at generating multi-hop
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can use textwrap.dedent() to eliminate the leading spaces.

question-answer pairs.
For each context, you should:
1. Identify multiple related facts or pieces of information
2. Create questions that require reasoning across these multiple pieces
3. Ensure the reasoning chain is clear and logical
4. Generate questions that require at least 2-3 steps of reasoning
5. Include the reasoning steps in the answer

Give your response with this information:
Question: [Complex question requiring multiple reasoning steps]
Reasoning Steps:
1. [First reasoning step]
2. [Second reasoning step]
3. [Final reasoning step]
Answer: [Final answer]
Supporting Facts: [List of relevant text segments used]
"""
self.system_message = BaseMessage.make_assistant_message(
role_name='Assistant', content=system_text
)

@programmable_capability
def generate_multi_hop_qa(
self, context: str
) -> ProgrammedAgentInstructionResult[MultiHopQA]:
context_prompt = ContextPrompt(
main_context=context, related_contexts=None
)

user_message = BaseMessage.make_user_message(
content=context_prompt.model_dump_json(), role_name="User"
)
response = self.step(
input_message=user_message, response_format=MultiHopQA
)
value = MultiHopQA.model_validate_json(response.msgs[0].content)

if response.msgs:
return ProgrammedAgentInstructionResult(
user_message=user_message,
agent_message=response.msgs[0],
value=value,
)
raise RuntimeError("No response from agent")
154 changes: 154 additions & 0 deletions camel/agents/programmed_agent_instruction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
import abc
import threading
from enum import Enum
from functools import wraps
from typing import Any, Callable, Generic, Optional, TypeVar

from pydantic import BaseModel, ConfigDict

from camel.agents import ChatAgent
from camel.messages import BaseMessage

T = TypeVar('T')


class ProgrammableAgentRequirement(Enum):
LAST_MESSAGE_NOT_USER = "LAST_MESSAGE_NOT_USER"
Comment on lines +28 to +29
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think it's not necessary to define this enum class just for 1 compont, maybe we can pass the variable directly to reduce code complexibility

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thinking for this is that with the requirements being an enum, implementors can easily know for example that all possible requirements are being met, since requirements are likely to be shared between different agents (as can the operations that modify agent state with them in consideration). The intention here is to sort of force certainty improving code to be separate from the functional code, which can be really beneficial for keeping things expectable in the future.

Though maybe the possible requirements should be part of a type parameter on the ProgrammableChatAgent implementation (several subclasses with different numbers of type parameters might be necessary until pydantic supports variadic generics), since they should be relevant only to the @programmable_capabilitys.

I wanted to allow specifying the agent state requirements in the @programmable_capability itself, but unfortunately python's decorators are pretty cumbersome to provide parameters when decorating functions.

There might be a nice way of doing that still though, like through providing a wrapping interface to the agent methods that requires explicitly stating the state expectations, and possibly having these return a sort of atomic operation accumulating object like what torch does. Incompatible operation chains then can be easily detected before execution, and the business logic can more easily coerce state into being compatible before starting the operation chain.

I think the direction here is pretty important for programmed composable agents, paging @lightaime for thoughts.

For now though, I think this direction is okay, it means requirements need to be shared between functions inside an agent, and can be easily enough changed to a different method in the future, we should maybe just encourage people to put the state requirements in this enum



class ProgrammedAgentInstructionResult(BaseModel, Generic[T]):
r"""
Result of a programmable agent instruction execution.
CaelumF marked this conversation as resolved.
Show resolved Hide resolved

Contains the messages exchanged during execution and the computed value.
The value type is specified by the generic type parameter T.
"""

user_message: BaseMessage
agent_message: BaseMessage
value: T

model_config = ConfigDict(arbitrary_types_allowed=True)


class AbstractProgrammableAgent(abc.ABC):
r"""
Abstract class for a programmable agent.
CaelumF marked this conversation as resolved.
Show resolved Hide resolved

A programmable agent is an agent that can be programmed to perform a
specific function or task. This class defines the interface for a
programmable
agent.

These methods should be implemented in order to ensure the agent supports
the necessary guarantees to enable a programming interface while
maintaining compatibility in a multi-agent system.

A programmable agent is responsible for providing and maintaining a
programming interface for its functionality.
"""

@abc.abstractmethod
def run_atomic(
self, callback: Callable[[], ProgrammedAgentInstructionResult[T]]
) -> ProgrammedAgentInstructionResult[T]:
r"""
Run an atomic operation on the agent.
CaelumF marked this conversation as resolved.
Show resolved Hide resolved

An atomic operation is an operation that is guaranteed to
be executed without interruption by any other operation.

If the operation fails or times out the agents state should be
unchanged.

If an operation is already in progress, this method should throw an
exception. (It is up to the caller to do any queuing)

If the agent is in a state where it can perform the operation,
it must leave the agent in a state where it can perform the
operation again. Though if state changes in successful operation
improve its ability to perform the operation, it should keep them.
"""
raise NotImplementedError

@abc.abstractmethod
def repair_state(self, requirement: ProgrammableAgentRequirement) -> None:
r"""
Repair the state of the agent.

Agents may have other non-atomic interfaces, such as a user interface,
or chat between other agents.

This method should restore the agent to a state where it can perform
operations according to the specified requirement.
"""
raise NotImplementedError


def programmable_capability(
func: Callable[..., ProgrammedAgentInstructionResult[T]],
) -> Callable[..., ProgrammedAgentInstructionResult[T]]:
r"""
Decorator for programmable agent capabilities.

Wraps a method to ensure it is executed atomically via the agent's
run_atomic interface.
The decorated method must return a ProgrammedAgentInstructionResult with
appropriate type parameter.
"""

@wraps(func)
def wrapper(
self, *args: Any, **kwargs: Any
) -> ProgrammedAgentInstructionResult[T]:
return self.run_atomic(lambda: func(self, *args, **kwargs))

return wrapper


class ProgrammableChatAgent(ChatAgent, AbstractProgrammableAgent):
r"""
A chat agent that can be programmed to perform specific tasks.

Provides a default implementation of atomic execution using threading locks
and basic state tracking for message roles. Implementing classes need to
provide specific repair logic for their use cases.
"""

def __init__(self, **kwargs: Any):
super().__init__(**kwargs)
self._operation_lock = threading.Lock()
self._last_message_role: Optional[str] = None

def run_atomic(
self, callback: Callable[[], ProgrammedAgentInstructionResult[T]]
) -> ProgrammedAgentInstructionResult[T]:
if not self._operation_lock.acquire(blocking=False):
raise RuntimeError("Operation already in progress")

try:
result = callback()
self._last_message_role = result.agent_message.role_name
return result
finally:
self._operation_lock.release()

def repair_state(self, requirement: ProgrammableAgentRequirement) -> None:
if requirement == ProgrammableAgentRequirement.LAST_MESSAGE_NOT_USER:
if self._last_message_role == "user":
raise NotImplementedError(
"Must implement repair for LAST_MESSAGE_NOT_USER"
)
Loading
Loading