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

Feature/v0.2.1/examples body data analysis #115

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
110 changes: 110 additions & 0 deletions examples/body_data_analysis/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Body Data Analysis

This example shows how to implement body data based health analysis based on loop, sample code can be found in the `examples/body_data_analysis` directory.

```bash
cd examples/body_data_analysis
```

## Overview

This example implements a body data analysis workflow that consists of the following key components:

1. **Body Data Acquisition**
Acquire and process user body data: the current user body data needs to be entered in advance in `examples/body_data_analysis/agent/body_data_acquisition/body_data.json`, the user can enter all the information, or only part of the information, the Agent will be based on the whether the information is missing or not and take the relevant query. (In the future, we will consider designing to get the relevant body data directly from the body fat scale)

2. **Interactive QA cycle**
- BodyAnalysisQA: Conduct an interactive QA to gather additional information
- Use web search tools to access specific BodyAnalysis content
- BodyAnalysisDecider: assesses whether enough information is being collected based on the following factors
- The user's physical condition
- Physical health criteria
- Use the DoWhileTask to continue the loop until enough information has been collected.
- The loop terminates when BodyAnalysisDecider returns decision=true.

3. **Final analysis**
- Body data analysis: Generate analysis based on
- Information gathered in a question and answer cycle
- Other information obtained through web searches

4. **Workflow Flow**
```
Start -> Body Data Acquisition -> Body_analysis_qa_loop(QA + Weather Search + Decision) -> Final analysis -> End

```

Workflows utilize Redis for state management and the Conductor server for workflow orchestration. This architecture enables
- Acquisition of user body data
- Health advice using web data
- Interactive improvement through structured Q&A
- Context-aware recommendations that combine multiple factors
- Continuous state management throughout the workflow


## Prerequisites

- Python 3.10+
- Required packages installed (see requirements.txt)
- Access to OpenAI API or compatible endpoint
- Access to Bing API key for web search functionality to search real-time weather information for outfit recommendations (see configs/tools/websearch.yml)
- Redis server running locally or remotely
- Conductor server running locally or remotely

## Configuration

The container.yaml file is a configuration file that manages dependencies and settings for different components of the system, including Conductor connections, Redis connections, and other service configurations. To set up your configuration:

1. Generate the container.yaml file:
```bash
python compile_container.py
```
This will create a container.yaml file with default settings under `examples/body_data_analysis`.

2. Configure your LLM settings in `configs/llms/gpt.yml` and `configs/llms/text_res.yml`:
- Set your OpenAI API key or compatible endpoint through environment variable or by directly modifying the yml file
```bash
export custom_openai_key="your_openai_api_key"
export custom_openai_endpoint="your_openai_endpoint"
```
- Configure other model settings like temperature as needed through environment variable or by directly modifying the yml file

3. Configure your Bing Search API key in `configs/tools/websearch.yml`:
- Set your Bing API key through environment variable or by directly modifying the yml file
```bash
export bing_api_key="your_bing_api_key"
```

4. Update settings in the generated `container.yaml`:
- Modify Redis connection settings:
- Set the host, port and credentials for your Redis instance
- Configure both `redis_stream_client` and `redis_stm_client` sections
- Update the Conductor server URL under conductor_config section
- Adjust any other component settings as needed

## Running the Example

1. Run the Body Data Analysis workflow:

Before running you need to fill in the body data in `examples/body_data_analysis/agent/body_data_acquisition/body_data.json` to simulate the scale results (or not)
If you want to use Chinese prompt, you can change the suffix name of the prompt file in 'body_analysis_qa' and 'body_analysis_decider' to _zh, and set the language to 'zh' in the acquisition `body_data_string = get_body_data(body_data['Body_Data']. language = "zh")`

For terminal/CLI usage:
```bash
python run_cli.py
```



## Troubleshooting

If you encounter issues:
- Verify Redis is running and accessible
- Check your OpenAI API key and Bing API key are valid
- Ensure all dependencies are installed correctly
- Review logs for any error messages
- Confirm Conductor server is running and accessible
- Check Redis Stream client and Redis STM client configuration

## Building the Example

Coming soon! This section will provide detailed instructions for building the step3_outfit_with_loop example step by step.
Empty file.
47 changes: 47 additions & 0 deletions examples/body_data_analysis/agent/body_analysis/body_analysis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from pathlib import Path
from typing import List

from omagent_core.models.llms.base import BaseLLMBackend
from omagent_core.engine.worker.base import BaseWorker
from omagent_core.utils.registry import registry
from omagent_core.models.llms.prompt.prompt import PromptTemplate
from omagent_core.models.llms.openai_gpt import OpenaiGPTLLM

from pydantic import Field


CURRENT_PATH = Path(__file__).parents[0]


@registry.register_worker()
class BodyAnalysis(BaseWorker, BaseLLMBackend):

llm: OpenaiGPTLLM

prompts: List[PromptTemplate] = Field(
default=[
PromptTemplate.from_file(
CURRENT_PATH.joinpath("sys_prompt_en.prompt"), role="system"
),
PromptTemplate.from_file(
CURRENT_PATH.joinpath("user_prompt_en.prompt"), role="user"
),
]
)

def _run(self, *args, **kwargs):

# Retrieve user instruction and optional weather info from workflow context
user_body_data = self.stm(self.workflow_instance_id).get("user_body_data")

chat_complete_res = self.simple_infer(bodydata=str(user_body_data))

# Extract recommendations from LLM response
body_analysis = chat_complete_res["choices"][0]["message"]["content"]

# Send recommendations via callback and return
self.callback.send_answer(agent_id=self.workflow_instance_id, msg=body_analysis)

self.stm(self.workflow_instance_id).clear()
return body_analysis

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
You are an experienced fitness personal trainer who specializes in analyzing your own physical condition for your clients based on international health indicators.

The following factors should be considered for a body condition analysis:

- Physical indicators provided by the user (e.g. height, weight, age, body fat percentage, muscle mass, etc.)
- Helping users make an analysis based on international health indicators that

At least contains:
1. BMI.
2. body fat percentage,
3. basal metabolic level (to give an approximate estimate).

It can additionally contain:
1. muscle mass, 2.
2. visceral fat grade, 3. obesity
3. obesity.
4. other health indicators that should be analyzed.

Note: Do not provide content other than the analysis of the user's physical condition, including exercise improvement programs, diet improvement, etc.

Presentation Requirements:

- Use simple, easy-to-understand language to ensure that the user can understand and perform
- Use a friendly, encouraging and supportive tone
- Do not use overly specialized terminology unless necessary and provide explanations
- Do not provide additional advice beyond the analysis of the body's state of health (e.g., fitness programs, dietary improvements, etc.)
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
您是一位资深健身私教,擅长为客户基于国际健康指标分析自己的身体状况。

身体状况分析应考虑以下因素:

- 用户提供的身体指标(如身高、体重、年龄、体脂率、肌肉量等)
- 基于国际健康指标帮助用户做出分析,
至少包含:
1. BMI,
2. 体脂率,
3. 基础代谢水平(给出大概估算数值),
额外可以包含:
1. 肌肉率,
2. 内脏脂肪等级,
3. 肥胖度,
4. 其他应该分析的健康指标。

注意:不提供除了用户身体状况分析意外的其他内容,包括运动改善计划,饮食改善等内容。

表达要求:

- 使用简单易懂的语言,确保用户能够理解和执行
- 语气友好、鼓励,并充满支持
- 不要使用过于专业的术语,除非必要,并提供解释
- 除了身体的健康状态的分析外不提供额外的其他建议(比如健身计划,饮食改善等)

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Now, it's your turn to complete the task.
Give anwer using the language according to the user's answer.

Input Information:
- Information about the user's body metrics: {{bodydata}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
现在,轮到你完成任务了。
根据用户的答案使用相应的语言给出答案。

Input Information:
- 用户身体指标信息: {{bodydata}}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import json_repair
import re
from pathlib import Path
from typing import List
from pydantic import Field

from omagent_core.models.llms.base import BaseLLMBackend
from omagent_core.utils.registry import registry
from omagent_core.models.llms.prompt.prompt import PromptTemplate
from omagent_core.engine.worker.base import BaseWorker
from omagent_core.models.llms.prompt.parser import StrParser
from omagent_core.models.llms.openai_gpt import OpenaiGPTLLM
from omagent_core.utils.logger import logging


CURRENT_PATH = root_path = Path(__file__).parents[0]


@registry.register_worker()
class BodyAnalysisDecider(BaseLLMBackend, BaseWorker):
llm: OpenaiGPTLLM
prompts: List[PromptTemplate] = Field(
default=[
PromptTemplate.from_file(
CURRENT_PATH.joinpath("sys_prompt_en.prompt"), role="system"
),
PromptTemplate.from_file(
CURRENT_PATH.joinpath("user_prompt_en.prompt"), role="user"
),
]
)

def _run(self, *args, **kwargs):

# Retrieve conversation context from memory, initializing empty if not present
if self.stm(self.workflow_instance_id).get("user_body_data"):
user_body_data = self.stm(self.workflow_instance_id).get("user_body_data")
else:
user_body_data = []

if self.stm(self.workflow_instance_id).get("search_info"):
search_info = self.stm(self.workflow_instance_id).get("search_info")
else:
search_info = []

if self.stm(self.workflow_instance_id).get("feedback"):
feedback = self.stm(self.workflow_instance_id).get("feedback")
else:
feedback = []

# Query LLM to analyze available information
chat_complete_res = self.simple_infer(
bodydata=str(user_body_data),
previous_search=str(search_info),
feedback=str(feedback)
)
content = chat_complete_res["choices"][0]["message"].get("content")
content = self._extract_from_result(content)
logging.info(content)

# Return decision and handle feedback if more information is needed
if content.get("decision") == "ready":
return {"decision": True}
elif content.get("reason"):
feedback.append(content["reason"])
self.stm(self.workflow_instance_id)["feedback"] = feedback
return {"decision": False}
else:
raise ValueError("LLM generation is not valid.")


def _extract_from_result(self, result: str) -> dict:
try:
pattern = r"```json\s+(.*?)\s+```"
match = re.search(pattern, result, re.DOTALL)
if match:
return json_repair.loads(match.group(1))
else:
return json_repair.loads(result)
except Exception as error:
raise ValueError("LLM generation is not valid.")
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
You are a helpful fitness personal trainer responsible for collecting information to help users analyze their physical condition based on international health indicators.

You will receive:
- Information about the user's health indicators
- Information from previous searches
- Feedback from the body data analysis decision maker on what other user information is needed to complete the analysis of the physical health state

Your task is to analyze all the information provided and determine if enough details have been gathered to generate a good physical health status analysis.

Please consider gathering information about the user's physical condition from the information required for the following health indicators.
1. BMI.
2. body fat percentage.
3. muscle mass.
4. visceral fat grade.
5. obesity level.
6. basal metabolic level.
7. waist-to-hip ratio
8. other health indicators that should be analyzed.

You should respond in this format:
{
"decision": "ready" or "need_more_info",
"reason": "If need_more_info, explain what specific information is still missing and why it's important. If ready, no explaination need to provide."
}

First and foremost, carefully analyze the user's instruction. If the user explicitly states they want an immediate recommendation or indicates they don't want to answer more questions, you should return "ready" regardless of missing information.

When evaluating whether there is enough information (only if the user has not asked for an immediate recommendation), consider the following:
1. Do you know the gender of the user?
2. Do you know the user's age?
3. Is the user's height known?
4. Is the user's weight known?
5. Are you aware of the indicators and formulas used for health analysis?
6. Do you know any other specific information that is needed to analyze your health status?

Your response must be in valid JSON format. Be specific in your reasoning about what information is missing or why the collected information is sufficient.
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
你是一名乐于助人的健身私教,负责收集信息,帮助用户基于国际健康指标分析自己的身体状况。

请考虑从以下健康指标所需的信息收集用户的身体状况,
1. BMI,
2. 体脂率,
3. 肌肉率,
4. 内脏脂肪等级,
5. 肥胖度,
6. 基础代谢水平,
7. 腰臀比
8. 其他应该分析的健康指标。


您将接收
- 用户的身体指标信息
- 之前搜索过的信息
- 来自身体数据分析决定者的反馈,说明还需要哪些其他用户信息来完成身体健康状态分析

您的任务是分析所有提供的信息,并判断是否已经收集到足够的细节来生成一个好的身体健康状态分析。

您应该按照以下格式进行回复:
{
"decision": "ready" 或 "need_more_info"、
"reason": "如果需要更多信息("need_more_info"),请解释还缺少哪些具体信息,以及为什么这些信息很重要。如果准备就绪("ready"),则无需解释。"
}

首先要仔细分析用户当前的信息。如果用户明确表示他们想要立即得到分析,或者表示他们不想回答更多问题,那么无论是否缺少信息,都应该返回"ready"。

在评估是否有足够的信息时(仅当用户没有要求立即推荐时),请考虑以下几点:
1. 您知道用户的性别吗?
2. 是否了解用户年龄?
3. 是否了解用户身高?
4. 是否知晓了用户体重?
5. 是否了解健康分析的指标以及计算公式
6. 是否了解其他需要用于身体健康状态分析的具体信息


您的回复必须是有效的 JSON 格式。请具体说明缺少哪些信息或已收集的信息为何足够。
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
现在,轮到你来完成任务了。

输入信息:
- 用户身体指标信息: {{bodydata}}
- 之前搜索过的信息:{{previous_search}}
- 决定者的反馈: {{feedback}}

Loading